feat: add notify command and cli args
This commit is contained in:
parent
92a97e8f8a
commit
fd18fa1a8b
|
@ -4,10 +4,24 @@ import ClassyPrelude
|
||||||
import GHC.IO.Encoding (setLocaleEncoding)
|
import GHC.IO.Encoding (setLocaleEncoding)
|
||||||
import GHC.IO.Encoding.UTF8 (utf8)
|
import GHC.IO.Encoding.UTF8 (utf8)
|
||||||
import Lib (runBothServers)
|
import Lib (runBothServers)
|
||||||
|
import Options.Applicative
|
||||||
|
import Types.Config (ServerOptions, serverOptionsParser)
|
||||||
|
|
||||||
main :: IO ()
|
main :: IO ()
|
||||||
main = do
|
main = do
|
||||||
setLocaleEncoding utf8
|
setLocaleEncoding utf8
|
||||||
hSetBuffering stdout LineBuffering
|
hSetBuffering stdout LineBuffering
|
||||||
hSetBuffering stderr LineBuffering
|
hSetBuffering stderr LineBuffering
|
||||||
runBothServers
|
|
||||||
|
opts <- execParser serverOptions
|
||||||
|
|
||||||
|
runBothServers opts
|
||||||
|
|
||||||
|
serverOptions :: ParserInfo ServerOptions
|
||||||
|
serverOptions =
|
||||||
|
info
|
||||||
|
(serverOptionsParser <**> helper)
|
||||||
|
( fullDesc
|
||||||
|
<> progDesc "Run the server with specified options"
|
||||||
|
<> header "Haskell Server - A configurable server application"
|
||||||
|
)
|
||||||
|
|
|
@ -33,6 +33,7 @@ library
|
||||||
State.RoomDataState
|
State.RoomDataState
|
||||||
State.RoomsState
|
State.RoomsState
|
||||||
Types.AppTypes
|
Types.AppTypes
|
||||||
|
Types.Config
|
||||||
Types.ConnectionState
|
Types.ConnectionState
|
||||||
Types.Participant
|
Types.Participant
|
||||||
Types.RoomData
|
Types.RoomData
|
||||||
|
@ -62,6 +63,7 @@ library
|
||||||
, http-types
|
, http-types
|
||||||
, lifted-base
|
, lifted-base
|
||||||
, mtl
|
, mtl
|
||||||
|
, optparse-applicative
|
||||||
, process
|
, process
|
||||||
, text
|
, text
|
||||||
, time
|
, time
|
||||||
|
@ -90,6 +92,7 @@ executable jitsi-rooms-exe
|
||||||
, jitsi-rooms
|
, jitsi-rooms
|
||||||
, lifted-base
|
, lifted-base
|
||||||
, mtl
|
, mtl
|
||||||
|
, optparse-applicative
|
||||||
, process
|
, process
|
||||||
, text
|
, text
|
||||||
, time
|
, time
|
||||||
|
|
|
@ -35,6 +35,7 @@ dependencies:
|
||||||
- time
|
- time
|
||||||
- wai-extra
|
- wai-extra
|
||||||
- process
|
- process
|
||||||
|
- optparse-applicative
|
||||||
|
|
||||||
ghc-options:
|
ghc-options:
|
||||||
- -Wall
|
- -Wall
|
||||||
|
|
|
@ -9,11 +9,12 @@ import ClassyPrelude
|
||||||
import State.ConnectedClientsState (initConnectionsState)
|
import State.ConnectedClientsState (initConnectionsState)
|
||||||
import State.RoomsState (initRoomsState)
|
import State.RoomsState (initRoomsState)
|
||||||
import Types.AppTypes
|
import Types.AppTypes
|
||||||
|
import Types.Config (ServerOptions)
|
||||||
import WebServer (runWebServer)
|
import WebServer (runWebServer)
|
||||||
import WebSocket.Server (runWebSocketServer)
|
import WebSocket.Server (runWebSocketServer)
|
||||||
|
|
||||||
runBothServers :: IO ()
|
runBothServers :: ServerOptions -> IO ()
|
||||||
runBothServers = do
|
runBothServers serverOptions = do
|
||||||
connectedClientsState <- initConnectionsState
|
connectedClientsState <- initConnectionsState
|
||||||
roomsState <- initRoomsState
|
roomsState <- initRoomsState
|
||||||
|
|
||||||
|
@ -21,7 +22,8 @@ runBothServers = do
|
||||||
Env
|
Env
|
||||||
{ connectedClientsState = connectedClientsState,
|
{ connectedClientsState = connectedClientsState,
|
||||||
profile = Dev,
|
profile = Dev,
|
||||||
roomsState = roomsState
|
roomsState = roomsState,
|
||||||
|
config = serverOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
_ <- concurrently (unApp runWebSocketServer env) (unApp runWebServer env)
|
_ <- concurrently (unApp runWebSocketServer env) (unApp runWebServer env)
|
||||||
|
|
|
@ -27,6 +27,8 @@ import State.RoomsState
|
||||||
)
|
)
|
||||||
import System.Process
|
import System.Process
|
||||||
import Text.Printf (printf)
|
import Text.Printf (printf)
|
||||||
|
import Types.AppTypes (HasConfig (getConfig))
|
||||||
|
import Types.Config (ServerOptions (..))
|
||||||
import Types.RoomData (RoomData, prettyPrintOpenedRoom)
|
import Types.RoomData (RoomData, prettyPrintOpenedRoom)
|
||||||
import Types.WebEnv
|
import Types.WebEnv
|
||||||
( HasWebEnv (getRequest),
|
( HasWebEnv (getRequest),
|
||||||
|
@ -40,7 +42,8 @@ roomDataHandler ::
|
||||||
MonadError ResponseReceived m,
|
MonadError ResponseReceived m,
|
||||||
MonadRoomDataStateRead m,
|
MonadRoomDataStateRead m,
|
||||||
MonadRoomDataStateModify m,
|
MonadRoomDataStateModify m,
|
||||||
MonadBroadcast m
|
MonadBroadcast m,
|
||||||
|
HasConfig env
|
||||||
) =>
|
) =>
|
||||||
m ResponseReceived
|
m ResponseReceived
|
||||||
roomDataHandler = do
|
roomDataHandler = do
|
||||||
|
@ -115,16 +118,21 @@ success = do
|
||||||
[("Content-Type", "text/plain")]
|
[("Content-Type", "text/plain")]
|
||||||
""
|
""
|
||||||
|
|
||||||
notifyRoomOpend :: (MonadIO m) => RoomData -> m ()
|
notifyRoomOpend :: (MonadIO m, HasConfig a0, MonadReader a0 m) => RoomData -> m ()
|
||||||
notifyRoomOpend room = do
|
notifyRoomOpend room = do
|
||||||
|
config' <- getConfig <$> ask
|
||||||
|
let ServerOptions {notifyExecutable = notifyExecutable'} = config'
|
||||||
let (name, user) = prettyPrintOpenedRoom room
|
let (name, user) = prettyPrintOpenedRoom room
|
||||||
liftIO $ printf "Room %s opened by %s\n" name user
|
liftIO $ printf "Room %s opened by %s\n" name user
|
||||||
exitCode <- liftIO $ system $ printf "/nix/store/23nz5mjk3dj7027byc6g5avx3mfwwnqm-libnotify-0.8.3/bin/notify-send 'Room %s opened by %s'" name user
|
let command = printf "%s open '%s' '%s'" notifyExecutable' name user
|
||||||
when (exitCode /= ExitSuccess) $ liftIO $ printf "Failed to notify room %s opened by %s\n" name user
|
exitCode <- liftIO $ system command
|
||||||
|
when (exitCode /= ExitSuccess) $ liftIO $ printf "Failed to notify room %s opened by %s running command %s\n" name user command
|
||||||
|
|
||||||
notifyRoomClosed :: (MonadIO m) => RoomData -> m ()
|
notifyRoomClosed :: (MonadIO m, HasConfig a0, MonadReader a0 m) => RoomData -> m ()
|
||||||
notifyRoomClosed room = do
|
notifyRoomClosed room = do
|
||||||
|
config' <- getConfig <$> ask
|
||||||
|
let ServerOptions {notifyExecutable = notifyExecutable'} = config'
|
||||||
let (name, _) = prettyPrintOpenedRoom room
|
let (name, _) = prettyPrintOpenedRoom room
|
||||||
liftIO $ printf "Room %s closed\n" name
|
liftIO $ printf "Room %s closed\n" name
|
||||||
exitCode <- liftIO $ system $ printf "/nix/store/23nz5mjk3dj7027byc6g5avx3mfwwnqm-libnotify-0.8.3/bin/notify-send 'Room %s closed'" name
|
exitCode <- liftIO $ system $ printf "%s closed '%s'" notifyExecutable' name
|
||||||
when (exitCode /= ExitSuccess) $ liftIO $ printf "Failed to notify room %s closed\n" name
|
when (exitCode /= ExitSuccess) $ liftIO $ printf "Failed to notify room %s closed\n" name
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{-# LANGUAGE DerivingVia #-}
|
{-# LANGUAGE DerivingVia #-}
|
||||||
|
|
||||||
module Types.AppTypes (Env (..), App (..), getConnectedClientState, AppProfile (Prod, Dev)) where
|
module Types.AppTypes (Env (..), App (..), HasConfig (getConfig), getConnectedClientState, AppProfile (Prod, Dev)) where
|
||||||
|
|
||||||
import ClassyPrelude
|
import ClassyPrelude
|
||||||
import State.ConnectedClientsState
|
import State.ConnectedClientsState
|
||||||
|
@ -8,13 +8,15 @@ import State.RoomsState
|
||||||
( HasRoomsState (getRoomsState),
|
( HasRoomsState (getRoomsState),
|
||||||
RoomsState,
|
RoomsState,
|
||||||
)
|
)
|
||||||
|
import Types.Config (ServerOptions)
|
||||||
|
|
||||||
data AppProfile = Prod | Dev
|
data AppProfile = Prod | Dev
|
||||||
|
|
||||||
data Env = Env
|
data Env = Env
|
||||||
{ connectedClientsState :: ConnectedClientsState,
|
{ connectedClientsState :: ConnectedClientsState,
|
||||||
roomsState :: RoomsState,
|
roomsState :: RoomsState,
|
||||||
profile :: AppProfile
|
profile :: AppProfile,
|
||||||
|
config :: ServerOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
instance HasConnectedClientState Env where
|
instance HasConnectedClientState Env where
|
||||||
|
@ -23,6 +25,12 @@ instance HasConnectedClientState Env where
|
||||||
instance HasRoomsState Env where
|
instance HasRoomsState Env where
|
||||||
getRoomsState = roomsState
|
getRoomsState = roomsState
|
||||||
|
|
||||||
|
class HasConfig a where
|
||||||
|
getConfig :: a -> ServerOptions
|
||||||
|
|
||||||
|
instance HasConfig Env where
|
||||||
|
getConfig = config
|
||||||
|
|
||||||
newtype App env a = App {unApp :: env -> IO a}
|
newtype App env a = App {unApp :: env -> IO a}
|
||||||
deriving
|
deriving
|
||||||
( Functor,
|
( Functor,
|
||||||
|
|
61
backend/src/Types/Config.hs
Normal file
61
backend/src/Types/Config.hs
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
module Types.Config
|
||||||
|
( ServerOptions (..),
|
||||||
|
serverOptionsParser,
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import ClassyPrelude
|
||||||
|
( Applicative ((<*>)),
|
||||||
|
Int,
|
||||||
|
Semigroup ((<>)),
|
||||||
|
Show,
|
||||||
|
String,
|
||||||
|
(<$>),
|
||||||
|
)
|
||||||
|
import Options.Applicative (Parser, auto, help, long, metavar, option, short, showDefault, strOption, value)
|
||||||
|
|
||||||
|
data ServerOptions = ServerOptions
|
||||||
|
{ port :: Int, -- Main server port
|
||||||
|
websocketPort :: Int, -- WebSocket server port
|
||||||
|
listenAddress :: String, -- Address to bind the server
|
||||||
|
notifyExecutable :: String -- Path to the notify executable
|
||||||
|
}
|
||||||
|
deriving (Show)
|
||||||
|
|
||||||
|
serverOptionsParser :: Parser ServerOptions
|
||||||
|
serverOptionsParser =
|
||||||
|
ServerOptions
|
||||||
|
<$> option
|
||||||
|
auto
|
||||||
|
( long "port"
|
||||||
|
<> short 'p'
|
||||||
|
<> metavar "PORT"
|
||||||
|
<> help "Port number for the main server (default: 8081)"
|
||||||
|
<> value 8081
|
||||||
|
<> showDefault
|
||||||
|
)
|
||||||
|
<*> option
|
||||||
|
auto
|
||||||
|
( long "websocketPort"
|
||||||
|
<> short 'w'
|
||||||
|
<> metavar "WS_PORT"
|
||||||
|
<> help "Port number for the WebSocket server (default: 9160)"
|
||||||
|
<> value 9160
|
||||||
|
<> showDefault
|
||||||
|
)
|
||||||
|
<*> strOption
|
||||||
|
( long "listenAddress"
|
||||||
|
<> short 'l'
|
||||||
|
<> metavar "ADDRESS"
|
||||||
|
<> help "IP address or hostname to bind the server (default: 127.0.0.1)"
|
||||||
|
<> value "127.0.0.1"
|
||||||
|
<> showDefault
|
||||||
|
)
|
||||||
|
<*> strOption
|
||||||
|
( long "notifyExecutable"
|
||||||
|
<> short 'n'
|
||||||
|
<> metavar "EXECUTABLE"
|
||||||
|
<> help "Path to the notify executable (default: /usr/bin/notify)"
|
||||||
|
<> value "/usr/bin/notify"
|
||||||
|
<> showDefault
|
||||||
|
)
|
|
@ -10,6 +10,7 @@ import State.ConnectedClientsState (HasConnectedClientState (getConnectedClientS
|
||||||
import State.RoomsState (HasRoomsState (getRoomsState))
|
import State.RoomsState (HasRoomsState (getRoomsState))
|
||||||
import Types.AppTypes
|
import Types.AppTypes
|
||||||
( Env (..),
|
( Env (..),
|
||||||
|
HasConfig (getConfig),
|
||||||
)
|
)
|
||||||
|
|
||||||
class HasWebEnv a where
|
class HasWebEnv a where
|
||||||
|
@ -31,3 +32,6 @@ instance HasRoomsState WebEnv where
|
||||||
instance HasWebEnv WebEnv where
|
instance HasWebEnv WebEnv where
|
||||||
getRequest = request
|
getRequest = request
|
||||||
getRespond = respond
|
getRespond = respond
|
||||||
|
|
||||||
|
instance HasConfig WebEnv where
|
||||||
|
getConfig = getConfig . appEnv
|
||||||
|
|
|
@ -29,7 +29,9 @@ import State.RoomsState
|
||||||
( getRoomState,
|
( getRoomState,
|
||||||
updateRoomState,
|
updateRoomState,
|
||||||
)
|
)
|
||||||
import Types.AppTypes (Env (..))
|
import Text.Printf (printf)
|
||||||
|
import Types.AppTypes (Env (..), HasConfig (getConfig))
|
||||||
|
import Types.Config (ServerOptions (..))
|
||||||
import Types.WebEnv
|
import Types.WebEnv
|
||||||
|
|
||||||
newtype ExceptTApp e a = E {unExceptTApp :: IO (Either e a)}
|
newtype ExceptTApp e a = E {unExceptTApp :: IO (Either e a)}
|
||||||
|
@ -82,7 +84,8 @@ app ::
|
||||||
MonadError ResponseReceived m,
|
MonadError ResponseReceived m,
|
||||||
MonadRoomDataStateModify m,
|
MonadRoomDataStateModify m,
|
||||||
MonadRoomDataStateRead m,
|
MonadRoomDataStateRead m,
|
||||||
MonadBroadcast m
|
MonadBroadcast m,
|
||||||
|
HasConfig env
|
||||||
) =>
|
) =>
|
||||||
m ResponseReceived
|
m ResponseReceived
|
||||||
app = requestPathHandler
|
app = requestPathHandler
|
||||||
|
@ -94,7 +97,8 @@ requestPathHandler ::
|
||||||
MonadError ResponseReceived m,
|
MonadError ResponseReceived m,
|
||||||
MonadRoomDataStateModify m,
|
MonadRoomDataStateModify m,
|
||||||
MonadRoomDataStateRead m,
|
MonadRoomDataStateRead m,
|
||||||
MonadBroadcast m
|
MonadBroadcast m,
|
||||||
|
HasConfig env
|
||||||
) =>
|
) =>
|
||||||
m ResponseReceived
|
m ResponseReceived
|
||||||
requestPathHandler = do
|
requestPathHandler = do
|
||||||
|
@ -122,7 +126,9 @@ notFound = do
|
||||||
|
|
||||||
runWebApp ::
|
runWebApp ::
|
||||||
( MonadIO m,
|
( MonadIO m,
|
||||||
MonadReader Env m
|
MonadReader Env m,
|
||||||
|
HasConfig Env,
|
||||||
|
HasConfig WebEnv
|
||||||
) =>
|
) =>
|
||||||
m Application
|
m Application
|
||||||
runWebApp = do
|
runWebApp = do
|
||||||
|
@ -141,10 +147,14 @@ runWebApp = do
|
||||||
|
|
||||||
runWebServer ::
|
runWebServer ::
|
||||||
( MonadIO m,
|
( MonadIO m,
|
||||||
MonadReader Env m
|
MonadReader Env m,
|
||||||
|
HasConfig Env,
|
||||||
|
HasConfig WebEnv
|
||||||
) =>
|
) =>
|
||||||
m ()
|
m ()
|
||||||
runWebServer = do
|
runWebServer = do
|
||||||
putStrLn "Webserver up and running at http://localhost:8081/"
|
config' <- getConfig <$> ask
|
||||||
runWebApp >>= liftIO . (run 8081 . logStdout)
|
let ServerOptions {port = webPort, listenAddress = address} = config'
|
||||||
|
putStrLn $ pack $ printf "Webserver up and running at http://%s:%d/" address webPort
|
||||||
|
runWebApp >>= liftIO . (run webPort . logStdout)
|
||||||
return ()
|
return ()
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
{-# LANGUAGE FlexibleContexts #-}
|
{-# LANGUAGE FlexibleContexts #-}
|
||||||
|
{-# LANGUAGE MonoLocalBinds #-}
|
||||||
|
|
||||||
module WebSocket.Server
|
module WebSocket.Server
|
||||||
( runWebSocketServer,
|
( runWebSocketServer,
|
||||||
|
@ -9,22 +10,27 @@ where
|
||||||
import ClassyPrelude
|
import ClassyPrelude
|
||||||
import Data.UUID.V4 (nextRandom)
|
import Data.UUID.V4 (nextRandom)
|
||||||
import Network.WebSockets qualified as WS
|
import Network.WebSockets qualified as WS
|
||||||
import Types.AppTypes
|
import Text.Printf
|
||||||
|
import Types.AppTypes (Env, HasConfig (getConfig))
|
||||||
|
import Types.Config (ServerOptions (..))
|
||||||
import WebSocket.WSApp (WSApp (..), WSEnv (..), wsApp)
|
import WebSocket.WSApp (WSApp (..), WSEnv (..), wsApp)
|
||||||
|
|
||||||
runWebSocketServer ::
|
runWebSocketServer ::
|
||||||
( MonadIO m,
|
( MonadIO m,
|
||||||
MonadReader Env m
|
Types.AppTypes.HasConfig Types.AppTypes.Env,
|
||||||
|
MonadReader Types.AppTypes.Env m
|
||||||
) =>
|
) =>
|
||||||
m ()
|
m ()
|
||||||
runWebSocketServer = do
|
runWebSocketServer = do
|
||||||
putStrLn "Websocket up at 0.0.0.0:9160"
|
config' <- getConfig <$> ask
|
||||||
|
let ServerOptions {websocketPort = wsPort, listenAddress = address} = config'
|
||||||
|
putStrLn $ pack $ printf "WebSocket server up and running at ws://%s:%d/" address wsPort
|
||||||
wsApp' <- runWSApp
|
wsApp' <- runWSApp
|
||||||
liftIO $ WS.runServer "0.0.0.0" 9160 wsApp'
|
liftIO $ WS.runServer address wsPort wsApp'
|
||||||
|
|
||||||
runWSApp ::
|
runWSApp ::
|
||||||
( MonadIO m,
|
( MonadIO m,
|
||||||
MonadReader Env m
|
MonadReader Types.AppTypes.Env m
|
||||||
) =>
|
) =>
|
||||||
m WS.ServerApp
|
m WS.ServerApp
|
||||||
runWSApp = do
|
runWSApp = do
|
||||||
|
|
Loading…
Reference in a new issue