haskell-xmpp-2.0.0: src/Network/XMPP/Ejabberd.hs
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
-- | Ejabberd api support
module Network.XMPP.Ejabberd
( EjabberdHost(..)
, EUser(..)
, VHost(..)
, EResult(..)
, RegisterUserReq(..)
, localEjabberdHost
, getRegisteredUsers
, registerNewUser
) where
import GHC.Generics (Generic)
import qualified Data.Aeson as J
import qualified Data.ByteString.Lazy as BSL
import Network.HTTP.Client (RequestBody(..), Response)
import Network.HTTP.Simple
(getResponseBody, httpLBS, parseRequest_, setRequestBody)
import Control.Exception
import Data.Text(Text)
import qualified Data.Text as Text
import Text.Printf
import Data.Char (isLower)
import Control.Applicative
data EUser =
EUser
{ euName :: Text
, euPassword :: Text
} deriving (Eq, Show, Generic)
newtype VHost =
VHost { vhHost :: Text }
deriving (Eq, Show, Generic)
data EResult a
= ESuccess a
| EError
{ eStatus :: Text
, eCode :: Int
, eMessage :: Text
}
deriving (Eq, Show)
data RegisterUserReq = RegisterUserReq
{ rurUser :: Text
, rurPassword :: Text
, rurHost :: Text
} deriving (Eq, Show, Generic)
instance J.FromJSON a => J.FromJSON (EResult a) where
parseJSON raw =
let failed = flip (J.withObject "EjabberdResponse") raw $ \o -> do
status <- o J..: "status"
code <- o J..: "code"
msg <- o J..: "message"
pure $ EError status code msg
success = ESuccess <$> J.parseJSON raw
in success <|> failed
instance J.ToJSON VHost where
toJSON = J.genericToJSON snakeLabel
instance J.ToJSON RegisterUserReq where
toJSON = J.genericToJSON snakeLabel
-- | Make sure to get the port right
-- https://docs.ejabberd.im/admin/guide/security/
-- an example is available in 'localEjabberdHost',
-- which is used for the integration tests.
newtype EjabberdHost = EjabberdHost String
snakeConstructor :: J.Options
snakeConstructor = J.defaultOptions { J.constructorTagModifier = J.camelTo2 '_' }
snakeLabel :: J.Options
snakeLabel = snakeConstructor { J.fieldLabelModifier = J.camelTo2 '_' . dropWhile isLower }
localEjabberdHost :: EjabberdHost
localEjabberdHost = EjabberdHost "http://localhost:5443"
toPath :: EjabberdHost -> String -> String
toPath (EjabberdHost d) = printf "POST %s/%s" d
-- | https://docs.ejabberd.im/developer/ejabberd-api/admin-api/#registered-users
--
-- @since 2.0.0
getRegisteredUsers :: EjabberdHost -> VHost -> IO (EResult [Text])
getRegisteredUsers ejabberd vhost = do -- TODO: monad reader with api host and manager
let body = RequestBodyLBS $ J.encode vhost
path = toPath ejabberd "api/registered_users"
req = setRequestBody body $ parseRequest_ path
resp :: Either SomeException (Response BSL.ByteString) <-
try $ httpLBS req
let eiResult = returnable . J.eitherDecode . getResponseBody <$> resp
pure $ either (EError "exception" (-1) . Text.pack . show) id eiResult
where returnable = either (EError "error" (-1) . Text.pack) id
-- | https://docs.ejabberd.im/developer/ejabberd-api/admin-api/#register
--
-- @since 2.0.0
registerNewUser :: EjabberdHost -> EUser -> VHost -> IO (EResult Text)
registerNewUser ejabberd newUser h = do
let body = RegisterUserReq (euName newUser) (euPassword newUser) $ vhHost h
encodedBody = RequestBodyLBS $ J.encode body
path = toPath ejabberd "api/register"
req = setRequestBody encodedBody $ parseRequest_ path
resp :: Either SomeException (Response BSL.ByteString) <-
try $ httpLBS req
let eiResult = returnable . J.eitherDecode . getResponseBody <$> resp
pure $ either (EError "exception" (-1) . Text.pack . show) id eiResult
where returnable = either (EError "error" (-1) . Text.pack) id