zgo-backend/src/Xero.hs

196 lines
5.2 KiB
Haskell
Raw Normal View History

2022-08-10 15:17:47 +00:00
{-# LANGUAGE OverloadedStrings #-}
module Xero where
2022-08-18 19:21:32 +00:00
import Control.Monad.IO.Class
2022-08-10 15:17:47 +00:00
import Data.Aeson
import qualified Data.Bson as B
2022-08-18 19:21:32 +00:00
import qualified Data.ByteString.Lazy as BL
2022-08-11 22:30:24 +00:00
import Data.Maybe
2022-08-10 15:17:47 +00:00
import qualified Data.Text as T
2022-08-18 19:21:32 +00:00
import Data.Text.Encoding
import Data.Time.Calendar
2022-08-11 22:30:24 +00:00
import Data.Time.Clock
2022-08-10 15:17:47 +00:00
import Database.MongoDB
import GHC.Generics
2022-08-18 19:21:32 +00:00
import Network.HTTP.Simple
import Network.HTTP.Types.Header
2022-08-10 15:17:47 +00:00
-- | Type to represent a Xero app configuration
data Xero =
Xero
{ x_id :: ObjectId
, x_clientId :: T.Text
, x_clientSecret :: T.Text
}
deriving (Eq, Show)
instance ToJSON Xero where
toJSON (Xero i cI s) =
object ["_id" .= show i, "clientId" .= cI, "clientSecret" .= s]
instance Val Xero where
val (Xero i cI s) = Doc ["_id" =: i, "clientId" =: cI, "clientSecret" =: s]
cast' (Doc d) = do
i <- B.lookup "_id" d
cI <- B.lookup "clientId" d
s <- B.lookup "clientSecret" d
Just (Xero i cI s)
cast' _ = Nothing
2022-08-11 22:30:24 +00:00
-- | Type to represent a Xero access token
data XeroToken =
XeroToken
{ t_id :: Maybe ObjectId
, t_address :: T.Text
, t_access :: T.Text
2022-08-18 19:21:32 +00:00
, t_expires :: Integer
2022-08-11 22:30:24 +00:00
, t_refresh :: T.Text
2022-08-18 19:21:32 +00:00
, t_accdte :: UTCTime
, t_refdte :: UTCTime
2022-08-11 22:30:24 +00:00
}
deriving (Eq, Show)
instance ToJSON XeroToken where
2022-08-18 19:21:32 +00:00
toJSON (XeroToken i a t e r aD d) =
2022-08-11 22:30:24 +00:00
case i of
Just oid ->
object
[ "_id" .= show oid
, "address" .= a
, "accessToken" .= t
, "expires" .= e
, "refreshToken" .= r
2022-08-18 19:21:32 +00:00
, "accExpires" .= aD
, "refExpires" .= d
2022-08-11 22:30:24 +00:00
]
Nothing ->
object
[ "_id" .= ("" :: String)
, "address" .= a
, "accessToken" .= t
, "expires" .= e
, "refreshToken" .= r
2022-08-18 19:21:32 +00:00
, "accExpires" .= aD
, "refExpires" .= d
2022-08-11 22:30:24 +00:00
]
instance FromJSON XeroToken where
parseJSON =
withObject "XeroToken" $ \obj -> do
2022-08-18 19:21:32 +00:00
t <- obj .: "access_token"
e <- obj .: "expires_in"
r <- obj .: "refresh_token"
2022-08-11 22:30:24 +00:00
pure $
XeroToken
2022-08-18 19:21:32 +00:00
Nothing
""
2022-08-11 22:30:24 +00:00
t
e
r
2022-08-18 19:21:32 +00:00
(UTCTime (fromGregorian 2000 1 1) (secondsToDiffTime 0))
(UTCTime (fromGregorian 2000 1 1) (secondsToDiffTime 0))
2022-08-11 22:30:24 +00:00
instance Val XeroToken where
2022-08-18 19:21:32 +00:00
val (XeroToken i a t e r aD d) =
2022-08-11 22:30:24 +00:00
if isJust i
then Doc
[ "_id" =: i
, "address" =: a
, "accessToken" =: t
, "expires" =: e
, "refreshToken" =: r
2022-08-18 19:21:32 +00:00
, "accExpires" =: aD
, "refExpires" =: d
2022-08-11 22:30:24 +00:00
]
else Doc
[ "address" =: a
, "accessToken" =: t
, "expires" =: e
, "refreshToken" =: r
2022-08-18 19:21:32 +00:00
, "accExpires" =: aD
, "refExpires" =: d
2022-08-11 22:30:24 +00:00
]
cast' (Doc d) = do
i <- B.lookup "_id" d
a <- B.lookup "address" d
t <- B.lookup "accessToken" d
e <- B.lookup "expires" d
r <- B.lookup "refreshToken" d
2022-08-18 19:21:32 +00:00
aD <- B.lookup "accExpires" d
d <- B.lookup "refExpires" d
Just (XeroToken i a t e r aD d)
2022-08-11 22:30:24 +00:00
cast' _ = Nothing
2022-08-18 19:21:32 +00:00
processToken :: XeroToken -> T.Text -> IO XeroToken
processToken t a = do
now <- getCurrentTime
return $
XeroToken
(t_id t)
a
(t_access t)
(t_expires t)
(t_refresh t)
(addUTCTime (fromIntegral $ t_expires t) now)
(addUTCTime 5184000 now)
2022-08-10 15:17:47 +00:00
-- Database actions
findXero :: Action IO (Maybe Document)
findXero = findOne (select [] "xero")
2022-08-11 22:30:24 +00:00
2022-08-18 19:21:32 +00:00
upsertToken :: XeroToken -> Action IO (Maybe Document)
2022-08-11 22:30:24 +00:00
upsertToken t = do
let token = val t
case token of
2022-08-18 19:21:32 +00:00
Doc d -> do
upsert (select ["address" =: t_address t] "xerotokens") d
findOne (select ["address" =: t_address t] "xerotokens")
_ -> return Nothing
2022-08-11 22:30:24 +00:00
findToken :: T.Text -> Action IO (Maybe Document)
findToken a = findOne (select ["address" =: a] "xerotokens")
2022-08-18 19:21:32 +00:00
-- | Function to request accesstoken
requestXeroToken ::
(Action IO (Maybe Document) -> IO (Maybe Document))
-> Xero
-> T.Text
-> T.Text
-> IO Bool
requestXeroToken f cred code address = do
token <- f $ findToken address
case token of
Just xT -> do
let xToken = cast' (Doc xT) :: Maybe XeroToken
case xToken of
Nothing -> return False
Just x -> return True
Nothing -> do
let pars =
"grant_type=authorization_code&code=" <>
code <> "&redirect_uri=http://localhost:4200/test"
let req =
setRequestBodyLBS (BL.fromStrict (encodeUtf8 pars)) $
addRequestHeader hContentType "application/x-www-form-urlencoded" $
setRequestSecure True $
setRequestBasicAuth
(encodeUtf8 $ x_clientId cred)
(encodeUtf8 $ x_clientSecret cred) $
setRequestHost "identity.xero.com" $
setRequestPort 443 $
setRequestMethod "POST" $
setRequestPath "/connect/token" defaultRequest
res <- httpJSON req
let rCode = getResponseStatusCode (res :: Response XeroToken)
case rCode of
200 -> do
let newToken = getResponseBody (res :: Response XeroToken)
pToken <- processToken newToken address
print pToken
_ <- f $ upsertToken pToken
return True
_ -> do
print res
return False