From 90e886c62db6ad5a1f1063c3903ba3594a68fbdd Mon Sep 17 00:00:00 2001 From: efim Date: Wed, 26 Apr 2023 11:42:40 +0400 Subject: [PATCH] new Join Room form that requests authcookie getting stubbed session #1, with auth module stubbed to accept that session in. and stubbed method for streaming room state, but that's enough to start testing room calling api's for other actions next - pass in observable from parent to re-toggle subscription websocket after successful login --- .../sunshine/planningpoker/Auth.scala | 3 +- .../planningpoker/MyHttpService.scala | 10 +- build.sbt | 2 +- .../sunshine/planningpoker/TestModels.scala | 7 +- .../sunshine/planningpoker/FrontendMain.scala | 3 +- .../planningpoker/JoinRoomComponent.scala | 117 ++++++++++++++++++ 6 files changed, 132 insertions(+), 10 deletions(-) create mode 100644 frontend/src/main/scala/industries/sunshine/planningpoker/JoinRoomComponent.scala diff --git a/backend/src/main/scala/industries/sunshine/planningpoker/Auth.scala b/backend/src/main/scala/industries/sunshine/planningpoker/Auth.scala index 95aa945..6476f24 100644 --- a/backend/src/main/scala/industries/sunshine/planningpoker/Auth.scala +++ b/backend/src/main/scala/industries/sunshine/planningpoker/Auth.scala @@ -62,7 +62,8 @@ object Auth { roomService.joinRoom(roomId, nickName, nickPassword, roomPassword) ) .leftMap(_.toString()) - newSessionId = Random.nextLong() + // newSessionId = Random.nextLong() // TODO return after i stop mocking RoomService + newSessionId = TestModels.testSessionId _ <- EitherT.liftF(sessions.update(_.updated(newSessionId, (roomId, playerId)))) } yield ResponseCookie( name = authcookieName, diff --git a/backend/src/main/scala/industries/sunshine/planningpoker/MyHttpService.scala b/backend/src/main/scala/industries/sunshine/planningpoker/MyHttpService.scala index 3a606a1..3173de8 100644 --- a/backend/src/main/scala/industries/sunshine/planningpoker/MyHttpService.scala +++ b/backend/src/main/scala/industries/sunshine/planningpoker/MyHttpService.scala @@ -24,10 +24,12 @@ object MyHttpService { AuthedRoutes.of { case GET -> Root / "subscribe" as (playerId, roomId) => { val send: Stream[IO, WebSocketFrame] = - Stream - .emits(TestModels.testChangesList) - .covary[IO] - .metered(1.second) + ( + Stream + .emits(TestModels.testChangesList) + .covary[IO] + .metered(1.second) ++ Stream.never[IO] + ) .map(state => WebSocketFrame.Text(state.asJson.noSpaces)) val receive: Pipe[IO, WebSocketFrame, Unit] = _.evalMap { diff --git a/build.sbt b/build.sbt index 7f5e1e9..59a81dd 100644 --- a/build.sbt +++ b/build.sbt @@ -33,7 +33,7 @@ lazy val frontend = project libraryDependencies += "org.scala-js" %%% "scalajs-dom" % "2.4.0", libraryDependencies += "com.raquo" %%% "laminar" % "15.0.1", libraryDependencies += "io.laminext" %%% "websocket-circe" % "0.15.0", - libraryDependencies += "io.laminext" %%% "fetch" % "0.15.0" + libraryDependencies += "io.laminext" %%% "fetch-circe" % "0.15.0" ) .dependsOn(common.js) diff --git a/common/src/main/scala/industries/sunshine/planningpoker/TestModels.scala b/common/src/main/scala/industries/sunshine/planningpoker/TestModels.scala index 4466506..ff93582 100644 --- a/common/src/main/scala/industries/sunshine/planningpoker/TestModels.scala +++ b/common/src/main/scala/industries/sunshine/planningpoker/TestModels.scala @@ -3,7 +3,7 @@ package industries.sunshine.planningpoker import industries.sunshine.planningpoker.common.Models.* object TestModels { - val me = Player("wormy", PlayerID(1)) + val me = Player("me", PlayerID(1)) val pony = Player("pony", PlayerID(10)) val birdy = Player("birdy", PlayerID(11)) val horsey = Player("horsey", PlayerID(12)) @@ -17,10 +17,11 @@ object TestModels { round = RoundState.Viewing( List(me.id -> "xs", pony.id -> "l", birdy.id -> "s", horsey.id -> "m") ), - playersPasswords = Map.empty // nickname into password + playersPasswords = Map("me" -> "nickpassword") // nickname into password ) - val testSessions = Map(1L -> (testRoomBackend.id, me.id)) + val testSessionId = 1L + val testSessions = Map(testSessionId -> (testRoomBackend.id, me.id)) val testRooms = Map(testRoomBackend.id -> testRoomBackend) val testChangesList = List( diff --git a/frontend/src/main/scala/industries/sunshine/planningpoker/FrontendMain.scala b/frontend/src/main/scala/industries/sunshine/planningpoker/FrontendMain.scala index 3b46e5a..5ebfea0 100644 --- a/frontend/src/main/scala/industries/sunshine/planningpoker/FrontendMain.scala +++ b/frontend/src/main/scala/industries/sunshine/planningpoker/FrontendMain.scala @@ -59,7 +59,8 @@ object Main { className := "h-24 w-full flex flex-for justify-center items-center bg-green-200", p(className := "text-2xl", "Here be header") ), - RoomView.renderRoom(stateSignal), + child <-- roomStateWSStream.isConnected.map( if (_) emptyNode else JoinRoomComponent.render()), + child <-- roomStateWSStream.isConnected.map( if (_) RoomView.renderRoom(stateSignal) else emptyNode), roomStateWSStream.connect ) } diff --git a/frontend/src/main/scala/industries/sunshine/planningpoker/JoinRoomComponent.scala b/frontend/src/main/scala/industries/sunshine/planningpoker/JoinRoomComponent.scala new file mode 100644 index 0000000..ec349de --- /dev/null +++ b/frontend/src/main/scala/industries/sunshine/planningpoker/JoinRoomComponent.scala @@ -0,0 +1,117 @@ +package industries.sunshine.planningpoker + +import scala.scalajs.js +import scala.scalajs.js.annotation.* +import com.raquo.laminar.api.L.{*, given} +import io.laminext.fetch.Fetch +import io.laminext.fetch.circe._ + +import scala.util.Failure +import scala.util.Success + + +import concurrent.ExecutionContext.Implicits.global + +object JoinRoomComponent { + // TODO inputs for room name, room password, nick name, nick password + // do the get to /login route + // display errors if any. + // but then what? attempt to start the websocket? + // and if websocket closed show this component, + // if it's open - doesn't show this component, show the room. + // i suppose it should be managed on a level up + // or rather what? ws stream should be retried every time someone presses submit button + // and receives 200 ok + // so, parent page should send in observer for the successful auth. and on that observer - start \ restart the websocket + def nameInput(data: Var[String], placeholderText: String) = input( + className := "border-2 m-1 rounded", + placeholder := placeholderText, + controlled( + value <-- data.signal, + onInput.mapToValue --> data.writer + ) + ) + def passInput(data: Var[String], placeholderText: String) = input( + tpe := "password", + className := "border-2 m-1 rounded", + placeholder := placeholderText, + controlled( + value <-- data.signal, + onInput.mapToValue --> data.writer + ) + ) + + val roomNameVar = Var("testroom") + val roomPassVar = Var("password") + val nicknameVar = Var("me") + val nicknamePass = Var("nickpass") + + val (responsesStream, responseReceived) = EventStream.withCallback[FetchResponse[String]] + + val submitButton = button( + "Join room", + onClick + .mapTo { + (roomNameVar.now(), roomPassVar.now(), nicknameVar.now(), nicknamePass.now()) + } + .flatMap { case (roomName, roomPass, nickname, nicknamePass) => + Fetch + .post( + "/api/login", + body = Requests.LogIn( + roomName, + nickname, + roomPass, + nicknamePass + ) + ) + .text + } --> responseReceived + ) + + def render(): Element = { + div( + className := "flex flex-col h-full justify-center", + "Logging in:", + nameInput(roomNameVar, "Enter room name:"), + passInput(roomPassVar, "room password"), + nameInput(nicknameVar, "Enter your nickname:"), + passInput(nicknamePass, "nickname pass:"), + submitButton, + div( + div( + code("received:") + ), + div( + cls := "flex flex-col space-y-4 p-4 max-h-96 overflow-auto bg-gray-900", + children.command <-- responsesStream.recoverToTry.map { + case Success(response) => + CollectionCommand.Append( + div( + div( + cls := "flex space-x-2 items-center", + code(cls := "text-green-500", "Status: "), + code(cls := "text-green-300", s"${response.status} ${response.statusText}") + ), + div( + cls := "text-green-400 text-xs", + code(response.data) + ) + ) + ) + case Failure(exception) => + CollectionCommand.Append( + div( + div( + cls := "flex space-x-2 items-center", + code(cls := "text-red-500", "Error: "), + code(cls := "text-red-300", exception.getMessage) + ) + ) + ) + } + ) + ) + ) + } +}