97 lines
3.2 KiB
Scala
97 lines
3.2 KiB
Scala
package industries.sunshine.planningpoker
|
|
|
|
import industries.sunshine.planningpoker.common.Models.*
|
|
import cats.effect.{Ref, Sync}
|
|
import cats.syntax.all._
|
|
import cats.data.EitherT
|
|
|
|
enum RoomError {
|
|
case RoomAlreadyExists(name: String)
|
|
case RoomMissing(name: String)
|
|
case RoomPassIncorrect
|
|
case NickPassIncorrect
|
|
}
|
|
|
|
trait RoomService[F[_]] {
|
|
def createRoom(newRoom: Room): F[Either[RoomError, Room]]
|
|
def updateRoom(room: Room): F[Unit]
|
|
def joinRoom(
|
|
id: RoomID,
|
|
nickName: String,
|
|
nickPassword: String,
|
|
roomPassword: String
|
|
): F[Either[RoomError, PlayerID]]
|
|
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]
|
|
}
|
|
}
|
|
}
|
|
override def updateRoom(room: Room): F[Unit] = stateRef.update { state =>
|
|
state.get(room.id).fold(state)(oldRoom => state.updated(room.id, room))
|
|
}
|
|
|
|
override def deleteRoom(roomID: RoomID): F[Unit] = stateRef.update(_.removed(roomID))
|
|
|
|
override def getRoom(roomID: RoomID): F[Option[Room]] = stateRef.get.map(_.get(roomID))
|
|
|
|
override def joinRoom(
|
|
id: RoomID,
|
|
nickName: String,
|
|
nickPassword: String,
|
|
roomPassword: String
|
|
): F[Either[RoomError, PlayerID]] = stateRef.modify { rooms =>
|
|
// need to cover cases:
|
|
// - player already present, then return as is, i guess
|
|
// - nick not known - add new player and new nick-password mapping
|
|
// - nick known - add new player
|
|
|
|
def addPlayer(room: Room): (PlayerID, Room) = {
|
|
room.players.find(_.name == nickName) match {
|
|
case Some(player) => player.id -> room
|
|
case None => // player is not present, but potentially was previously
|
|
val addingPlayer = Player.create(nickPassword)
|
|
val roomWithPlayer = room.copy(players = addingPlayer :: room.players)
|
|
room.playersPasswords.get(nickName) match {
|
|
case Some(_) => addingPlayer.id -> roomWithPlayer
|
|
case None =>
|
|
addingPlayer.id -> roomWithPlayer.copy(playersPasswords =
|
|
roomWithPlayer.playersPasswords.updated(nickName, nickPassword)
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
val joiningWithChecks = for {
|
|
room <- rooms.get(id).toRight(RoomError.RoomMissing(id.name))
|
|
_ <- Either.cond(room.password == roomPassword, (), RoomError.RoomPassIncorrect)
|
|
isNickPassCorrect = room.playersPasswords
|
|
.get(nickName)
|
|
.fold(true)(existingPass => existingPass == nickPassword)
|
|
_ <- Either.cond(
|
|
isNickPassCorrect,
|
|
(),
|
|
RoomError.NickPassIncorrect
|
|
)
|
|
(playerId, updatedRoom) = addPlayer(room)
|
|
} yield playerId
|
|
|
|
rooms -> joiningWithChecks
|
|
}
|
|
|
|
}
|
|
object RoomService {
|
|
def make[F[_]: Sync]: F[RoomService[F]] = {
|
|
Ref.of[F, Map[RoomID, Room]](Map.empty).map(new InMemoryRoomService[F](_))
|
|
}
|
|
}
|