Last active
August 29, 2015 14:25
-
-
Save codedmart/d2bda27f9fa9bff66a3e to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| {-# LANGUAGE OverloadedStrings #-} | |
| {-# LANGUAGE FlexibleContexts #-} | |
| module Lib.Chat (eioServer, ServerState (..)) where | |
| import Prelude hiding (mapM_) | |
| import Control.Monad.IO.Class (liftIO) | |
| import Control.Monad.Trans.Reader | |
| import Data.Aeson ((.=)) | |
| import Data.Foldable (mapM_) | |
| import qualified Control.Concurrent.STM as STM | |
| import qualified Data.Aeson as Aeson | |
| import qualified Data.Text as Text | |
| import qualified Network.SocketIO as SocketIO | |
| import Database.RethinkDB (next, fromDatum) | |
| import Database.RethinkDB.Datum (resultToMaybe, Datum) | |
| import Control.Concurrent | |
| import Control.Exception | |
| import Control.Monad | |
| import qualified Model.User as User | |
| import Model.Types.User (SecureUser, ChangeUser) | |
| data AddUser = AddUser Text.Text | |
| instance Aeson.FromJSON AddUser where | |
| parseJSON = Aeson.withText "AddUser" $ pure . AddUser | |
| data NumConnected = NumConnected !Int | |
| instance Aeson.ToJSON NumConnected where | |
| toJSON (NumConnected n) = Aeson.object [ "numUsers" .= n] | |
| data NewMessage = NewMessage Text.Text | |
| instance Aeson.FromJSON NewMessage where | |
| parseJSON = Aeson.withText "NewMessage" $ pure . NewMessage | |
| data Said = Said Text.Text Text.Text | |
| instance Aeson.ToJSON Said where | |
| toJSON (Said username message) = Aeson.object | |
| [ "username" .= username | |
| , "message" .= message | |
| ] | |
| data UserName = UserName Text.Text | |
| instance Aeson.ToJSON UserName where | |
| toJSON (UserName un) = Aeson.object [ "username" .= un ] | |
| data UserJoined = UserJoined Text.Text Int | |
| instance Aeson.ToJSON UserJoined where | |
| toJSON (UserJoined un n) = Aeson.object | |
| [ "username" .= un | |
| , "numUsers" .= n | |
| ] | |
| data Connection = Connection [SecureUser] | |
| instance Aeson.ToJSON Connection where | |
| toJSON (Connection c) = Aeson.object ["users" .= c] | |
| data Changes = Changes ChangeUser | |
| instance Aeson.ToJSON Changes where | |
| toJSON (Changes c) = Aeson.object ["users" .= c] | |
| -------------------------------------------------------------------------------- | |
| data ServerState = ServerState { ssNConnected :: STM.TVar Int } | |
| --server :: ServerState -> StateT SocketIO.RoutingTable Snap.Snap () | |
| eioServer state pool = do | |
| userNameMVar <- liftIO STM.newEmptyTMVarIO | |
| let forUserName m = liftIO (STM.atomically (STM.tryReadTMVar userNameMVar)) >>= mapM_ m | |
| -- get rethinkdb cursor | |
| cursor <- liftIO $ User.listRawChanges pool | |
| -- get socket for emitTo | |
| socket <- ask | |
| liftIO $ putStrLn "connected" | |
| liftIO $ forkIO $ forever $ next cursor >>= \c -> do | |
| case c of | |
| Nothing -> undefined | |
| Just x -> do | |
| let u = resultToMaybe $ fromDatum x :: Maybe ChangeUser | |
| case u of | |
| Nothing -> undefined | |
| Just user -> SocketIO.emitTo socket "changes" (Changes user) | |
| SocketIO.on "new message" $ \(NewMessage message) -> | |
| forUserName $ \userName -> | |
| SocketIO.broadcast "new message" (Said userName message) | |
| SocketIO.on "add user" $ \(AddUser userName) -> do | |
| n <- liftIO $ STM.atomically $ do | |
| n <- (+ 1) <$> STM.readTVar (ssNConnected state) | |
| STM.putTMVar userNameMVar userName | |
| STM.writeTVar (ssNConnected state) n | |
| return n | |
| SocketIO.emit "login" (NumConnected n) | |
| SocketIO.broadcast "user joined" (UserJoined userName n) | |
| SocketIO.appendDisconnectHandler $ do | |
| (n, mUserName) <- liftIO $ STM.atomically $ do | |
| n <- (+ (-1)) <$> STM.readTVar (ssNConnected state) | |
| mUserName <- STM.tryReadTMVar userNameMVar | |
| STM.writeTVar (ssNConnected state) n | |
| return (n, mUserName) | |
| case mUserName of | |
| Nothing -> return () | |
| Just userName -> | |
| SocketIO.broadcast "user left" (UserJoined userName n) | |
| SocketIO.on "typing" $ | |
| forUserName $ \userName -> | |
| SocketIO.broadcast "typing" (UserName userName) | |
| SocketIO.on "stop typing" $ | |
| forUserName $ \userName -> | |
| SocketIO.broadcast "stop typing" (UserName userName) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Api/Main.hs:135:45: | |
| Couldn't match expected type ‘Control.Monad.Trans.State.Strict.StateT | |
| SocketIO.RoutingTable (ReaderT SocketIO.Socket WaiMonad) a0’ | |
| with actual type ‘ReaderT SocketIO.Socket m0 ()’ | |
| In the second argument of ‘($)’, namely ‘eioServer state p’ | |
| In a stmt of a 'do' block: | |
| handler <- SocketIO.initialize waiAPI $ eioServer state p |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| {-# LANGUAGE RankNTypes #-} | |
| {-# LANGUAGE DataKinds #-} | |
| {-# LANGUAGE TypeOperators #-} | |
| {-# LANGUAGE OverloadedStrings #-} | |
| import Servant hiding (Get, Post, Put, Delete, ReqBody) | |
| import qualified Servant as S | |
| import Data.Monoid ((<>)) | |
| import Data.Text (Text) | |
| import System.Console.CmdArgs | |
| import Data.Text.Lazy (pack) | |
| import Network.Wai.Handler.Warp (run) | |
| {-import Control.Monad.IO.Class (liftIO)-} | |
| import Control.Monad.Morph (hoist) | |
| {-import Control.Monad.Trans.Except-} | |
| import Control.Monad.Trans.Either | |
| import Control.Monad.Trans.Reader | |
| import Network.Wai | |
| import Network.Wai.Middleware.Cors | |
| import Network.EngineIO.Wai | |
| import qualified Network.SocketIO as SocketIO | |
| import qualified Control.Concurrent.STM as STM | |
| import Lib.Chat | |
| import Lib.Config | |
| import Lib.RethinkDB | |
| import Model.Types.User (SecureUser) | |
| import Model.Types.UserSignup (UserSignup) | |
| import Model.Types.UserEdit (UserEdit) | |
| import qualified Model.User as User | |
| type Get a = S.Get '[JSON] a | |
| type Post a = S.Post '[JSON] a | |
| type Put a = S.Put '[JSON] a | |
| type Delete a = S.Delete '[JSON] a | |
| type ReqBody a = S.ReqBody '[JSON] a | |
| type UsersAPI = | |
| Get [SecureUser] | |
| :<|> ReqBody UserSignup :> Post SecureUser | |
| :<|> Capture "id" Text :> ReqBody UserEdit :> Put SecureUser | |
| :<|> Capture "id" Text :> Delete () | |
| usersServer :: ServerT UsersAPI AppIO | |
| usersServer = | |
| getUsers | |
| :<|> postUser | |
| :<|> putUser | |
| :<|> deleteUser | |
| where | |
| getUsers :: AppIO [SecureUser] | |
| getUsers = User.list | |
| postUser :: UserSignup -> AppIO SecureUser | |
| postUser = User.create | |
| putUser :: Text -> UserEdit -> AppIO SecureUser | |
| putUser = User.update | |
| deleteUser :: Text -> AppIO () | |
| deleteUser = User.delete | |
| type API = | |
| "info" :> Get ApiInfo | |
| :<|> "users" :> UsersAPI | |
| serverT :: ServerT API AppIO | |
| serverT = getApiInfo | |
| :<|> usersServer | |
| where | |
| getApiInfo :: AppIO ApiInfo | |
| getApiInfo = apiInfo | |
| appIOToEither' :: AppConfig -> AppIO a -> EitherT ServantErr IO a | |
| appIOToEither' t r = runReaderT (hoist (EitherT . runEitherT) r) t | |
| appIOToEither :: AppConfig -> AppIO :~> EitherT ServantErr IO | |
| appIOToEither t = Nat $ appIOToEither' t | |
| server :: AppConfig -> Server API | |
| server t = enter (appIOToEither t) serverT | |
| type RawAPI = | |
| "socket.io" :> Raw | |
| :<|> Raw | |
| rawServer :: WaiMonad () -> Server RawAPI | |
| rawServer h = socketIO | |
| :<|> serveDirectory "resources" | |
| where | |
| socketIO :: Application | |
| socketIO req respond = toWaiApplication h req respond | |
| corsResourcePolicy :: CorsResourcePolicy | |
| corsResourcePolicy = CorsResourcePolicy | |
| { corsOrigins = Just (["chrome-extension://aicmkgpgakddgnaphhhpliifpcfhicfo", "http://getmetal.org"], True) | |
| , corsMethods = ["GET", "HEAD", "OPTIONS", "POST", "PUT", "DELETE"] | |
| , corsRequestHeaders = simpleResponseHeaders | |
| , corsExposedHeaders = Nothing | |
| , corsMaxAge = Nothing | |
| , corsVaryOrigin = False | |
| , corsRequireOrigin = False | |
| , corsIgnoreFailures = False | |
| } | |
| stack :: Application -> Application | |
| stack app = cors' $ app | |
| where | |
| cors' = cors (const $ Just corsResourcePolicy) | |
| api :: Proxy (API :<|> RawAPI) | |
| api = Proxy | |
| app :: AppConfig -> WaiMonad () -> Application | |
| app t h = serve api $ server t :<|> rawServer h | |
| main :: IO () | |
| main = do | |
| -- setup environment | |
| args' <- cmdArgs $ AppOptions "development" | |
| -- Config | |
| cfg <- parseConfig (env args') (pack ".api") | |
| let dbh = dbHost cfg | |
| -- DB | |
| p <- createRethinkPool dbh | |
| let tn = AppConfig p cfg | |
| -- socketio | |
| state <- ServerState <$> STM.newTVarIO 0 | |
| handler <- SocketIO.initialize waiAPI $ eioServer state p | |
| -- RethinkDB setup | |
| setupDB dbh | |
| putStrLn $ "Running on " <> show 3001 | |
| run 3001 $ stack $ app tn handler | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment