packages feed

mondo (empty) → 0.1.0.0

raw patch · 8 files changed

+470/−0 lines, 8 filesdep +aesondep +authenticate-oauthdep +basesetup-changed

Dependencies added: aeson, authenticate-oauth, base, bytestring, http-client, http-client-tls, mtl, servant, servant-client, transformers

Files

+ LICENSE view
@@ -0,0 +1,20 @@+Copyright (c) 2016 Michael B. Gale
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ README.md view
@@ -0,0 +1,7 @@+# Haskell bindings for the Mondo API
+
+## Getting started
+
+There is some example code in `test/Example.hs`. The code assumes that a file named `token.txt` containing a valid OAuth token is in the same folder.
+
+To run the example, run `cabal repl` (if using a cabal sandbox) or `ghci Mondo.hs` (if not). Then load the example into the REPL with `:l test/Example.hs`. Finally, invoke the example with `withMondo token foo`.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple
+main = defaultMain
+ mondo.cabal view
@@ -0,0 +1,90 @@+-- Initial mondo.cabal generated by cabal init.  For further documentation,
+--  see http://haskell.org/cabal/users-guide/
+
+-- The name of the package.
+name:                   mondo
+
+-- The package version.  See the Haskell package versioning policy (PVP)
+-- for standards guiding when and how versions should be incremented.
+-- http://www.haskell.org/haskellwiki/Package_versioning_policy
+-- PVP summary:         +-+------- breaking API changes
+--                      | | +----- non-breaking API additions
+--                      | | | +--- code changes with no API change
+version:                0.1.0.0
+
+-- A short (one-line) description of the package.
+synopsis:               Haskell bindings for the Mondo API
+
+-- A longer description of the package.
+description:            Provides Haskell bindings for the Mondo API.
+
+-- The license under which the package is released.
+license:                MIT
+
+-- The file containing the license text.
+license-file:           LICENSE
+
+-- The package author(s).
+author:                 Michael B. Gale
+
+-- An email address to which users can send suggestions, bug reports, and
+-- patches.
+maintainer:             michael.gale@cl.cam.ac.uk
+
+-- A copyright notice.
+-- copyright:
+
+category:               Web
+
+build-type:             Simple
+
+-- Extra files to be distributed with the package, such as examples or a
+-- README.
+extra-source-files:     README.md,
+                        test/Example.hs
+
+-- Constraint on the version of Cabal needed to build this package.
+cabal-version:          >=1.10
+
+source-repository head
+  type:                 git
+  location:             https://github.com/mbg/mondo
+
+library
+  -- Modules exported by the library.
+  exposed-modules:      Mondo
+
+  -- Modules included in this library but not exported.
+  other-modules:        Mondo.API,
+                        Mondo.Types
+
+  -- LANGUAGE extensions used by modules in this package.
+  -- other-extensions:
+
+  -- Other library packages from which modules are imported.
+  build-depends:        base >=4.8 && <4.9,
+                        aeson >=0.11,
+                        servant >=0.5,
+                        servant-client >=0.5,
+                        transformers >=0.4,
+                        http-client >= 0.4,
+                        http-client-tls >= 0.2,
+                        authenticate-oauth >=1.5,
+                        bytestring >=0.10,
+                        mtl
+
+  -- Directories containing source files.
+  hs-source-dirs:       src
+
+  -- Base language which the package is written in.
+  default-language:     Haskell2010
+
+  default-extensions:   TypeOperators,
+                        DataKinds,
+                        DeriveGeneric,
+                        OverloadedStrings,
+                        ScopedTypeVariables,
+                        FlexibleInstances,
+                        FlexibleContexts,
+                        UndecidableInstances,
+                        TypeFamilies
+ src/Mondo.hs view
@@ -0,0 +1,32 @@+--------------------------------------------------------------------------------
+-- Haskell bindings for the Mondo API                                         --
+-- Written by Michael B. Gale (michael.gale@cl.cam.ac.uk)                     --
+--------------------------------------------------------------------------------
+
+module Mondo (
+    module Mondo.Types,
+    module Mondo.API
+) where
+
+--------------------------------------------------------------------------------
+
+import Control.Monad.IO.Class
+
+import qualified Data.ByteString.Internal as BS
+
+import Web.Authenticate.OAuth
+
+import Servant.API.BasicAuth
+import Servant.Client (ServantError)
+
+import Mondo.Types
+import Mondo.API
+
+--------------------------------------------------------------------------------
+
+mondoAuth :: OAuth
+mondoAuth = newOAuth {
+    oauthServerName = "https://auth.getmondo.co.uk/"
+}
+
+--------------------------------------------------------------------------------
+ src/Mondo/API.hs view
@@ -0,0 +1,139 @@+--------------------------------------------------------------------------------
+-- Haskell bindings for the Mondo API                                         --
+-- Written by Michael B. Gale (michael.gale@cl.cam.ac.uk)                     --
+--------------------------------------------------------------------------------
+
+{-# LANGUAGE FunctionalDependencies #-}
+
+module Mondo.API where
+
+--------------------------------------------------------------------------------
+
+import GHC.TypeLits
+
+import qualified Data.ByteString.Internal as BS
+import Data.Proxy
+import Data.Monoid
+
+import Control.Monad.Trans
+import Control.Monad.Trans.Except
+import Control.Monad.Trans.Reader
+
+import Network.HTTP.Client (Manager, newManager)
+import Network.HTTP.Client.TLS
+
+import Servant.API hiding (addHeader)
+import Servant.Common.Req (Req(..), addHeader)
+import Servant.Client
+
+import Mondo.Types
+
+--------------------------------------------------------------------------------
+
+data MondoAuth
+
+instance HasClient api => HasClient (MondoAuth :> api) where
+    type Client (MondoAuth :> api) = String -> Client api
+
+    clientWithRoute Proxy req url mgr val =
+        clientWithRoute (Proxy :: Proxy api) (mondoAuthReq val req) url mgr
+
+mondoAuthReq :: String -> Req -> Req
+mondoAuthReq token = addHeader "Authorization" ("Bearer " <> token)
+
+--------------------------------------------------------------------------------
+
+data MondoAccount
+
+instance HasClient api => HasClient (MondoAccount :> api) where
+    type Client (MondoAccount :> api) = AccountID -> Client api
+
+    clientWithRoute Proxy req url mgr val =
+        clientWithRoute (Proxy :: Proxy api) (addHeader "account_id" val req) url mgr
+
+--------------------------------------------------------------------------------
+
+-- | A type representing the Mondo API.
+type MondoAPI =
+      MondoAuth :> "accounts" :> Get '[JSON] AccountsResponse
+ :<|> MondoAuth :> "balance" :> QueryParam "account_id" AccountID :> Get '[JSON] Balance
+ :<|> MondoAuth :> "transactions" :> Capture "transaction_id" String :> Get '[JSON] TransactionResponse
+ :<|> MondoAuth :> "transactions" :> QueryParam "account_id" AccountID :> Get '[JSON] Transactions
+
+
+mondoAPI :: Proxy MondoAPI
+mondoAPI = Proxy
+
+-- | The URL of the Mondo API.
+mondoURL :: BaseUrl
+mondoURL = BaseUrl Https "api.getmondo.co.uk" 443 "/"
+
+--------------------------------------------------------------------------------
+
+type Mondo = ReaderT String (ReaderT Manager (ExceptT ServantError IO))
+
+withMondo :: String -> Mondo a -> IO (Either ServantError a)
+withMondo tkn m = do
+    manager <- newManager tlsManagerSettings
+    runExceptT (runReaderT (runReaderT m tkn) manager)
+
+credential :: Mondo String
+credential = ask
+
+manager :: Mondo Manager
+manager = lift ask
+
+--------------------------------------------------------------------------------
+
+api = client mondoAPI mondoURL
+
+getAccounts :: Mondo AccountsResponse
+getAccounts = do
+    mgr <- manager
+    tkn <- credential
+    let
+        fn = getNth (Proxy :: Proxy 0) $ api mgr
+    lift $ lift $ fn tkn
+
+getBalance :: String -> Mondo Balance
+getBalance acc = do
+    mgr <- manager
+    tkn <- credential
+    let
+        fn = getNth (Proxy :: Proxy 1) $ api mgr
+    lift $ lift $ fn tkn (Just acc)
+
+getTransaction :: String -> Mondo Transaction
+getTransaction tr = do
+    mgr <- manager
+    tkn <- credential
+    let
+        fn = getNth (Proxy :: Proxy 2) $ api mgr
+    lift $ lift (transaction <$> fn tkn tr)
+
+getTransactions :: AccountID -> Mondo Transactions
+getTransactions acc = do
+    mgr <- manager
+    tkn <- credential
+    let
+        fn = getNth (Proxy :: Proxy 3) $ api mgr
+    lift $ lift $ fn tkn (Just acc)
+
+--------------------------------------------------------------------------------
+
+class GetNth (n :: Nat) a b | n a -> b where
+    getNth :: Proxy n -> a -> b
+
+instance x ~ y => GetNth 0 x y where
+    getNth _ x = x
+
+instance {-# OVERLAPPING #-}
+  GetNth 0 (x :<|> y) x where
+      getNth _ (x :<|> _) = x
+
+instance
+  (GetNth (n-1) x y) => GetNth n (a :<|> x) y where
+      getNth _ (_ :<|> x) = getNth (Proxy :: Proxy (n-1)) x
+
+
+--------------------------------------------------------------------------------
+ src/Mondo/Types.hs view
@@ -0,0 +1,146 @@+--------------------------------------------------------------------------------
+-- Haskell bindings for the Mondo API                                         --
+-- Written by Michael B. Gale (michael.gale@cl.cam.ac.uk)                     --
+--------------------------------------------------------------------------------
+
+module Mondo.Types where
+
+--------------------------------------------------------------------------------
+
+import GHC.Generics
+
+import Control.Monad (mzero)
+
+import Data.Aeson
+
+--------------------------------------------------------------------------------
+
+type AccountID = String
+
+--------------------------------------------------------------------------------
+
+data AccountsResponse = AccountsResponse {
+    accounts :: [Account]
+} deriving (Show, Generic)
+
+instance FromJSON AccountsResponse
+
+data Account = Account {
+    id          :: AccountID,
+    description :: String,
+    created     :: String
+} deriving (Show, Generic)
+
+accountID :: Account -> String
+accountID (Account x _ _) = x
+
+instance FromJSON Account
+
+data Balance = Balance {
+    balance     :: Integer,
+    currency    :: String,
+    spend_today :: Integer
+} deriving (Show, Generic)
+
+instance FromJSON Balance
+
+-- | Enumerates reasons which cause transactions to be declined.
+data DeclineReason
+    = InsufficientFunds
+    | CardInactive
+    | CardBlocked
+    | Other
+    deriving Show
+
+instance FromJSON DeclineReason where
+    parseJSON (String "INSUFFICIENT_FUNDS") = pure InsufficientFunds
+    parseJSON (String "CARD_INACTIVE")      = pure CardInactive
+    parseJSON (String "CARD_BLOCKED")       = pure CardBlocked
+    parseJSON (String "OTHER")              = pure Other
+    parseJSON _                             = mzero
+
+data Address = Address {
+    addrAddress   :: String,
+    addrCity      :: String,
+    addrCountry   :: String,
+    addrLatitude  :: String,
+    addrLongitude :: String,
+    addrPostcode  :: String,
+    addrRegion    :: String
+} deriving Show
+
+instance FromJSON Address where
+    parseJSON (Object v) =
+        Address <$> v .: "address"
+                <*> v .: "city"
+                <*> v .: "country"
+                <*> v .: "latitude"
+                <*> v .: "longitude"
+                <*> v .: "postcode"
+                <*> v .: "region"
+
+data Merchant = Merchant {
+    merchantAddress  :: Maybe Address,
+    merchantCreated  :: Maybe String,
+    merchantGroupID  :: Maybe String,
+    merchantID       :: String,
+    merchantLogo     :: Maybe String,
+    merchantEmoji    :: Maybe String,
+    merchantName     :: Maybe String,
+    merchantCategory :: Maybe String
+} deriving Show
+
+instance FromJSON Merchant where
+    parseJSON (Object v) =
+        Merchant <$> v .: "address"
+                 <*> v .: "created"
+                 <*> v .: "group_id"
+                 <*> v .: "id"
+                 <*> v .: "logo"
+                 <*> v .: "emoji"
+                 <*> v .: "name"
+                 <*> v .: "category"
+
+data Transactions = Transactions {
+    transactions :: [Transaction]
+} deriving (Show, Generic)
+
+instance FromJSON Transactions
+
+data TransactionResponse = TransactionResponse {
+    transaction :: Transaction
+} deriving (Show, Generic)
+
+instance FromJSON TransactionResponse
+
+-- TODO: add metadata
+
+data Transaction = Transaction {
+    transactionAccountBalance :: Integer,
+    transactionAmount         :: Integer,
+    transactionCreated        :: String,
+    transactionCurrency       :: String,
+    transactionDescription    :: String,
+    transactionID             :: String,
+    transactionDeclineReason  :: Maybe DeclineReason,
+    transactionIsLoad         :: Bool,
+    transactionSettled        :: Bool,
+    transactionCategory       :: Maybe String,
+    transactionMerchant       :: Merchant
+} deriving Show
+
+instance FromJSON Transaction where
+    parseJSON (Object v) =
+        Transaction <$> v .: "account_balance"
+                    <*> v .: "amount"
+                    <*> v .: "created"
+                    <*> v .: "currency"
+                    <*> v .: "description"
+                    <*> v .: "id"
+                    <*> v .: "decline_reason"
+                    <*> v .: "is_load"
+                    <*> v .: "settled"
+                    <*> v .: "category"
+                    <*> v .: "merchant"
+
+--------------------------------------------------------------------------------
+ test/Example.hs view
@@ -0,0 +1,34 @@+--------------------------------------------------------------------------------
+-- Haskell bindings for the Mondo API                                         --
+-- Written by Michael B. Gale (michael.gale@cl.cam.ac.uk)                     --
+--------------------------------------------------------------------------------
+
+module Example where
+
+--------------------------------------------------------------------------------
+
+import Control.Monad.IO.Class
+
+import System.IO.Unsafe
+
+import Mondo
+
+--------------------------------------------------------------------------------
+
+token :: String
+token = head $ lines $ unsafePerformIO $ readFile "test/token.txt"
+
+--------------------------------------------------------------------------------
+
+foo :: Mondo ()
+foo = do
+    AccountsResponse [acc] <- getAccounts
+    liftIO $ print acc
+
+    r2 <- getBalance (accountID acc)
+    liftIO $ print r2
+
+    r3 <- getTransactions (accountID acc)
+    liftIO $ print r3
+
+--------------------------------------------------------------------------------