{-|
Module : Resources.Join
Description :
Copyright : (c) Alexander Vieth, 2015
Licence : BSD3
Maintainer : aovieth@gmail.com
Stability : experimental
Portability : non-portable (GHC only)
-}
{-# LANGUAGE AutoDeriveTypeable #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE StandaloneDeriving #-}
module Resources.Join (
resource
) where
import GHC.Generics
import Control.Monad.State.Class as SC
import Control.Monad.Trans.Class
import Control.Monad.Trans.Reader
import Control.Monad.Trans.Except
import Data.Typeable
import qualified Data.Map as M
import Data.Functor.Constant as FC
import Data.Aeson
import Data.JSON.Schema
import Rest
import Rest.Resource as R
import Types.GameId
import Types.UserData hiding (password)
import Types.Credentials hiding (password)
import qualified Types.Credentials as Credentials
import Types.GameState
import Types.Server
-- | Use this resource to join a game.
--
-- PUT : ask to join the game, passing the game password and your
-- credentials in the request body.
--
resource :: Resource (ReaderT GameId Server) (ReaderT GameId Server) () Void Void
resource = mkResourceId
{ R.name = "join"
, R.schema = singleton () $ unnamedSingle (const ())
, R.update = Just update
}
where
update :: Handler (ReaderT GameId Server)
update = secureHandler $ mkInputHandler (jsonO . jsonE . jsonI) $ doUpdate
doUpdate :: JoinGameInput -> ExceptT (Reason JoinGameError) (ReaderT GameId Server) JoinGameOutput
doUpdate input = do
gameId <- lift ask
state <- SC.get
case M.lookup gameId (games state) of
Nothing -> throwE NotFound
Just (pwd', gameState) -> case pwd' == pwd of
False -> throwE NotAllowed
True -> case registerUser creds gameState of
Left e -> throwE (domainReason e)
Right nextGameState -> do
SC.put (state { games = M.alter (const (Just (pwd', nextGameState))) gameId (games state) })
return Joined
where
creds = credentials input
pwd = password input
registerUser :: Credentials -> GameState -> Either JoinGameError GameState
registerUser credentials gameState = case gameState of
GameNotStarted map duration duration' -> case M.lookup uname map of
Nothing ->
if M.size map == 7
then Left GameFull
else Right (GameNotStarted (M.insert uname userData map) duration duration')
where
userData = UserData pwd (FC.Constant ())
Just _ -> Left UsernameTaken
GameStarted _ _ _ _ _ _ -> Left GameAlreadyStarted
where
uname = Credentials.username credentials
pwd = Credentials.password credentials
data JoinGameInput = JoinGameInput {
password :: Password
-- ^ Password for the game.
, credentials :: Credentials
-- ^ Credentials of the user who wishes to join.
}
deriving instance Generic JoinGameInput
deriving instance Typeable JoinGameInput
instance FromJSON JoinGameInput
instance ToJSON JoinGameInput
instance JSONSchema JoinGameInput where
schema = gSchema
data JoinGameOutput = Joined
deriving instance Generic JoinGameOutput
deriving instance Typeable JoinGameOutput
instance FromJSON JoinGameOutput
instance ToJSON JoinGameOutput
instance JSONSchema JoinGameOutput where
schema = gSchema
data JoinGameError
= UsernameTaken
| GameAlreadyStarted
| GameFull
deriving (Show, Generic, Typeable)
instance JSONSchema JoinGameError where
schema = gSchema
instance ToJSON JoinGameError
instance ToResponseCode JoinGameError where
toResponseCode UsernameTaken = 403
toResponseCode GameAlreadyStarted = 403
toResponseCode GameFull = 403