/src/Rooms.hs

https://gitlab.com/gilmi/massage · Haskell · 192 lines · 122 code · 38 blank · 32 comment · 11 complexity · 42e0d332b08763bce386c34bd8801d2e MD5 · raw file

  1. {-# LANGUAGE TemplateHaskell, TypeOperators, OverloadedStrings #-}
  2. {- | Rooms
  3. * Rooms are places where users can chat.
  4. * Rooms have a name and a title, they also store the conversation log and who is in the room and what their permissions are.
  5. * A user permission for a room can either be Normal or Admin. A Normal user can send and receive messages written in the room,
  6. An Admin can invite or accept new users to the room, change their permissions or change the room title.
  7. There are a few default rooms to convey messages to the user, such as "Errors" room.
  8. Type from Types.hs:
  9. data Room = Room
  10. { _rName :: T.Text
  11. , _rTitle :: T.Text
  12. , _rList :: [(User, Permission)]
  13. , _rLog :: [LogMsg]
  14. }
  15. -}
  16. module Rooms where
  17. import Data.Monoid ((<>))
  18. import Data.List (delete, deleteBy)
  19. import Data.Function (on)
  20. import qualified Data.Text.IO as T
  21. import qualified Data.Text as T
  22. import qualified Control.Monad.STM as STM
  23. import qualified Control.Concurrent.STM as STM
  24. import Types
  25. import Utils
  26. import qualified Handler as H
  27. import Data.Label as Lens
  28. data MsgIn
  29. = SendMessage Message
  30. | AddUser User Permission Name
  31. | RemUser User Name
  32. | NewRoom User Name
  33. data MsgOut
  34. = SendMessage' [User] Message
  35. deriving Show
  36. data Err
  37. = RoomNotFound Name
  38. | RoomExists Room
  39. | UserExists User Room
  40. | UserNotFound User Room
  41. printErr :: Err -> T.Text
  42. printErr = \case
  43. RoomNotFound r -> "Room '" <> r <> "' not found."
  44. RoomExists r -> "Room '" <> _rName r <> "' already exists."
  45. UserExists u r -> "User '" <> _uName u <> "' already exists in room " <> _rName r <> "."
  46. UserNotFound u r -> "User '" <> _uName u <> "' note found in room " <> _rName r <> "."
  47. type Rooms = [Room]
  48. newRoom :: User -> Name -> Rooms -> Either Err Rooms
  49. newRoom user room rooms =
  50. case filter ((== room) . _rName) rooms of
  51. [r] ->
  52. throwErr (RoomExists r)
  53. _ ->
  54. pure (Room room mempty [(user,Admin)] mempty : rooms)
  55. addUser :: User -> Permission -> Name -> Rooms -> Either Err Rooms
  56. addUser user perm room rooms =
  57. case filter ((== room) . _rName) rooms of
  58. [r] ->
  59. case lookup user (_rList r) of
  60. Just _ -> throwErr (UserExists user r)
  61. Nothing -> pure $ (Lens.modify rList ((user, perm):) r : delete r rooms)
  62. _ ->
  63. throwErr (RoomNotFound room)
  64. remUser :: User -> Name -> Rooms -> Either Err Rooms
  65. remUser user room rooms =
  66. case filter ((== room) . _rName) rooms of
  67. [r] ->
  68. case lookup user (_rList r) of
  69. Nothing -> throwErr (UserNotFound user r)
  70. Just _ -> pure $ (Lens.modify rList (deleteBy (on (==) (_uName . fst)) (user, undefined)) r : delete r rooms)
  71. _ ->
  72. throwErr (RoomNotFound room)
  73. sendMessage :: Message -> Rooms -> Either Err ([User], Rooms)
  74. sendMessage m@Message{..} rooms =
  75. case filter ((== _mRoom) . _rName) rooms of
  76. [r] ->
  77. pure (map fst $ _rList r, Lens.modify rLog (msgToLogMsg m:) r : delete r rooms)
  78. _ ->
  79. throwErr (RoomNotFound _mRoom)
  80. type RoomsHandler = H.Handler MsgIn
  81. new :: STM.TVar Rooms -> STM.TQueue MsgOut -> IO RoomsHandler
  82. new statevar = H.newHandler statevar inHandler outHandler
  83. {- | inHandler
  84. resposible on getting messages from inside the application and handle them
  85. -}
  86. inHandler :: STM.TVar Rooms -> STM.TQueue MsgIn -> IO [MsgOut]
  87. inHandler rooms input = do
  88. action <- STM.atomically $ do
  89. action <- STM.readTQueue input
  90. case action of
  91. SendMessage msg ->
  92. sendMessageSTM msg rooms
  93. AddUser user perm room ->
  94. addUserSTM user perm room rooms
  95. RemUser user room ->
  96. remUserSTM user room rooms
  97. NewRoom user room ->
  98. newRoomSTM user room rooms
  99. action
  100. sendMessageSTM :: Message -> STM.TVar Rooms -> STM.STM (IO [MsgOut])
  101. sendMessageSTM msg rs = do
  102. rooms <- STM.readTVar rs
  103. case sendMessage msg rooms of
  104. Right (users, rooms') -> do
  105. STM.writeTVar rs rooms'
  106. pure $ do
  107. T.putStrLn ("Message Sent.\n " <> T.pack (show msg))
  108. pure [SendMessage' users msg]
  109. Left err -> do
  110. pure $ T.putStrLn ("Error when sending message: " <> printErr err) >> pure []
  111. addUserSTM :: User -> Permission -> Name -> STM.TVar Rooms -> STM.STM (IO [MsgOut])
  112. addUserSTM user perm room rs = do
  113. rooms <- STM.readTVar rs
  114. case addUser user perm room rooms of
  115. Right rooms' -> do
  116. STM.writeTVar rs rooms'
  117. pure $ T.putStrLn ("User '" <> _uName user <> "' added to " <> room <> ".\n ") >> pure []
  118. Left err -> do
  119. pure $ T.putStrLn ("Error when adding new user: " <> printErr err) >> pure []
  120. remUserSTM :: User -> Name -> STM.TVar Rooms -> STM.STM (IO [MsgOut])
  121. remUserSTM user room rs = do
  122. rooms <- STM.readTVar rs
  123. case remUser user room rooms of
  124. Right rooms' -> do
  125. STM.writeTVar rs rooms'
  126. pure $ T.putStrLn ("User '" <> _uName user <> "' removed from " <> room <> ".\n ") >> pure []
  127. Left err -> do
  128. pure $ T.putStrLn ("Error when removing user: " <> printErr err) >> pure []
  129. newRoomSTM :: User -> Name -> STM.TVar Rooms -> STM.STM (IO [MsgOut])
  130. newRoomSTM user room rs = do
  131. rooms <- STM.readTVar rs
  132. case newRoom user room rooms of
  133. Right rooms' -> do
  134. STM.writeTVar rs rooms'
  135. pure $ T.putStrLn ("User '" <> _uName user <> "' created room " <> room <> ".\n ") >> pure []
  136. Left err -> do
  137. pure $ T.putStrLn ("Error when removing user: " <> printErr err) >> pure []
  138. {- | outHandler
  139. This part gets the messages that needs to be sent and sends them.
  140. -- MsgOut should be changed to `Either Err MsgOut` to report failures as well
  141. -}
  142. outHandler :: STM.TQueue MsgOut -> STM.TQueue MsgOut -> IO ()
  143. outHandler input output = STM.atomically $ do
  144. STM.writeTQueue output =<< STM.readTQueue input