From df35f09b710a69803f536be56b44154c8e469238 Mon Sep 17 00:00:00 2001 From: efim Date: Sun, 23 Apr 2023 15:00:00 +0400 Subject: [PATCH] circe codec derivation to models --- .../planningpoker/MyHttpService.scala | 8 +- build.sbt | 13 ++- .../sunshine/planningpoker/Models.scala | 56 ++++----- .../sunshine/planningpoker/Requests.scala | 5 +- .../sunshine/planningpoker/TestModels.scala | 106 ++++++++++++++++++ .../planningpoker/PlanningPokerUrgh.scala | 4 +- .../sunshine/planningpoker/TableView.scala | 4 +- 7 files changed, 148 insertions(+), 48 deletions(-) create mode 100644 common/src/main/scala/industries/sunshine/planningpoker/TestModels.scala diff --git a/backend/src/main/scala/industries/sunshine/planningpoker/MyHttpService.scala b/backend/src/main/scala/industries/sunshine/planningpoker/MyHttpService.scala index 20b783b..ee7d1a2 100644 --- a/backend/src/main/scala/industries/sunshine/planningpoker/MyHttpService.scala +++ b/backend/src/main/scala/industries/sunshine/planningpoker/MyHttpService.scala @@ -4,7 +4,8 @@ import cats.effect._ import cats.syntax.all._ import org.http4s._, org.http4s.dsl.io._, org.http4s.implicits._ import org.http4s.websocket.WebSocketFrame -import io.circe.generic.auto._ +// import io.circe.generic.auto._ +import io.circe.syntax._ import org.http4s.circe.CirceEntityDecoder._ import scala.concurrent.duration._ import org.http4s.server.websocket.WebSocketBuilder @@ -23,9 +24,14 @@ object MyHttpService { AuthedRoutes.of { case GET -> Root / subscribe as (playerId, roomId) => { val send: Stream[IO, WebSocketFrame] = + { + val a = Stream + .emits(TestModels.testChangesList) + .covary[IO].metered(5.second).map(state => WebSocketFrame.Text(state.asJson.noSpaces)) Stream .awakeEvery[IO](1.seconds) .map(_ => WebSocketFrame.Text("text")) + } val receive: Pipe[IO, WebSocketFrame, Unit] = _.evalMap { case WebSocketFrame.Text(text, _) => Sync[IO].delay(println(text)) case other => Sync[IO].delay(println(other)) diff --git a/build.sbt b/build.sbt index c2fd62b..52c7a0a 100644 --- a/build.sbt +++ b/build.sbt @@ -52,16 +52,17 @@ lazy val backend = project libraryDependencies += "co.fs2" %% "fs2-core" % "3.6.1", libraryDependencies += "org.typelevel" %% "cats-core" % "2.9.0", libraryDependencies += "org.typelevel" %% "cats-effect" % "3.4.9", - libraryDependencies ++= Seq( - "io.circe" %% "circe-core", - "io.circe" %% "circe-generic", - "io.circe" %% "circe-parser" - ).map(_ % circeVersion) ) .dependsOn(common) lazy val common = project .in(file("common")) .settings( - commonSettings + commonSettings, + libraryDependencies ++= Seq( + "io.circe" %% "circe-core", + "io.circe" %% "circe-generic", + "io.circe" %% "circe-parser" + ).map(_ % circeVersion), + ) diff --git a/common/src/main/scala/industries/sunshine/planningpoker/Models.scala b/common/src/main/scala/industries/sunshine/planningpoker/Models.scala index 1d559ce..9509bb9 100644 --- a/common/src/main/scala/industries/sunshine/planningpoker/Models.scala +++ b/common/src/main/scala/industries/sunshine/planningpoker/Models.scala @@ -1,6 +1,7 @@ package industries.sunshine.planningpoker.common import java.util.UUID +import io.circe._ object Models { @@ -20,58 +21,41 @@ object Models { allowedCards: List[String], round: RoundState, canCloseRound: Boolean = false - ) { + ) derives Codec.AsObject { def playersCount: Int = players.size } - object RoomStateView { - val me = Player("wormy", PlayerID(1)) - val testRoom = { - val pony = Player("pony", PlayerID(2)) - RoomStateView( - players = List(me, Player("birdy", PlayerID(3)), pony), - me = me.id, - allowedCards = List("xs", "s", "m", "l", "xl"), - round = VotingRound(myCard = Some("s"), alreadyVoted = List(me, pony)), - canCloseRound = true - ) - } - val testOpenedRoom = { - val pony = Player("pony", PlayerID(10)) - val birdy = Player("birdy", PlayerID(11)) - val horsey = Player("horsey", PlayerID(12)) - RoomStateView( - players = List(me, birdy, pony, horsey), - me = me.id, - allowedCards = List("xs", "s", "m", "l", "xl"), - round = ViewingRound( - Map(me.id -> "xs", pony.id -> "l", birdy.id -> "s", horsey.id -> "m") - ), - canCloseRound = true - ) - } + given Encoder[Map[PlayerID, String]] = Encoder.encodeMap[PlayerID, String] + given Decoder[Map[PlayerID, String]] = Decoder.decodeMap[PlayerID, String] + given Codec[Map[PlayerID, String]] = Codec.from(summon[Decoder[Map[PlayerID, String]]], summon[Encoder[Map[PlayerID, String]]]) - } - trait RoundState + enum RoundState derives Codec.AsObject: /** view state for round before votes are open player can know their vote and * who of the other players have voted */ - final case class VotingRound( + case Voting( myCard: Option[String], alreadyVoted: List[Player] - ) extends RoundState + ) /** view state for round after opening the votes */ - final case class ViewingRound( + case Viewing( votes: Map[PlayerID, String] - ) extends RoundState + ) - final case class PlayerID(id: Long) - final case class Player(name: String, id: PlayerID) + final case class PlayerID(id: Long) derives Codec.AsObject + object PlayerID { + given KeyEncoder[PlayerID] with + def apply(key: PlayerID): String = key.toString + given KeyDecoder[PlayerID] with + def apply(key: String): Option[PlayerID] = key.toLongOption.map(PlayerID.apply(_)) + } - final case class RoomID(id: Long) + final case class Player(name: String, id: PlayerID) derives Codec.AsObject + + final case class RoomID(id: Long) derives Codec.AsObject } diff --git a/common/src/main/scala/industries/sunshine/planningpoker/Requests.scala b/common/src/main/scala/industries/sunshine/planningpoker/Requests.scala index 25e3e39..7f750a2 100644 --- a/common/src/main/scala/industries/sunshine/planningpoker/Requests.scala +++ b/common/src/main/scala/industries/sunshine/planningpoker/Requests.scala @@ -1,5 +1,8 @@ package industries.sunshine.planningpoker +import io.circe.generic.semiauto._ +import io.circe._ + object Requests { - final case class LogIn(roomName: String, nickname: String, password: String) + final case class LogIn(roomName: String, nickname: String, password: String) derives Codec.AsObject } diff --git a/common/src/main/scala/industries/sunshine/planningpoker/TestModels.scala b/common/src/main/scala/industries/sunshine/planningpoker/TestModels.scala new file mode 100644 index 0000000..515a6a2 --- /dev/null +++ b/common/src/main/scala/industries/sunshine/planningpoker/TestModels.scala @@ -0,0 +1,106 @@ +package industries.sunshine.planningpoker + +import industries.sunshine.planningpoker.common.Models.* + +object TestModels { + val me = Player("wormy", PlayerID(1)) + val pony = Player("pony", PlayerID(10)) + val birdy = Player("birdy", PlayerID(11)) + val horsey = Player("horsey", PlayerID(12)) + + val testRoom = { + RoomStateView( + players = List(me, birdy, pony), + me = me.id, + allowedCards = List("xs", "s", "m", "l", "xl"), + round = RoundState.Voting(myCard = Some("s"), alreadyVoted = List(me, pony)), + canCloseRound = true + ) + } + val testOpenedRoom = { + RoomStateView( + players = List(me, birdy, pony, horsey), + me = me.id, + allowedCards = List("xs", "s", "m", "l", "xl"), + round = RoundState.Viewing( + Map(me.id -> "xs", pony.id -> "l", birdy.id -> "s", horsey.id -> "m") + ), + canCloseRound = true + ) + } + + val testChangesList = List( + RoomStateView( + players = List(me), + me = me.id, + allowedCards = List("xs", "s", "m", "l", "xl"), + round = RoundState.Voting(myCard = None, alreadyVoted = List.empty), + canCloseRound = true + ), + RoomStateView( + players = List(me, pony), + me = me.id, + allowedCards = List("xs", "s", "m", "l", "xl"), + round = RoundState.Voting(myCard = None, alreadyVoted = List.empty), + canCloseRound = true + ), + RoomStateView( + players = List(me, birdy, pony), + me = me.id, + allowedCards = List("xs", "s", "m", "l", "xl"), + round = RoundState.Voting(myCard = None, alreadyVoted = List.empty), + canCloseRound = true + ), + RoomStateView( + players = List(me, birdy, pony), + me = me.id, + allowedCards = List("xs", "s", "m", "l", "xl"), + round = RoundState.Voting(myCard = None, alreadyVoted = List(birdy)), + canCloseRound = true + ), + RoomStateView( + players = List(me, birdy, pony), + me = me.id, + allowedCards = List("xs", "s", "m", "l", "xl"), + round = RoundState.Voting(myCard = Some("m"), alreadyVoted = List(birdy)), + canCloseRound = true + ), + RoomStateView( + players = List(me, birdy, pony), + me = me.id, + allowedCards = List("xs", "s", "m", "l", "xl"), + round = RoundState.Voting(myCard = Some("m"), alreadyVoted = List(birdy)), + canCloseRound = true + ), + RoomStateView( + players = List(me, birdy, pony, horsey), + me = me.id, + allowedCards = List("xs", "s", "m", "l", "xl"), + round = RoundState.Voting(myCard = Some("m"), alreadyVoted = List(birdy)), + canCloseRound = true + ), + RoomStateView( + players = List(me, birdy, pony, horsey), + me = me.id, + allowedCards = List("xs", "s", "m", "l", "xl"), + round = RoundState.Voting(myCard = Some("m"), alreadyVoted = List(birdy, horsey)), + canCloseRound = true + ), + RoomStateView( + players = List(me, birdy, pony, horsey), + me = me.id, + allowedCards = List("xs", "s", "m", "l", "xl"), + round = RoundState.Voting(myCard = Some("m"), alreadyVoted = List(birdy, horsey, pony)), + canCloseRound = true + ), + RoomStateView( + players = List(me, birdy, pony, horsey), + me = me.id, + allowedCards = List("xs", "s", "m", "l", "xl"), + round = RoundState.Viewing( + Map(me.id -> "m", pony.id -> "l", birdy.id -> "s", horsey.id -> "m") + ), + canCloseRound = true + ) + ) +} diff --git a/frontend/src/main/scala/industries/sunshine/planningpoker/PlanningPokerUrgh.scala b/frontend/src/main/scala/industries/sunshine/planningpoker/PlanningPokerUrgh.scala index 69108ab..586b440 100644 --- a/frontend/src/main/scala/industries/sunshine/planningpoker/PlanningPokerUrgh.scala +++ b/frontend/src/main/scala/industries/sunshine/planningpoker/PlanningPokerUrgh.scala @@ -23,7 +23,7 @@ object Main { myId: Option[PlayerID] ) // TODO is this ok for state creation? link with auth component and use in another? - val appStateSignal = Var(AppState(Some(RoomStateView.me.id))).signal + val appStateSignal = Var(AppState(Some(TestModels.me.id))).signal def appElement(): Element = { div( @@ -32,7 +32,7 @@ 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(RoomStateView.testRoom) + RoomView.renderRoom(TestModels.testRoom) ) } } diff --git a/frontend/src/main/scala/industries/sunshine/planningpoker/TableView.scala b/frontend/src/main/scala/industries/sunshine/planningpoker/TableView.scala index db6f3fd..67b157d 100644 --- a/frontend/src/main/scala/industries/sunshine/planningpoker/TableView.scala +++ b/frontend/src/main/scala/industries/sunshine/planningpoker/TableView.scala @@ -40,11 +40,11 @@ object TableView { myId: Option[PlayerID] ): CardState = { state match { - case isOpen: VotingRound => + case isOpen: RoundState.Voting => if (myId.forall(_ == id)) { isOpen.myCard.fold(NoCard(name))(vote => Open(vote)) } else isOpen.alreadyVoted.find(_.id == id).fold(NoCard(name))(_ => CardBack) - case isClosed: ViewingRound => + case isClosed: RoundState.Viewing => isClosed.votes .get(id) .fold {