Compare commits
No commits in common. "867b2f0f20204e8e3161a561d03ec042110f5c4c" and "8098f245524eda8052f1c9e0903680bf309f027c" have entirely different histories.
867b2f0f20
...
8098f24552
|
@ -27,22 +27,22 @@ trait Auth[F[_]] {
|
||||||
*/
|
*/
|
||||||
def joinRoom(roomId: RoomID, playerId: PlayerID): F[ResponseCookie]
|
def joinRoom(roomId: RoomID, playerId: PlayerID): F[ResponseCookie]
|
||||||
|
|
||||||
def deleteSession(sessionId: Long): F[ResponseCookie]
|
def deleteSession(sessionId: Long): F[Unit]
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
object Auth {
|
object Auth {
|
||||||
type SessionsMap = Map[Long, (RoomID, PlayerID)]
|
type SessionsMap = Map[Long, (RoomID, PlayerID)]
|
||||||
|
|
||||||
// TODO make "remove session usable from authed route"
|
|
||||||
val authcookieName = "authcookie"
|
|
||||||
|
|
||||||
class SimpleAuth[F[_]: Sync](sessions: Ref[F, SessionsMap]) extends Auth[F] {
|
class SimpleAuth[F[_]: Sync](sessions: Ref[F, SessionsMap]) extends Auth[F] {
|
||||||
|
|
||||||
|
val authcookieName = "authcookie"
|
||||||
|
|
||||||
override def joinRoom(roomId: RoomID, playerId: PlayerID): F[ResponseCookie] = {
|
override def joinRoom(roomId: RoomID, playerId: PlayerID): F[ResponseCookie] = {
|
||||||
// TODO check for existing session for same room
|
// TODO check for existing session for same room
|
||||||
// and do i want to logout if existing session for another room? ugh
|
// and do i want to logout if existing session for another room? ugh
|
||||||
val newSessionId = Random.nextLong()
|
// newSessionId = Random.nextLong() // TODO return after i stop mocking RoomService
|
||||||
|
val newSessionId = TestModels.testSessionId
|
||||||
sessions
|
sessions
|
||||||
.update(_.updated(newSessionId, (roomId, playerId)))
|
.update(_.updated(newSessionId, (roomId, playerId)))
|
||||||
.as(
|
.as(
|
||||||
|
@ -67,16 +67,8 @@ object Auth {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override def deleteSession(sessionId: Long): F[ResponseCookie] = {
|
override def deleteSession(sessionId: Long): F[Unit] = {
|
||||||
sessions
|
sessions.update(_.removed(sessionId))
|
||||||
.update(_.removed(sessionId))
|
|
||||||
.as(
|
|
||||||
ResponseCookie(
|
|
||||||
name = authcookieName,
|
|
||||||
content = "",
|
|
||||||
secure = true
|
|
||||||
).clearCookie
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,12 +22,9 @@ object MyHttpService {
|
||||||
val authedRoomRoutes: AuthedRoutes[(PlayerID, RoomID), IO] =
|
val authedRoomRoutes: AuthedRoutes[(PlayerID, RoomID), IO] =
|
||||||
AuthedRoutes.of {
|
AuthedRoutes.of {
|
||||||
case GET -> Root / "subscribe" as (playerId, roomId) => {
|
case GET -> Root / "subscribe" as (playerId, roomId) => {
|
||||||
val initial = Stream.evals(roomService.getRoom(roomId))
|
|
||||||
val subscription = roomService.subscribe(roomId)
|
|
||||||
|
|
||||||
val send: Stream[IO, WebSocketFrame] =
|
val send: Stream[IO, WebSocketFrame] =
|
||||||
(initial ++ subscription)
|
roomService
|
||||||
.evalTap(state => IO(println(s">> sending room state $state to $playerId")))
|
.subscribe(roomId)
|
||||||
.map(state => WebSocketFrame.Text(state.getViewFor(playerId).asJson.noSpaces))
|
.map(state => WebSocketFrame.Text(state.getViewFor(playerId).asJson.noSpaces))
|
||||||
|
|
||||||
val receive: Pipe[IO, WebSocketFrame, Unit] = _.evalMap {
|
val receive: Pipe[IO, WebSocketFrame, Unit] = _.evalMap {
|
||||||
|
@ -38,23 +35,17 @@ object MyHttpService {
|
||||||
wsb.build(send, receive)
|
wsb.build(send, receive)
|
||||||
}
|
}
|
||||||
case GET -> Root / "vote" / vote as (playerId, roomId) => {
|
case GET -> Root / "vote" / vote as (playerId, roomId) => {
|
||||||
IO(println(s">> got $vote from $playerId in $roomId")) >>
|
// TODO forward these to the service implementation
|
||||||
roomService.acceptVote(roomId, playerId, vote) >> Ok()
|
IO(println(s">> got $vote from $playerId in $roomId")) >> Ok()
|
||||||
}
|
}
|
||||||
case GET -> Root / "end-voting" as (playerId, roomId) => {
|
case GET -> Root / "end-voting" as (playerId, roomId) => {
|
||||||
IO(println(s">> got request to end voting from $playerId in $roomId")) >>
|
IO(println(s">> got request to end voting from $playerId in $roomId")) >> Ok()
|
||||||
roomService.endVoting(roomId, playerId) >> Ok()
|
|
||||||
}
|
}
|
||||||
case GET -> Root / "new-poll" as (playerId, roomId) => {
|
case GET -> Root / "new-poll" as (playerId, roomId) => {
|
||||||
IO(println(s">> got request to start new voting from $playerId in $roomId")) >>
|
IO(println(s">> got request to start new voting from $playerId in $roomId")) >> Ok()
|
||||||
roomService.startNewPoll(roomId, playerId) >> Ok()
|
|
||||||
}
|
}
|
||||||
case GET -> Root / "logout" as (playerId, roomId) => {
|
case GET -> Root / "logout" as (playerId, roomId) => {
|
||||||
for {
|
IO(println(s">> got request to logout from $playerId in $roomId")) >> Ok()
|
||||||
_ <- 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))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,32 +63,13 @@ object MyHttpService {
|
||||||
playerId <- EitherT(roomService.joinRoom(roomId, nickName, nickPassword, roomPassword))
|
playerId <- EitherT(roomService.joinRoom(roomId, nickName, nickPassword, roomPassword))
|
||||||
authCookie <- EitherT.liftF(auth.joinRoom(roomId, playerId))
|
authCookie <- EitherT.liftF(auth.joinRoom(roomId, playerId))
|
||||||
_ <- EitherT.liftF(IO(println(s"> logging in $nickName to $roomName")))
|
_ <- EitherT.liftF(IO(println(s"> logging in $nickName to $roomName")))
|
||||||
resp = Response(Status.Ok).addCookie(authCookie)
|
resp <- EitherT.liftF(Ok().flatTap(resp => IO(resp.addCookie(authCookie))))
|
||||||
} yield resp
|
} yield resp
|
||||||
|
|
||||||
val response = responseOrError.leftSemiflatMap(error => Forbidden(error.toString())).merge
|
val response = responseOrError.leftSemiflatMap(error => Forbidden(error.toString())).merge
|
||||||
|
|
||||||
response
|
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
|
(authenticationRoute <+> authMiddleware(authedRoomRoutes)).orNotFound
|
||||||
|
|
|
@ -15,23 +15,14 @@ enum RoomError {
|
||||||
}
|
}
|
||||||
|
|
||||||
trait RoomService[F[_]] {
|
trait RoomService[F[_]] {
|
||||||
def createRoom(
|
def createRoom(newRoom: Room): F[Either[RoomError, Room]]
|
||||||
roomName: String,
|
def updateRoom(room: Room): F[Unit]
|
||||||
nickName: String,
|
|
||||||
nickPassword: String,
|
|
||||||
roomPassword: String
|
|
||||||
): F[Either[RoomError, Room]]
|
|
||||||
def updateRoom(roomId: RoomID, roomUpd: Room => Room): F[Unit]
|
|
||||||
def joinRoom(
|
def joinRoom(
|
||||||
id: RoomID,
|
id: RoomID,
|
||||||
nickName: String,
|
nickName: String,
|
||||||
nickPassword: String,
|
nickPassword: String,
|
||||||
roomPassword: String
|
roomPassword: String
|
||||||
): F[Either[RoomError, PlayerID]]
|
): 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 deleteRoom(roomID: RoomID): F[Unit]
|
||||||
def getRoom(roomID: RoomID): F[Option[Room]]
|
def getRoom(roomID: RoomID): F[Option[Room]]
|
||||||
def subscribe(roomID: RoomID): Stream[F, Room]
|
def subscribe(roomID: RoomID): Stream[F, Room]
|
||||||
|
@ -40,91 +31,33 @@ trait RoomService[F[_]] {
|
||||||
class InMemoryRoomService[F[_]: Concurrent](stateRef: Ref[F, Map[RoomID, (Room, Topic[F, Room])]])
|
class InMemoryRoomService[F[_]: Concurrent](stateRef: Ref[F, Map[RoomID, (Room, Topic[F, Room])]])
|
||||||
extends RoomService[F] {
|
extends RoomService[F] {
|
||||||
|
|
||||||
// TODO accept allowed cards and separate request
|
override def createRoom(newRoom: Room): F[Either[RoomError, Room]] = {
|
||||||
override def createRoom(
|
|
||||||
roomName: String,
|
|
||||||
nickName: String,
|
|
||||||
nickPassword: String,
|
|
||||||
roomPassword: String
|
|
||||||
): F[Either[RoomError, Room]] = {
|
|
||||||
for {
|
for {
|
||||||
updatesTopic <- Topic[F, Room]
|
updatesTopic <- Topic[F, Room]
|
||||||
room <- stateRef.modify { rooms =>
|
room <- stateRef.modify { rooms =>
|
||||||
val roomId = RoomID(roomName)
|
rooms.get(newRoom.id) match {
|
||||||
rooms.get(roomId) match {
|
|
||||||
case Some(_) =>
|
case Some(_) =>
|
||||||
rooms -> RoomError.RoomAlreadyExists(roomName).asLeft[Room]
|
rooms -> RoomError.RoomAlreadyExists(newRoom.id.name).asLeft[Room]
|
||||||
case None =>
|
case None =>
|
||||||
val ownerPlayer = Player.create(nickName)
|
(rooms.updated(newRoom.id, (newRoom, updatesTopic))) -> newRoom.asRight[RoomError]
|
||||||
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
|
} yield room
|
||||||
}
|
}
|
||||||
|
override def updateRoom(room: Room): F[Unit] = {
|
||||||
override def updateRoom(roomId: RoomID, roomUpd: Room => Room): F[Unit] = {
|
|
||||||
for {
|
for {
|
||||||
// modify is function to update state and compute auxillary value to return, here - topic
|
// modify is function to update state and compute auxillary value to return, here - topic
|
||||||
publishUpd <- stateRef.modify[F[Unit]] { state =>
|
topic <- stateRef.modify[Topic[F, Room]] { state =>
|
||||||
state.get(roomId) match {
|
state.get(room.id) match {
|
||||||
case Some((oldRoom, topic)) =>
|
case Some((oldRoom, topic)) => state.updated(room.id, (room, topic)) -> topic
|
||||||
val newRoom = roomUpd(oldRoom)
|
|
||||||
state.updated(roomId, (newRoom, topic)) -> topic.publish1(newRoom).void
|
|
||||||
case None =>
|
case None =>
|
||||||
throw new IllegalStateException(s"updateRoom with $roomId on nonexistent room")
|
throw new IllegalStateException(s"updateRoom with ${room.id} 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 ()
|
} 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(_) => room
|
|
||||||
case RoundState.Voting(votes) => room.copy(round = RoundState.Viewing(votes))
|
|
||||||
}
|
|
||||||
)
|
|
||||||
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
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
/** 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] = {
|
override def deleteRoom(roomID: RoomID): F[Unit] = {
|
||||||
for {
|
for {
|
||||||
topic <- stateRef.modify[Topic[F, Room]](state =>
|
topic <- stateRef.modify[Topic[F, Room]](state =>
|
||||||
|
|
|
@ -76,32 +76,6 @@ object JoinRoomComponent {
|
||||||
}
|
}
|
||||||
} --> responseReceived
|
} --> 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(
|
div(
|
||||||
className := "flex flex-col h-full justify-center",
|
className := "flex flex-col h-full justify-center",
|
||||||
|
@ -111,7 +85,6 @@ object JoinRoomComponent {
|
||||||
nameInput(nicknameVar, "Enter your nickname:"),
|
nameInput(nicknameVar, "Enter your nickname:"),
|
||||||
passInput(nicknamePass, "nickname pass:"),
|
passInput(nicknamePass, "nickname pass:"),
|
||||||
submitButton,
|
submitButton,
|
||||||
newRoomButton,
|
|
||||||
div(
|
div(
|
||||||
div(
|
div(
|
||||||
code("received:")
|
code("received:")
|
||||||
|
|
Loading…
Reference in New Issue