backend - adding room service and wiring
preparing to create a better auth module that uses room service
This commit is contained in:
parent
dfff814079
commit
64267a5f67
|
@ -6,7 +6,8 @@ import cats.data.OptionT
|
||||||
import org.http4s.Request
|
import org.http4s.Request
|
||||||
import industries.sunshine.planningpoker.common.Models.PlayerID
|
import industries.sunshine.planningpoker.common.Models.PlayerID
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import industries.sunshine.planningpoker.common.Models.RoomID
|
import industries.sunshine.planningpoker.common.Models.{RoomID, Room}
|
||||||
|
import org.http4s.ResponseCookie
|
||||||
|
|
||||||
trait Auth {
|
trait Auth {
|
||||||
|
|
||||||
|
@ -24,10 +25,10 @@ trait Auth {
|
||||||
roomName: String,
|
roomName: String,
|
||||||
roomPassword: String,
|
roomPassword: String,
|
||||||
nickName: String
|
nickName: String
|
||||||
): IO[Either[Unit, Long]]
|
): IO[Either[Unit, ResponseCookie]]
|
||||||
|
|
||||||
def leaveRoom(
|
def leaveRoom(
|
||||||
sessionId: Long
|
sessionId: Long
|
||||||
): IO[Unit]
|
): IO[Unit]
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -36,38 +37,56 @@ object Auth {
|
||||||
def pureBadStub = new Auth {
|
def pureBadStub = new Auth {
|
||||||
override def authUser =
|
override def authUser =
|
||||||
Kleisli((req: Request[IO]) =>
|
Kleisli((req: Request[IO]) =>
|
||||||
OptionT.liftF(IO(println(s"authUser: $req")) >> IO(PlayerID(14) -> RoomID(101)))
|
OptionT.liftF(
|
||||||
|
IO(println(s"authUser: $req")) >> IO(
|
||||||
|
PlayerID(14) -> RoomID("testroom")
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
override def accessRoom(
|
override def accessRoom(
|
||||||
roomName: String,
|
roomName: String,
|
||||||
roomPassword: String,
|
roomPassword: String,
|
||||||
nickName: String
|
nickName: String
|
||||||
) =
|
) =
|
||||||
IO(println(s"> access room for $roomName $roomPassword $nickName, to return stub 111")) >> IO.pure(Right(111L))
|
IO(
|
||||||
|
println(
|
||||||
|
s"> access room for $roomName $roomPassword $nickName, to return stub 111"
|
||||||
|
)
|
||||||
|
) >> IO.pure(Right(ResponseCookie("authcookie", "1")))
|
||||||
|
|
||||||
override def leaveRoom(sessionId: Long): IO[Unit] =
|
override def leaveRoom(sessionId: Long): IO[Unit] =
|
||||||
IO(s"got request to leave for $sessionId")
|
IO(s"got request to leave for $sessionId")
|
||||||
}
|
}
|
||||||
|
|
||||||
type SessionsMap = Map[Long, (RoomID, PlayerID)]
|
type SessionsMap = Map[Long, (RoomID, PlayerID)]
|
||||||
val sessionsRef = Ref.of[IO, SessionsMap](Map.empty)
|
val sessionsRef =
|
||||||
val roomPasswordsRef = Ref.of[IO, Map[Long, String]](Map.empty)
|
Ref.of[IO, SessionsMap](Map(1L -> (RoomID("testroom"), PlayerID(1L))))
|
||||||
|
val roomsRef =
|
||||||
|
Ref.of[IO, Map[RoomID, Room]](Map(RoomID("testroom") -> Room.testRoom))
|
||||||
|
|
||||||
def apply(): IO[Auth] =
|
def apply(): IO[Auth] =
|
||||||
for {
|
for {
|
||||||
store <- sessionsRef
|
store <- sessionsRef
|
||||||
roomPasswords <- roomPasswordsRef
|
rooms <- roomsRef
|
||||||
} yield new Auth {
|
} yield new Auth {
|
||||||
|
|
||||||
|
val authcookie = "authcookie"
|
||||||
|
|
||||||
override def authUser = Kleisli { (req: Request[IO]) =>
|
override def authUser = Kleisli { (req: Request[IO]) =>
|
||||||
{
|
{
|
||||||
???
|
??? // oh, this one for when cookie present
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
import cats.syntax.either._
|
||||||
|
import cats.syntax.option._
|
||||||
override def accessRoom(
|
override def accessRoom(
|
||||||
roomName: String,
|
roomName: String,
|
||||||
roomPassword: String,
|
roomPassword: String,
|
||||||
nickName: String
|
nickName: String
|
||||||
): IO[Either[Unit, Long]] = ???
|
): IO[Either[Unit, ResponseCookie]] = {
|
||||||
|
|
||||||
|
???
|
||||||
|
}
|
||||||
|
|
||||||
override def leaveRoom(sessionId: Long): IO[Unit] = ???
|
override def leaveRoom(sessionId: Long): IO[Unit] = ???
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,17 +12,21 @@ object BackendApp extends IOApp {
|
||||||
val host = host"0.0.0.0"
|
val host = host"0.0.0.0"
|
||||||
val port = port"8080"
|
val port = port"8080"
|
||||||
|
|
||||||
val server = for {
|
val wiring = for {
|
||||||
srv <- EmberServerBuilder
|
roomService <- Resource.eval(RoomService.make[IO])
|
||||||
|
httpService = MyHttpService.create(Auth.pureBadStub)
|
||||||
|
server <- EmberServerBuilder
|
||||||
.default[IO]
|
.default[IO]
|
||||||
.withHost(host)
|
.withHost(host)
|
||||||
.withPort(port)
|
.withPort(port)
|
||||||
.withHttpWebSocketApp(MyHttpService.create(Auth.pureBadStub)(_))
|
.withHttpWebSocketApp(httpService(_))
|
||||||
.build
|
.build
|
||||||
} yield srv
|
} yield server
|
||||||
|
|
||||||
server.use(server =>
|
wiring.use(server =>
|
||||||
IO.delay(println(s"Server Has Started at ${server.address}")) >> IO.never.as(ExitCode.Success))
|
IO.delay(println(s"Server Has Started at ${server.address}")) >> IO.never
|
||||||
|
.as(ExitCode.Success)
|
||||||
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,12 +62,9 @@ object MyHttpService {
|
||||||
resp <- authResult match {
|
resp <- authResult match {
|
||||||
case Left(error) =>
|
case Left(error) =>
|
||||||
Forbidden(error)
|
Forbidden(error)
|
||||||
case Right(sessionId) => {
|
case Right(authCookie) => {
|
||||||
Ok("Logged in!").map(
|
IO(println(s"> logging in ${data.nickname} to ${data.roomName}")) >>
|
||||||
_.addCookie(
|
Ok().map(_.addCookie(authCookie))
|
||||||
ResponseCookie("authcookie", sessionId.toString())
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} yield resp
|
} yield resp
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
package industries.sunshine.planningpoker
|
||||||
|
|
||||||
|
import industries.sunshine.planningpoker.common.Models.*
|
||||||
|
import cats.effect.{Ref, Sync}
|
||||||
|
import cats.syntax.all._
|
||||||
|
|
||||||
|
enum RoomError {
|
||||||
|
case RoomAlreadyExists(name: String)
|
||||||
|
}
|
||||||
|
|
||||||
|
trait RoomService[F[_]] {
|
||||||
|
def createRoom(newRoom: Room): F[Either[RoomError, Room]]
|
||||||
|
def updateRoom(room: Room): F[Unit]
|
||||||
|
def deleteRoom(roomID: RoomID): F[Unit]
|
||||||
|
def getRoom(roomID: RoomID): F[Option[Room]]
|
||||||
|
}
|
||||||
|
|
||||||
|
class InMemoryRoomService[F[_]: Sync](stateRef: Ref[F, Map[RoomID, Room]])
|
||||||
|
extends RoomService[F] {
|
||||||
|
override def createRoom(newRoom: Room): F[Either[RoomError, Room]] = {
|
||||||
|
stateRef.modify { rooms =>
|
||||||
|
rooms.get(newRoom.id) match {
|
||||||
|
case Some(_) =>
|
||||||
|
rooms -> RoomError.RoomAlreadyExists(newRoom.id.name).asLeft[Room]
|
||||||
|
case None =>
|
||||||
|
(rooms.updated(newRoom.id, newRoom)) -> newRoom.asRight[RoomError]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
def updateRoom(room: Room): F[Unit] = stateRef.update { state =>
|
||||||
|
state.get(room.id).fold(state)(oldRoom => state.updated(room.id, room))
|
||||||
|
}
|
||||||
|
|
||||||
|
def deleteRoom(roomID: RoomID): F[Unit] = stateRef.update(_.removed(roomID))
|
||||||
|
|
||||||
|
def getRoom(roomID: RoomID): F[Option[Room]] = stateRef.get.map(_.get(roomID))
|
||||||
|
|
||||||
|
}
|
||||||
|
object RoomService {
|
||||||
|
def make[F[_]: Sync]: F[RoomService[F]] = {
|
||||||
|
Ref.of[F, Map[RoomID, Room]](Map.empty).map(new InMemoryRoomService[F](_))
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,10 +6,14 @@ import io.circe._
|
||||||
object Models {
|
object Models {
|
||||||
|
|
||||||
/** view of the single planning poker round
|
/** view of the single planning poker round
|
||||||
* @param players - people who are currently playing
|
* @param players
|
||||||
* @param allowedCards- the cards values that can be used by players
|
* \- people who are currently playing
|
||||||
* @param round - state of the selected cards of the players
|
* @param allowedCards-
|
||||||
* @param canCloseRound - whether current player has access to button to finish the round
|
* the cards values that can be used by players
|
||||||
|
* @param round
|
||||||
|
* \- state of the selected cards of the players
|
||||||
|
* @param canCloseRound
|
||||||
|
* \- whether current player has access to button to finish the round
|
||||||
*/
|
*/
|
||||||
final case class RoomStateView(
|
final case class RoomStateView(
|
||||||
players: List[Player],
|
players: List[Player],
|
||||||
|
@ -23,30 +27,72 @@ object Models {
|
||||||
|
|
||||||
object RoomStateView {
|
object RoomStateView {
|
||||||
val empty = RoomStateView(
|
val empty = RoomStateView(
|
||||||
List.empty, PlayerID(0), List.empty, RoundState.Voting(None, List.empty), false
|
List.empty,
|
||||||
|
PlayerID(0),
|
||||||
|
List.empty,
|
||||||
|
RoundState.Voting(None, List.empty),
|
||||||
|
false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
enum RoundState derives Codec.AsObject:
|
enum RoundState derives Codec.AsObject:
|
||||||
|
|
||||||
/** view state for round before votes are open player can know their vote and
|
/** view state for round before votes are open player can know their vote
|
||||||
* who of the other players have voted
|
* and who of the other players have voted
|
||||||
*/
|
*/
|
||||||
case Voting(
|
case Voting(
|
||||||
myCard: Option[String],
|
myCard: Option[String],
|
||||||
alreadyVoted: List[Player]
|
alreadyVoted: List[Player]
|
||||||
)
|
)
|
||||||
|
|
||||||
/** view state for round after opening the votes
|
/** view state for round after opening the votes
|
||||||
*/
|
*/
|
||||||
case Viewing(
|
case Viewing(
|
||||||
votes: List[(PlayerID, String)]
|
votes: List[(PlayerID, String)]
|
||||||
)
|
)
|
||||||
final case class PlayerID(id: Long) derives Codec.AsObject
|
final case class PlayerID(id: Long) derives Codec.AsObject
|
||||||
|
|
||||||
final case class Player(name: String, id: PlayerID) derives Codec.AsObject
|
final case class Player(name: String, id: PlayerID) derives Codec.AsObject
|
||||||
|
|
||||||
final case class RoomID(id: Long) derives Codec.AsObject
|
final case class RoomID(name: String) derives Codec.AsObject
|
||||||
|
|
||||||
|
final case class Room(
|
||||||
|
id: RoomID,
|
||||||
|
players: List[Player],
|
||||||
|
owner: PlayerID,
|
||||||
|
password: String,
|
||||||
|
allowedCards: List[String],
|
||||||
|
round: RoundState
|
||||||
|
) {
|
||||||
|
def toViewFor(playerId: PlayerID): RoomStateView = {
|
||||||
|
players
|
||||||
|
.find(_.id == playerId)
|
||||||
|
.fold(ifEmpty = RoomStateView.empty)((me: Player) =>
|
||||||
|
RoomStateView(
|
||||||
|
players,
|
||||||
|
me.id,
|
||||||
|
allowedCards,
|
||||||
|
round,
|
||||||
|
playerId == owner
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
object Room {
|
||||||
|
val testRoom = Room(
|
||||||
|
id = RoomID("testroom"),
|
||||||
|
players = List(
|
||||||
|
Player("me", PlayerID(1L)),
|
||||||
|
Player("horsey", PlayerID(444L)),
|
||||||
|
Player("froggy", PlayerID(555L)),
|
||||||
|
Player("owley", PlayerID(777L))
|
||||||
|
),
|
||||||
|
owner = PlayerID(1L),
|
||||||
|
password = "password",
|
||||||
|
allowedCards = List("S", "M", "L"),
|
||||||
|
// TODO - this needs to be a different hting
|
||||||
|
round = RoundState.Voting(None, List.empty)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue