Initial commit
This commit is contained in:
commit
5279f5c739
12 changed files with 685 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
.stack-work/
|
||||||
|
*~
|
3
ChangeLog.md
Normal file
3
ChangeLog.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# Changelog for zgo-backend
|
||||||
|
|
||||||
|
## Unreleased changes
|
23
LICENSE
Normal file
23
LICENSE
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
[The MIT License (MIT)][]
|
||||||
|
|
||||||
|
Copyright (c) 2022 Rene Vergara
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
[The MIT License (MIT)]: https://opensource.org/licenses/MIT
|
1
README.md
Normal file
1
README.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
# zgo-backend
|
2
Setup.hs
Normal file
2
Setup.hs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
import Distribution.Simple
|
||||||
|
main = defaultMain
|
179
app/Main.hs
Normal file
179
app/Main.hs
Normal file
|
@ -0,0 +1,179 @@
|
||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
|
module Main where
|
||||||
|
|
||||||
|
import Control.Concurrent (forkIO, threadDelay)
|
||||||
|
import Control.Monad
|
||||||
|
import Control.Monad.IO.Class
|
||||||
|
import Data.Aeson
|
||||||
|
import Data.SecureMem
|
||||||
|
import qualified Data.Text as T
|
||||||
|
import qualified Data.Text.Lazy as L
|
||||||
|
import Database.MongoDB
|
||||||
|
import GHC.Generics
|
||||||
|
import Network.HTTP.Simple
|
||||||
|
import Network.HTTP.Types.Status
|
||||||
|
import Network.Wai.Middleware.HttpAuth
|
||||||
|
import Web.Scotty
|
||||||
|
import ZGoBackend
|
||||||
|
|
||||||
|
passkey :: SecureMem
|
||||||
|
passkey = secureMemFromByteString "superSecret"
|
||||||
|
|
||||||
|
nodeAddress :: T.Text
|
||||||
|
nodeAddress =
|
||||||
|
"zs1xnpqd2tae9d95f8fhe4l0q7j44a5vf993m0pcnnvr56uqr4lgqlamesk5v4c5rhtvywc6lvlduy"
|
||||||
|
|
||||||
|
dbUser :: T.Text
|
||||||
|
dbUser = "zgo"
|
||||||
|
|
||||||
|
dbPassword :: T.Text
|
||||||
|
dbPassword = "zcashrules"
|
||||||
|
|
||||||
|
main :: IO ()
|
||||||
|
main = do
|
||||||
|
putStrLn "Starting Server..."
|
||||||
|
pipe <- connect $ host "127.0.0.1"
|
||||||
|
let run = access pipe master "zgo"
|
||||||
|
j <- run (auth dbUser dbPassword)
|
||||||
|
_ <- forkIO (setInterval 60 (checkZcashPrices pipe))
|
||||||
|
if j
|
||||||
|
then putStrLn "Connected to MongoDB!"
|
||||||
|
else fail "MongoDB connection failed!"
|
||||||
|
scotty 4000 $ do
|
||||||
|
middleware $
|
||||||
|
basicAuth
|
||||||
|
(\u p -> return $ u == "user" && secureMemFromByteString p == passkey)
|
||||||
|
"ZGo Backend"
|
||||||
|
--Get list of countries for UI
|
||||||
|
get "/api/countries" $ do
|
||||||
|
countries <- liftIO $ run listCountries
|
||||||
|
case countries of
|
||||||
|
[] -> do
|
||||||
|
status noContent204
|
||||||
|
Web.Scotty.json
|
||||||
|
(object ["message" .= ("No countries available" :: String)])
|
||||||
|
_ -> do
|
||||||
|
Web.Scotty.json
|
||||||
|
(object
|
||||||
|
[ "message" .= ("Country data found" :: String)
|
||||||
|
, "countries" .= toJSON (map parseCountryBson countries)
|
||||||
|
])
|
||||||
|
--Get user associated with session
|
||||||
|
get "/api/user" $ do
|
||||||
|
session <- param "session"
|
||||||
|
user <- liftIO $ run (findUser session)
|
||||||
|
case user of
|
||||||
|
Nothing -> status noContent204
|
||||||
|
Just u ->
|
||||||
|
Web.Scotty.json
|
||||||
|
(object
|
||||||
|
[ "message" .= ("User found" :: String)
|
||||||
|
, "user" .= toJSON (parseUserBson u)
|
||||||
|
])
|
||||||
|
--Delete user
|
||||||
|
Web.Scotty.delete "/api/user/:id" $ do text "Deleted that guy!"
|
||||||
|
--Get txs from DB that have less than 10 confirmations
|
||||||
|
get "/api/pending" $ do
|
||||||
|
session <- param "session"
|
||||||
|
pending <- liftIO $ run (findPending session)
|
||||||
|
case pending of
|
||||||
|
[] -> do
|
||||||
|
status noContent204
|
||||||
|
Web.Scotty.json
|
||||||
|
(object ["message" .= ("No pending transactions" :: String)])
|
||||||
|
_ -> do
|
||||||
|
Web.Scotty.json
|
||||||
|
(object
|
||||||
|
[ "message" .= ("Found pending transactions" :: String)
|
||||||
|
, "txs" .= toJSON (map parseZGoTxBson pending)
|
||||||
|
])
|
||||||
|
--Get current blockheight from Zcash node
|
||||||
|
get "/api/blockheight" $ do
|
||||||
|
blockInfo <- makeZcashCall "getblock" ["-1"]
|
||||||
|
Web.Scotty.json (result (getResponseBody blockInfo :: RpcResponse Block))
|
||||||
|
--Get transactions associated with ZGo node
|
||||||
|
get "/api/txs" $ do
|
||||||
|
txs <- makeZcashCall "z_listreceivedbyaddress" [nodeAddress]
|
||||||
|
Web.Scotty.json (result (getResponseBody txs :: RpcResponse [ZcashTx]))
|
||||||
|
--Get the ZGo node's shielded address
|
||||||
|
get "/api/getaddr" $ do Web.Scotty.json (object ["addr" .= nodeAddress])
|
||||||
|
--Get owner by address
|
||||||
|
get "/api/owner" $ do text "Here's an owner for you"
|
||||||
|
--Upsert owner to DB
|
||||||
|
post "/api/owner" $ do text "I added an owner for you"
|
||||||
|
--Validate user, updating record
|
||||||
|
post "/api/validateuser" $ do text "Marked user as validated"
|
||||||
|
--Get items associated with the given address
|
||||||
|
get "/api/items" $ do text "Here are your items"
|
||||||
|
--Upsert item
|
||||||
|
post "/api/item" $ do text "I upserted the item for you"
|
||||||
|
--Delete item
|
||||||
|
Web.Scotty.delete "/api/item/:id" $ do text "Deleted that pesky item"
|
||||||
|
--Get price for Zcash
|
||||||
|
get "/api/price" $ do
|
||||||
|
currency <- param "currency"
|
||||||
|
price <- liftIO $ run (findPrice currency)
|
||||||
|
case price of
|
||||||
|
Nothing -> do
|
||||||
|
status noContent204
|
||||||
|
Web.Scotty.json (object ["message" .= ("No price" :: String)])
|
||||||
|
Just p -> do
|
||||||
|
Web.Scotty.json
|
||||||
|
(object
|
||||||
|
[ "message" .= ("Price found!" :: String)
|
||||||
|
, "price" .= toJSON (parseZGoPrice p)
|
||||||
|
])
|
||||||
|
--Get all closed orders for the address
|
||||||
|
get "/api/allorders" $ do text "Here are the orders"
|
||||||
|
--Get order by id for receipts
|
||||||
|
get "/api/order/:id" $ do
|
||||||
|
oId <- param "id"
|
||||||
|
text (L.pack ("Here's the order" <> oId))
|
||||||
|
--Get order by session
|
||||||
|
get "/api/order" $ do
|
||||||
|
diff <- param "diff"
|
||||||
|
text (L.pack ("This is a diff order" <> diff))
|
||||||
|
--Upsert order
|
||||||
|
post "/api/order" $ do text "Upserted your order"
|
||||||
|
get "/api/test" $ do
|
||||||
|
q <- liftIO getZcashPrices
|
||||||
|
a <- liftIO $ mapM_ run (updatePrices (getResponseBody q))
|
||||||
|
text "Updated the DB!"
|
||||||
|
|
||||||
|
-- |Make a Zcash RPC call
|
||||||
|
makeZcashCall :: (MonadIO m, FromJSON a) => T.Text -> [T.Text] -> m (Response a)
|
||||||
|
makeZcashCall m p = do
|
||||||
|
let username = "zecwallet"
|
||||||
|
let password = "rdsxlun6v4a"
|
||||||
|
let payload =
|
||||||
|
RpcCall {jsonrpc = "1.0", callId = "test", method = m, parameters = p}
|
||||||
|
let myRequest =
|
||||||
|
setRequestBodyJSON payload $
|
||||||
|
setRequestPort 8232 $
|
||||||
|
setRequestBasicAuth username password $
|
||||||
|
setRequestMethod "POST" defaultRequest
|
||||||
|
httpJSON myRequest
|
||||||
|
|
||||||
|
-- |Timer for repeating actions
|
||||||
|
setInterval :: Int -> IO () -> IO ()
|
||||||
|
setInterval secs func = do
|
||||||
|
forever $ threadDelay (secs * 1000000) >> func
|
||||||
|
|
||||||
|
-- |Function to query the CoinGecko API for the price of Zcash
|
||||||
|
getZcashPrices :: IO (Response CoinGeckoPrices)
|
||||||
|
getZcashPrices = do
|
||||||
|
let priceRequest =
|
||||||
|
setRequestQueryString
|
||||||
|
[("ids", Just "zcash"), ("vs_currencies", Just "usd,gbp,eur,cad,aud")] $
|
||||||
|
setRequestPort 443 $
|
||||||
|
setRequestSecure True $
|
||||||
|
setRequestHost "api.coingecko.com" $
|
||||||
|
setRequestPath "/api/v3/simple/price" defaultRequest
|
||||||
|
httpJSON priceRequest
|
||||||
|
|
||||||
|
checkZcashPrices :: Pipe -> IO ()
|
||||||
|
checkZcashPrices p = do
|
||||||
|
q <- getZcashPrices
|
||||||
|
mapM_ (access p master "zgo") (updatePrices (getResponseBody q))
|
||||||
|
putStrLn "Got new prices"
|
68
package.yaml
Normal file
68
package.yaml
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
name: zgo-backend
|
||||||
|
version: 0.1.0.0
|
||||||
|
git: "https://gitlab.com/pitmutt/zgo-backend"
|
||||||
|
license: MIT
|
||||||
|
author: "Rene Vergara"
|
||||||
|
maintainer: "rene@vergara.network"
|
||||||
|
copyright: "Copyright (c) 2022 Vergara Technologies LLC"
|
||||||
|
|
||||||
|
extra-source-files:
|
||||||
|
- README.md
|
||||||
|
- ChangeLog.md
|
||||||
|
|
||||||
|
# Metadata used when publishing your package
|
||||||
|
synopsis: Haskell Back-end for the ZGo point-of-sale application
|
||||||
|
category: Web
|
||||||
|
|
||||||
|
# To avoid duplicated efforts in documentation and dealing with the
|
||||||
|
# complications of embedding Haddock markup inside cabal files, it is
|
||||||
|
# common to point users to the README.md file.
|
||||||
|
description: Please see the README on GitLab at <https://gitlab.com/pitmutt/zgo-backend#readme>
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
- base >= 4.7 && < 5
|
||||||
|
|
||||||
|
library:
|
||||||
|
source-dirs: src
|
||||||
|
dependencies:
|
||||||
|
- mongoDB
|
||||||
|
- time
|
||||||
|
- text
|
||||||
|
- unordered-containers
|
||||||
|
- bson
|
||||||
|
- aeson
|
||||||
|
|
||||||
|
executables:
|
||||||
|
zgo-backend-exe:
|
||||||
|
main: Main.hs
|
||||||
|
source-dirs: app
|
||||||
|
ghc-options:
|
||||||
|
- -threaded
|
||||||
|
- -rtsopts
|
||||||
|
- -with-rtsopts=-N
|
||||||
|
dependencies:
|
||||||
|
- zgo-backend
|
||||||
|
- base
|
||||||
|
- scotty
|
||||||
|
- wai-extra
|
||||||
|
- securemem
|
||||||
|
- text
|
||||||
|
- aeson
|
||||||
|
- mongoDB
|
||||||
|
- http-types
|
||||||
|
- http-conduit
|
||||||
|
- time
|
||||||
|
|
||||||
|
tests:
|
||||||
|
zgo-backend-test:
|
||||||
|
main: Spec.hs
|
||||||
|
source-dirs: test
|
||||||
|
ghc-options:
|
||||||
|
- -threaded
|
||||||
|
- -rtsopts
|
||||||
|
- -with-rtsopts=-N
|
||||||
|
- -main-is Spec
|
||||||
|
dependencies:
|
||||||
|
- zgo-backend
|
||||||
|
- hspec
|
||||||
|
- QuickCheck
|
236
src/ZGoBackend.hs
Normal file
236
src/ZGoBackend.hs
Normal file
|
@ -0,0 +1,236 @@
|
||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
{-# LANGUAGE DeriveGeneric #-}
|
||||||
|
{-# LANGUAGE DeriveAnyClass #-}
|
||||||
|
{-# LANGUAGE DuplicateRecordFields #-}
|
||||||
|
|
||||||
|
module ZGoBackend where
|
||||||
|
|
||||||
|
import Control.Monad
|
||||||
|
import Control.Monad.IO.Class
|
||||||
|
import Data.Aeson
|
||||||
|
import qualified Data.Bson as B
|
||||||
|
import Data.Char
|
||||||
|
import qualified Data.HashMap.Strict as HM
|
||||||
|
import qualified Data.Text as T
|
||||||
|
import Data.Time.Clock
|
||||||
|
import Database.MongoDB
|
||||||
|
import GHC.Generics
|
||||||
|
|
||||||
|
-- Models for API objects
|
||||||
|
-- | A type to model Zcash RPC calls
|
||||||
|
data RpcCall =
|
||||||
|
RpcCall
|
||||||
|
{ jsonrpc :: T.Text
|
||||||
|
, callId :: T.Text
|
||||||
|
, method :: T.Text
|
||||||
|
, parameters :: [T.Text]
|
||||||
|
}
|
||||||
|
deriving (Show, Generic)
|
||||||
|
|
||||||
|
instance ToJSON RpcCall where
|
||||||
|
toJSON (RpcCall j c m p) =
|
||||||
|
object ["jsonrpc" .= j, "id" .= c, "method" .= m, "params" .= p]
|
||||||
|
|
||||||
|
-- | A type to model the response of the Zcash RPC
|
||||||
|
data RpcResponse r =
|
||||||
|
MakeRpcResponse
|
||||||
|
{ err :: Maybe T.Text
|
||||||
|
, respId :: T.Text
|
||||||
|
, result :: r
|
||||||
|
}
|
||||||
|
deriving (Show, Generic, ToJSON)
|
||||||
|
|
||||||
|
instance (FromJSON r) => FromJSON (RpcResponse r) where
|
||||||
|
parseJSON (Object obj) =
|
||||||
|
MakeRpcResponse <$> obj .: "error" <*> obj .: "id" <*> obj .: "result"
|
||||||
|
parseJSON _ = mzero
|
||||||
|
|
||||||
|
-- | Type to model a (simplified) block of Zcash blockchain
|
||||||
|
data Block =
|
||||||
|
Block
|
||||||
|
{ height :: Integer
|
||||||
|
, size :: Integer
|
||||||
|
}
|
||||||
|
deriving (Show, Generic, ToJSON)
|
||||||
|
|
||||||
|
instance FromJSON Block where
|
||||||
|
parseJSON (Object obj) = Block <$> obj .: "height" <*> obj .: "size"
|
||||||
|
parseJSON _ = mzero
|
||||||
|
|
||||||
|
-- | Type to model a Zcash shielded transaction
|
||||||
|
data ZcashTx =
|
||||||
|
ZcashTx
|
||||||
|
{ txid :: T.Text
|
||||||
|
, amount :: Double
|
||||||
|
, amountZat :: Integer
|
||||||
|
, blockheight :: Integer
|
||||||
|
, blocktime :: Integer
|
||||||
|
, change :: Bool
|
||||||
|
, confirmations :: Integer
|
||||||
|
, memo :: String
|
||||||
|
}
|
||||||
|
deriving (Show, Generic)
|
||||||
|
|
||||||
|
instance FromJSON ZcashTx where
|
||||||
|
parseJSON =
|
||||||
|
withObject "ZcashTx" $ \obj -> do
|
||||||
|
t <- obj .: "txid"
|
||||||
|
a <- obj .: "amount"
|
||||||
|
aZ <- obj .: "amountZat"
|
||||||
|
bh <- obj .: "blockheight"
|
||||||
|
bt <- obj .: "blocktime"
|
||||||
|
c <- obj .: "change"
|
||||||
|
conf <- obj .: "confirmations"
|
||||||
|
m <- obj .: "memo"
|
||||||
|
pure $ ZcashTx t a aZ bh bt c conf (decodeHexText m)
|
||||||
|
|
||||||
|
instance ToJSON ZcashTx where
|
||||||
|
toJSON (ZcashTx t a aZ bh bt c conf m) =
|
||||||
|
object
|
||||||
|
[ "amount" .= a
|
||||||
|
, "amountZat" .= aZ
|
||||||
|
, "txid" .= t
|
||||||
|
, "blockheight" .= bh
|
||||||
|
, "blocktime" .= bt
|
||||||
|
, "change" .= c
|
||||||
|
, "confirmations" .= conf
|
||||||
|
, "memo" .= m
|
||||||
|
]
|
||||||
|
|
||||||
|
-- | Helper function to turn a hex-encoded memo strings to readable text
|
||||||
|
decodeHexText :: String -> String
|
||||||
|
decodeHexText hexText
|
||||||
|
| chunk == "00" = decodeHexText (drop 2 hexText)
|
||||||
|
| null chunk = ""
|
||||||
|
| otherwise = chr (read ("0x" <> chunk)) : decodeHexText (drop 2 hexText)
|
||||||
|
where
|
||||||
|
chunk = take 2 hexText
|
||||||
|
|
||||||
|
-- Types for the ZGo database documents
|
||||||
|
-- | Type to model a country for the database's country list
|
||||||
|
data Country =
|
||||||
|
Country
|
||||||
|
{ _id :: String
|
||||||
|
, name :: T.Text
|
||||||
|
, code :: T.Text
|
||||||
|
}
|
||||||
|
deriving (Eq, Show, Generic, ToJSON)
|
||||||
|
|
||||||
|
parseCountryBson :: B.Document -> Maybe Country
|
||||||
|
parseCountryBson d = do
|
||||||
|
i <- B.lookup "_id" d
|
||||||
|
n <- B.lookup "name" d
|
||||||
|
c <- B.lookup "code" d
|
||||||
|
pure $ Country (show (i :: B.ObjectId)) n c
|
||||||
|
|
||||||
|
data User =
|
||||||
|
User
|
||||||
|
{ _id :: String
|
||||||
|
, address :: T.Text
|
||||||
|
, session :: T.Text
|
||||||
|
, blocktime :: Integer
|
||||||
|
, pin :: T.Text
|
||||||
|
, validated :: Bool
|
||||||
|
, expired :: Bool
|
||||||
|
}
|
||||||
|
deriving (Eq, Show, Generic, ToJSON)
|
||||||
|
|
||||||
|
parseUserBson :: B.Document -> Maybe User
|
||||||
|
parseUserBson d = do
|
||||||
|
i <- B.lookup "_id" d
|
||||||
|
a <- B.lookup "address" d
|
||||||
|
s <- B.lookup "session" d
|
||||||
|
b <- B.lookup "blocktime" d
|
||||||
|
p <- B.lookup "pin" d
|
||||||
|
v <- B.lookup "validated" d
|
||||||
|
e <- B.lookup "expired" d
|
||||||
|
pure $ User (show (i :: B.ObjectId)) a s b p v e
|
||||||
|
|
||||||
|
-- | Type to model a ZGo transaction
|
||||||
|
data ZGoTx =
|
||||||
|
ZGoTx
|
||||||
|
{ _id :: String
|
||||||
|
, address :: T.Text
|
||||||
|
, session :: T.Text
|
||||||
|
, confirmations :: Integer
|
||||||
|
, amount :: Double
|
||||||
|
, txid :: T.Text
|
||||||
|
, memo :: T.Text
|
||||||
|
}
|
||||||
|
deriving (Eq, Show, Generic, ToJSON)
|
||||||
|
|
||||||
|
parseZGoTxBson :: B.Document -> Maybe ZGoTx
|
||||||
|
parseZGoTxBson d = do
|
||||||
|
i <- B.lookup "_id" d
|
||||||
|
a <- B.lookup "address" d
|
||||||
|
s <- B.lookup "session" d
|
||||||
|
c <- B.lookup "confirmations" d
|
||||||
|
am <- B.lookup "amount" d
|
||||||
|
t <- B.lookup "txid" d
|
||||||
|
m <- B.lookup "memo" d
|
||||||
|
pure $ ZGoTx (show (i :: B.ObjectId)) a s c am t m
|
||||||
|
|
||||||
|
-- |Type to model a price in the ZGo database
|
||||||
|
data ZGoPrice =
|
||||||
|
ZGoPrice
|
||||||
|
{ _id :: String
|
||||||
|
, currency :: T.Text
|
||||||
|
, price :: Double
|
||||||
|
, timestamp :: String
|
||||||
|
}
|
||||||
|
deriving (Eq, Show, Generic, ToJSON)
|
||||||
|
|
||||||
|
parseZGoPrice :: B.Document -> Maybe ZGoPrice
|
||||||
|
parseZGoPrice d = do
|
||||||
|
i <- B.lookup "_id" d
|
||||||
|
c <- B.lookup "currency" d
|
||||||
|
p <- B.lookup "price" d
|
||||||
|
t <- B.lookup "timestamp" d
|
||||||
|
pure $ ZGoPrice (show (i :: B.ObjectId)) c p (show (t :: B.Value))
|
||||||
|
|
||||||
|
-- | Type for the CoinGecko response
|
||||||
|
newtype CoinGeckoPrices =
|
||||||
|
CoinGeckoPrices [(T.Text, Double)]
|
||||||
|
deriving (Eq, Show)
|
||||||
|
|
||||||
|
instance FromJSON CoinGeckoPrices where
|
||||||
|
parseJSON =
|
||||||
|
withObject "CoinGeckoPrices" $ \obj -> do
|
||||||
|
z <- obj .: "zcash"
|
||||||
|
pure $ CoinGeckoPrices (HM.toList z)
|
||||||
|
|
||||||
|
-- Functions for querying the ZGo database
|
||||||
|
-- | Function to query DB for countries list
|
||||||
|
listCountries :: Action IO [Document]
|
||||||
|
listCountries = rest =<< find (select [] "countries")
|
||||||
|
|
||||||
|
-- | Function to query DB for unexpired user by session ID
|
||||||
|
findUser :: String -> Action IO (Maybe Document)
|
||||||
|
findUser s = findOne (select ["session" =: s, "expired" =: False] "users")
|
||||||
|
|
||||||
|
-- | Function to query DB for transactions with less than 10 confirmations
|
||||||
|
findPending :: String -> Action IO [Document]
|
||||||
|
findPending s =
|
||||||
|
rest =<<
|
||||||
|
find
|
||||||
|
(select
|
||||||
|
["session" =: s, "confirmations" =: ["$lt" =: (10 :: Integer)]]
|
||||||
|
"txs")
|
||||||
|
|
||||||
|
-- | Function to query DB for price by currency
|
||||||
|
findPrice :: String -> Action IO (Maybe Document)
|
||||||
|
findPrice c = findOne (select ["currency" =: c] "prices")
|
||||||
|
|
||||||
|
-- | Function to update prices in ZGo db
|
||||||
|
updatePrices :: CoinGeckoPrices -> [Action IO ()]
|
||||||
|
updatePrices (CoinGeckoPrices []) = []
|
||||||
|
updatePrices (CoinGeckoPrices x) = do
|
||||||
|
updateOnePrice (head x) : updatePrices (CoinGeckoPrices (tail x))
|
||||||
|
|
||||||
|
-- | Function to update one price in ZGo db
|
||||||
|
updateOnePrice :: (T.Text, Double) -> Action IO ()
|
||||||
|
updateOnePrice (c, v) = do
|
||||||
|
t <- liftIO getCurrentTime
|
||||||
|
upsert
|
||||||
|
(select ["currency" =: c] "prices")
|
||||||
|
["currency" =: c, "price" =: v, "timestamp" =: t]
|
67
stack.yaml
Normal file
67
stack.yaml
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
# This file was automatically generated by 'stack init'
|
||||||
|
#
|
||||||
|
# Some commonly used options have been documented as comments in this file.
|
||||||
|
# For advanced use and comprehensive documentation of the format, please see:
|
||||||
|
# https://docs.haskellstack.org/en/stable/yaml_configuration/
|
||||||
|
|
||||||
|
# Resolver to choose a 'specific' stackage snapshot or a compiler version.
|
||||||
|
# A snapshot resolver dictates the compiler version and the set of packages
|
||||||
|
# to be used for project dependencies. For example:
|
||||||
|
#
|
||||||
|
# resolver: lts-3.5
|
||||||
|
# resolver: nightly-2015-09-21
|
||||||
|
# resolver: ghc-7.10.2
|
||||||
|
#
|
||||||
|
# The location of a snapshot can be provided as a file or url. Stack assumes
|
||||||
|
# a snapshot provided as a file might change, whereas a url resource does not.
|
||||||
|
#
|
||||||
|
# resolver: ./custom-snapshot.yaml
|
||||||
|
# resolver: https://example.com/snapshots/2018-01-01.yaml
|
||||||
|
resolver:
|
||||||
|
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/19/4.yaml
|
||||||
|
|
||||||
|
# User packages to be built.
|
||||||
|
# Various formats can be used as shown in the example below.
|
||||||
|
#
|
||||||
|
# packages:
|
||||||
|
# - some-directory
|
||||||
|
# - https://example.com/foo/bar/baz-0.0.2.tar.gz
|
||||||
|
# subdirs:
|
||||||
|
# - auto-update
|
||||||
|
# - wai
|
||||||
|
packages:
|
||||||
|
- .
|
||||||
|
# Dependency packages to be pulled from upstream that are not in the resolver.
|
||||||
|
# These entries can reference officially published versions as well as
|
||||||
|
# forks / in-progress versions pinned to a git hash. For example:
|
||||||
|
#
|
||||||
|
# extra-deps:
|
||||||
|
# - acme-missiles-0.3
|
||||||
|
# - git: https://github.com/commercialhaskell/stack.git
|
||||||
|
# commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a
|
||||||
|
#
|
||||||
|
# extra-deps: []
|
||||||
|
|
||||||
|
# Override default flag values for local packages and extra-deps
|
||||||
|
# flags: {}
|
||||||
|
|
||||||
|
# Extra package databases containing global packages
|
||||||
|
# extra-package-dbs: []
|
||||||
|
|
||||||
|
# Control whether we use the GHC we find on the path
|
||||||
|
# system-ghc: true
|
||||||
|
#
|
||||||
|
# Require a specific version of stack, using version ranges
|
||||||
|
# require-stack-version: -any # Default
|
||||||
|
# require-stack-version: ">=2.7"
|
||||||
|
#
|
||||||
|
# Override the architecture used by stack, especially useful on Windows
|
||||||
|
# arch: i386
|
||||||
|
# arch: x86_64
|
||||||
|
#
|
||||||
|
# Extra directories used by stack for building
|
||||||
|
# extra-include-dirs: [/path/to/dir]
|
||||||
|
# extra-lib-dirs: [/path/to/dir]
|
||||||
|
#
|
||||||
|
# Allow a newer minor version of GHC than the snapshot specifies
|
||||||
|
# compiler-check: newer-minor
|
13
stack.yaml.lock
Normal file
13
stack.yaml.lock
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
# This file was autogenerated by Stack.
|
||||||
|
# You should not edit this file by hand.
|
||||||
|
# For more information, please see the documentation at:
|
||||||
|
# https://docs.haskellstack.org/en/stable/lock_files
|
||||||
|
|
||||||
|
packages: []
|
||||||
|
snapshots:
|
||||||
|
- completed:
|
||||||
|
size: 618683
|
||||||
|
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/19/4.yaml
|
||||||
|
sha256: d4ee004c46ba878d2f304f5d748d493057be579192a8d148527f3ba55c9df57f
|
||||||
|
original:
|
||||||
|
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/19/4.yaml
|
14
test/Spec.hs
Normal file
14
test/Spec.hs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
module Spec where
|
||||||
|
|
||||||
|
import Test.Hspec
|
||||||
|
import Test.QuickCheck
|
||||||
|
import ZGoBackend
|
||||||
|
|
||||||
|
main :: IO ()
|
||||||
|
main =
|
||||||
|
hspec $ do
|
||||||
|
describe "Decode Memo" $ do
|
||||||
|
it "converts to readable text" $ do
|
||||||
|
decodeHexText
|
||||||
|
"5a474f3a3a35643364343439342d353163302d343332642d383439352d3035303431393935376165610a5265706c792d546f3a0a7a733177366e6b616d65617a633567756a6d363933353073796c35773874677679617068756d73337077386579747a7935796d303878376476736b6d796b6b61746d777275636d67763365723865" `shouldBe`
|
||||||
|
"ZGO::5d3d4494-51c0-432d-8495-050419957aea\nReply-To:\nzs1w6nkameazc5gujm69350syl5w8tgvyaphums3pw8eytzy5ym08x7dvskmykkatmwrucmgv3er8e"
|
77
zgo-backend.cabal
Normal file
77
zgo-backend.cabal
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
cabal-version: 1.12
|
||||||
|
|
||||||
|
-- This file has been generated from package.yaml by hpack version 0.34.4.
|
||||||
|
--
|
||||||
|
-- see: https://github.com/sol/hpack
|
||||||
|
|
||||||
|
name: zgo-backend
|
||||||
|
version: 0.1.0.0
|
||||||
|
synopsis: Haskell Back-end for the ZGo point-of-sale application
|
||||||
|
description: Please see the README on GitLab at <https://gitlab.com/pitmutt/zgo-backend#readme>
|
||||||
|
category: Web
|
||||||
|
author: Rene Vergara
|
||||||
|
maintainer: rene@vergara.network
|
||||||
|
copyright: Copyright (c) 2022 Vergara Technologies LLC
|
||||||
|
license: MIT
|
||||||
|
license-file: LICENSE
|
||||||
|
build-type: Simple
|
||||||
|
extra-source-files:
|
||||||
|
README.md
|
||||||
|
ChangeLog.md
|
||||||
|
|
||||||
|
source-repository head
|
||||||
|
type: git
|
||||||
|
location: https://gitlab.com/pitmutt/zgo-backend
|
||||||
|
|
||||||
|
library
|
||||||
|
exposed-modules:
|
||||||
|
ZGoBackend
|
||||||
|
other-modules:
|
||||||
|
Paths_zgo_backend
|
||||||
|
hs-source-dirs:
|
||||||
|
src
|
||||||
|
build-depends:
|
||||||
|
aeson
|
||||||
|
, base >=4.7 && <5
|
||||||
|
, bson
|
||||||
|
, mongoDB
|
||||||
|
, text
|
||||||
|
, time
|
||||||
|
, unordered-containers
|
||||||
|
default-language: Haskell2010
|
||||||
|
|
||||||
|
executable zgo-backend-exe
|
||||||
|
main-is: Main.hs
|
||||||
|
other-modules:
|
||||||
|
Paths_zgo_backend
|
||||||
|
hs-source-dirs:
|
||||||
|
app
|
||||||
|
ghc-options: -threaded -rtsopts -with-rtsopts=-N
|
||||||
|
build-depends:
|
||||||
|
aeson
|
||||||
|
, base
|
||||||
|
, http-conduit
|
||||||
|
, http-types
|
||||||
|
, mongoDB
|
||||||
|
, scotty
|
||||||
|
, securemem
|
||||||
|
, text
|
||||||
|
, time
|
||||||
|
, wai-extra
|
||||||
|
, zgo-backend
|
||||||
|
default-language: Haskell2010
|
||||||
|
|
||||||
|
test-suite zgo-backend-test
|
||||||
|
type: exitcode-stdio-1.0
|
||||||
|
main-is: Spec.hs
|
||||||
|
other-modules:
|
||||||
|
Paths_zgo_backend
|
||||||
|
hs-source-dirs:
|
||||||
|
test
|
||||||
|
ghc-options: -threaded -rtsopts -with-rtsopts=-N -main-is Spec
|
||||||
|
build-depends:
|
||||||
|
QuickCheck
|
||||||
|
, base >=4.7 && <5
|
||||||
|
, hspec
|
||||||
|
, zgo-backend
|
||||||
|
default-language: Haskell2010
|
Loading…
Reference in a new issue