dummy Auth and authed routes

This commit is contained in:
efim 2023-04-23 13:40:37 +04:00
parent c501d14094
commit c6bfdacd1d
4 changed files with 229 additions and 97 deletions

View File

@ -0,0 +1,74 @@
package industries.sunshine.planningpoker
import cats.effect._
import cats.data.Kleisli
import cats.data.OptionT
import org.http4s.Request
import industries.sunshine.planningpoker.common.Models.PlayerID
import java.util.UUID
import industries.sunshine.planningpoker.common.Models.RoomID
trait Auth {
/** for middleware that converts sessionId into PlayerId
*/
def authUser: Kleisli[[A] =>> cats.data.OptionT[cats.effect.IO, A], Request[
cats.effect.IO
], (PlayerID, RoomID)]
/** Get sessionId for accessing a room
* @param roomPassword
* \- requirement to get access to the room
*/
def accessRoom(
roomName: String,
roomPassword: String,
nickName: String
): IO[Either[Unit, Long]]
def leaveRoom(
sessionId: Long
): IO[Unit]
}
object Auth {
def pureBadStub = new Auth {
override def authUser =
Kleisli((req: Request[IO]) =>
OptionT.liftF(IO(println(s"authUser: $req")) >> IO(PlayerID(14) -> RoomID(101)))
)
override def accessRoom(
roomName: String,
roomPassword: String,
nickName: String
) =
IO(println(s"> access room for $roomName $roomPassword $nickName, to return stub 111")) >> IO.pure(Right(111L))
override def leaveRoom(sessionId: Long): IO[Unit] =
IO(s"got request to leave for $sessionId")
}
type SessionsMap = Map[Long, (RoomID, PlayerID)]
val sessionsRef = Ref.of[IO, SessionsMap](Map.empty)
val roomPasswordsRef = Ref.of[IO, Map[Long, String]](Map.empty)
def apply(): IO[Auth] =
for {
store <- sessionsRef
roomPasswords <- roomPasswordsRef
} yield new Auth {
override def authUser = Kleisli { (req: Request[IO]) =>
{
???
}
}
override def accessRoom(
roomName: String,
roomPassword: String,
nickName: String
): IO[Either[Unit, Long]] = ???
override def leaveRoom(sessionId: Long): IO[Unit] = ???
}
}

View File

@ -2,53 +2,27 @@ package industries.sunshine.planningpoker
import cats.effect._
import cats.syntax.all._
import org.http4s._, org.http4s.dsl.io._, org.http4s.implicits._
import com.comcast.ip4s._
import org.http4s.HttpRoutes
import org.http4s.dsl.io._
import org.http4s.implicits._
import org.http4s.ember.server._
import org.http4s.websocket.WebSocketFrame
import io.circe.generic.auto._
import org.http4s.circe.CirceEntityDecoder._
import scala.concurrent.duration._
import org.http4s.server.websocket.WebSocketBuilder
import fs2._
object BackendApp extends IOApp.Simple {
def service(wsb: WebSocketBuilder[IO]) = HttpRoutes
.of[IO] {
case req @ POST -> Root / login => {
for {
data <- req.as[Requests.LogIn]
resp <- Ok(s"got some: ${data}")
} yield resp
}
case GET -> Root / subscribe => {
val send: Stream[IO, WebSocketFrame] =
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))
}
wsb.build(send, receive)
}
case _ => Ok("hello")
}
.orNotFound
override def run: IO[Unit] = {
val a = 1
IO.println(s"Hello, World! $a") >>
EmberServerBuilder
val host = host"0.0.0.0"
val port = port"8080"
val server = for {
srv <- EmberServerBuilder
.default[IO]
.withHost(ipv4"0.0.0.0")
.withPort(port"8080")
.withHttpWebSocketApp(service)
.withHost(host)
.withPort(port)
.withHttpWebSocketApp(MyHttpService.create(Auth.pureBadStub)(_))
.build
.use(_ => IO.never)
.as(ExitCode.Success)
} yield srv
server.use(server =>
IO.delay(println(s"Server Has Started at ${server.address}")) >> IO.never.as(ExitCode.Success))
}
}

View File

@ -0,0 +1,79 @@
package industries.sunshine.planningpoker
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 org.http4s.circe.CirceEntityDecoder._
import scala.concurrent.duration._
import org.http4s.server.websocket.WebSocketBuilder
import fs2._
import industries.sunshine.planningpoker.common.Models.RoomID
import industries.sunshine.planningpoker.common.Models.PlayerID
import org.http4s.server.AuthMiddleware.apply
import org.http4s.server.AuthMiddleware
object MyHttpService {
def create(auth: Auth)(
wsb: WebSocketBuilder[cats.effect.IO]
): HttpApp[cats.effect.IO] = {
val authedRoomRoutes: AuthedRoutes[(PlayerID, RoomID), IO] =
AuthedRoutes.of {
case GET -> Root / subscribe as (playerId, roomId) => {
val send: Stream[IO, WebSocketFrame] =
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))
}
wsb.build(send, receive)
}
case GET -> Root / "vote" / vote as (playerId, roomId) => {
// TODO forward these to the service implementation
Ok(s">> got $vote from $playerId in $roomId")
}
case GET -> Root / "end-voting" as (playerId, roomId) => {
Ok(s">> got request to end voting from $playerId in $roomId")
}
case GET -> Root / "new-poll" as (playerId, roomId) => {
Ok(s">> got request to start new voting from $playerId in $roomId")
}
}
val authMiddleware = AuthMiddleware(auth.authUser)
val aa = authMiddleware(authedRoomRoutes)
val authenticationRoute = HttpRoutes
.of[IO] {
case req @ POST -> Root / login => {
for {
data <- req.as[Requests.LogIn]
authResult <- auth.accessRoom(
data.roomName,
data.password,
data.nickname
)
resp <- authResult match {
case Left(error) =>
Forbidden(error)
case Right(sessionId) => {
Ok("Logged in!").map(
_.addCookie(
ResponseCookie("authcookie", sessionId.toString())
)
)
}
}
} yield resp
}
case _ => Ok("hello")
}
(authenticationRoute <+> authMiddleware(authedRoomRoutes)).orNotFound
}
}

View File

@ -3,70 +3,75 @@ package industries.sunshine.planningpoker.common
import java.util.UUID
object Models {
/** view of the single planning poker round
* @param players
* \- people who are currently playing
* @param allowedCards
* \- 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(
players: List[Player],
me: PlayerID,
allowedCards: List[String],
round: RoundState,
canCloseRound: Boolean = false
) {
def playersCount: Int = players.size
}
object RoomStateView {
val me = Player("wormy", PlayerID())
val testRoom = {
val pony = Player("pony", PlayerID())
RoomStateView(
players = List(me, Player("birdy", PlayerID()), 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())
val birdy = Player("birdy", PlayerID())
val horsey = Player("horsey", PlayerID())
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
)
/** view of the single planning poker round
* @param players
* \- people who are currently playing
* @param allowedCards
* \- 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(
players: List[Player],
me: PlayerID,
allowedCards: List[String],
round: RoundState,
canCloseRound: Boolean = false
) {
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
)
}
trait RoundState
}
/** 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(
myCard: Option[String],
alreadyVoted: List[Player]
) extends RoundState
trait RoundState
/** view state for round after opening the votes
*/
final case class ViewingRound(
votes: Map[PlayerID, String]
) extends RoundState
/** 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(
myCard: Option[String],
alreadyVoted: List[Player]
) extends RoundState
final class PlayerID
final case class Player(name: String, id: PlayerID)
/** view state for round after opening the votes
*/
final case class ViewingRound(
votes: Map[PlayerID, String]
) extends RoundState
final case class PlayerID(id: Long)
final case class Player(name: String, id: PlayerID)
final case class RoomID(id: Long)
}