shelduck-0.1.2.0: src/Shelduck.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
module Shelduck (
requestTopic,
requestEndpoint,
requestOpts,
requestParameters,
Shelduck.response,
timing,
info,
TopicResult,
server,
WebhookRequest,
performRequest,
blank,
doHandle
) where
import Configuration
import Control.Concurrent
import Control.Concurrent.Async
import Control.Concurrent.STM.TVar
import Control.Lens hiding ((.=))
import Control.Lens.TH
import Control.Monad
import Control.Monad.STM
import Control.Monad.Trans.Class
import Control.Monad.Trans.Reader
import Data.Aeson
import Data.Aeson.Lens
import Data.ByteString.Lazy (toStrict)
import qualified Data.ByteString.Lazy as L
import Data.Maybe
import Data.Monoid
import Data.Text
import Data.Text.Encoding
import Internal
import qualified Network.Wreq as W
import Rainbow
import Shelly
import Templating
import Web.Spock.Safe
data WebhookRequest = WebhookRequest {
_requestEndpoint :: Text,
_requestOpts :: W.Options,
_requestParameters :: Value,
_requestTopic :: Text
}
blank :: WebhookRequest
blank = WebhookRequest mempty W.defaults (object []) mempty
$(makeLenses ''WebhookRequest)
data TimedResponse = TimedResponse {
_response :: W.Response L.ByteString,
_timing :: Int
} deriving (Show)
$(makeLenses ''TimedResponse)
type TopicResult = Maybe Text
doPost :: (WebhookRequest, Text, Text) -> ReaderT a IO (W.Response L.ByteString)
doPost (w, e, p) = lift $ W.postWith (w ^. requestOpts) (unpack e) (encodeUtf8 p)
doLog :: W.Response L.ByteString -> ReaderT a IO (W.Response L.ByteString)
doLog r = lift ((info . pack . show) (r ^. W.responseStatus)) >> return r
doWait :: Int -> W.Response L.ByteString -> ReaderT (TVar TopicResult) IO TimedResponse
doWait c x = ask >>=
\t -> lift $ do
(c, r) <- pollingIO c t predicate (return x)
return $ TimedResponse r c
where predicate t = fmap isJust (readTVarIO t)
doHandle :: WebhookRequest -> ReaderT (TVar TopicResult) IO Bool
doHandle w = ask >>=
\t -> lift $ do
b <- atomically $ do
b' <- readTVar t
writeTVar t Nothing
return b'
handleSuccess (fromMaybe mempty b) (w ^. requestTopic)
doLogTimings :: Int -> TimedResponse -> IO ()
doLogTimings i t = void $ info $ mconcat ["Took approx: ", (pack . show) time, " microseconds..."]
where time = (i - (t ^. timing)) * pollTime
performRequest :: WebhookRequest -> ReaderT (TVar TopicResult) IO TimedResponse
performRequest w = doTemplating >>=
doPost >>=
doLog >>=
doWait polls >>=
(\x -> lift (doLogTimings polls x) >> doHandle w >> return x)
where doTemplating = lift $ do
e <- template (w ^. requestEndpoint)
p <- template ((decodeUtf8 . toStrict . encode) $ w ^. requestParameters)
return (w, e, p)
handleSuccess :: Text -> Text -> IO Bool
handleSuccess b t =
if b == t
then success (mconcat [" Good topic: ", showResult b]) >> return True
else failure (mconcat [" Bad topic: ", showResult b]) >> return False
where showResult = pack . show
failure :: Text -> IO ()
failure x = putChunkLn $ chunk x & fore red
info :: Text -> IO ()
info x = putChunkLn $ chunk x & fore cyan
success :: Text -> IO ()
success x = putChunkLn $ chunk x & fore green
ngrok :: IO ()
ngrok = shelly $ verbosely $ run "ngrok" ["start", "shelduck"] >>= (liftIO . info)
server :: TVar TopicResult -> IO ()
server t = void $ concurrently ngrok $ do
info "Starting server to listen for webhooks"
runSpock 8080 $ spockT id $
post root $ do
responseBody <- body
let topic = responseBody ^? key "topic"
lift $ case topic of
Just (String s) -> record (Just s) t
Nothing -> record Nothing t
r <- lift $ readTVarIO t
text $ r & (pack . show)