Bootstrapped my haskell client
This commit is contained in:
parent
36cc33f9e9
commit
a775330b4f
12 changed files with 389 additions and 2 deletions
34
haskell/src/SpaceTraders/APIClient/Agent.hs
Normal file
34
haskell/src/SpaceTraders/APIClient/Agent.hs
Normal file
|
@ -0,0 +1,34 @@
|
|||
{-# LANGUAGE DeriveAnyClass #-}
|
||||
{-# LANGUAGE DeriveGeneric #-}
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
|
||||
module SpaceTraders.APIClient.Agent
|
||||
( RegisterMessage(..)
|
||||
, myAgent
|
||||
, register
|
||||
) where
|
||||
|
||||
import Data.Aeson
|
||||
import GHC.Generics
|
||||
import qualified Data.Text as T
|
||||
import Network.HTTP.Simple
|
||||
|
||||
import SpaceTraders.APIClient.Client
|
||||
import SpaceTraders.Model.Agent
|
||||
|
||||
myAgent :: T.Text -> IO (Either APIError Agent)
|
||||
myAgent t = send $ setRequestPath "/v2/my/agent"
|
||||
$ tokenReq t
|
||||
|
||||
data RegisterRequest = RegisterRequest { symbol :: T.Text
|
||||
, faction :: T.Text
|
||||
} deriving (ToJSON, Generic, Show)
|
||||
data RegisterMessage = RegisterMessage { token :: T.Text
|
||||
, agent :: Agent
|
||||
} deriving (FromJSON, Generic, Show)
|
||||
|
||||
register :: T.Text -> T.Text -> IO (Either APIError RegisterMessage)
|
||||
register s f = send $ setRequestPath "/v2/register"
|
||||
$ setRequestMethod "POST"
|
||||
$ setRequestBodyJSON RegisterRequest{symbol = s, faction = f}
|
||||
$ defaultReq
|
69
haskell/src/SpaceTraders/APIClient/Client.hs
Normal file
69
haskell/src/SpaceTraders/APIClient/Client.hs
Normal file
|
@ -0,0 +1,69 @@
|
|||
{-# LANGUAGE OverloadedStrings #-}
|
||||
|
||||
module SpaceTraders.APIClient.Client
|
||||
( APIError(..)
|
||||
, APIMessage(..)
|
||||
, defaultReq
|
||||
, fromJSONValue
|
||||
, send
|
||||
, tokenReq
|
||||
) where
|
||||
|
||||
import Control.Exception
|
||||
import Control.Monad
|
||||
import Data.Aeson
|
||||
import Data.Aeson.Types
|
||||
import qualified Data.Text as T
|
||||
import qualified Data.Text.Encoding as T
|
||||
import Network.HTTP.Simple
|
||||
import Network.HTTP.Types.Status
|
||||
|
||||
data APIError = APIError { apiErrorCode :: Int
|
||||
, apiErrorMessage :: T.Text
|
||||
} deriving Show
|
||||
instance Exception APIError
|
||||
instance FromJSON APIError where
|
||||
parseJSON (Object o) = do
|
||||
e <- o .: "error"
|
||||
APIError <$> e .: "code"
|
||||
<*> e .: "message"
|
||||
parseJSON _ = mzero
|
||||
|
||||
data APIMessage = APIMessage { data_ :: Value } deriving (Show)
|
||||
instance FromJSON APIMessage where
|
||||
parseJSON (Object o) = APIMessage <$> o .: "data"
|
||||
parseJSON _ = mzero
|
||||
|
||||
defaultReq :: Request
|
||||
defaultReq = setRequestHost "api.spacetraders.io"
|
||||
$ setRequestPort 443
|
||||
$ setRequestSecure True
|
||||
$ setRequestHeader "Content-Type" ["application/json"]
|
||||
$ defaultRequest
|
||||
|
||||
tokenReq :: T.Text -> Request
|
||||
tokenReq token = setRequestHeader "Authorization" [T.encodeUtf8 $ "Bearer " <> token]
|
||||
$ defaultReq
|
||||
|
||||
fromJSONValue :: FromJSON a => Value -> Either String a
|
||||
fromJSONValue = parseEither parseJSON
|
||||
|
||||
send :: FromJSON a => Request -> IO (Either APIError a)
|
||||
send request = do
|
||||
response <- httpLbs request
|
||||
let status = statusCode $ getResponseStatus response
|
||||
body = getResponseBody response
|
||||
if status >= 200 && status <= 299
|
||||
then case eitherDecode body of
|
||||
Left e -> return $ Left APIError{apiErrorCode = -1000, apiErrorMessage = T.pack $ concat ["Error decoding JSON APIMessage: ", e]}
|
||||
Right r -> case fromJSONValue (data_ r) of
|
||||
Left e -> return $ Left APIError{apiErrorCode = -1001, apiErrorMessage = T.pack $ concat ["Error decoding JSON message contents: ", e]}
|
||||
Right m -> return $ Right m
|
||||
else case eitherDecode body of
|
||||
Left e -> return $ Left APIError{apiErrorCode = -status, apiErrorMessage = T.pack $ concat ["Error decoding JSON APIError: ", e, ". Got HTTP body: ", show body]}
|
||||
Right e -> return $ Left e
|
||||
|
||||
--handleAPIError :: SomeException -> IO (Maybe RegisterMessage)
|
||||
--handleAPIError e = do
|
||||
-- print e
|
||||
-- return Nothing
|
56
haskell/src/SpaceTraders/Database.hs
Normal file
56
haskell/src/SpaceTraders/Database.hs
Normal file
|
@ -0,0 +1,56 @@
|
|||
{-# LANGUAGE OverloadedStrings #-}
|
||||
{-# LANGUAGE QuasiQuotes #-}
|
||||
|
||||
module SpaceTraders.Database
|
||||
( close
|
||||
, open
|
||||
) where
|
||||
|
||||
import Control.Exception
|
||||
import qualified Database.SQLite.Simple as S
|
||||
import Text.RawString.QQ
|
||||
|
||||
migrations :: [S.Query]
|
||||
migrations = [
|
||||
[r|CREATE TABLE schema_version (
|
||||
version INTEGER NOT NULL
|
||||
);
|
||||
|],
|
||||
[r|CREATE TABLE tokens (
|
||||
id INTEGER PRIMARY KEY,
|
||||
data TEXT NOT NULL
|
||||
);
|
||||
|],
|
||||
[r|CREATE TABLE agents (
|
||||
id INTEGER PRIMARY KEY,
|
||||
data TEXT NOT NULL
|
||||
);
|
||||
|],
|
||||
[r|CREATE TABLE contracts (
|
||||
id INTEGER PRIMARY KEY,
|
||||
data TEXT NOT NULL
|
||||
);
|
||||
|]]
|
||||
|
||||
close :: S.Connection -> IO ()
|
||||
close conn = S.close conn
|
||||
|
||||
open :: IO S.Connection
|
||||
open = do
|
||||
conn <- S.open "spacetraders.db"
|
||||
S.execute_ conn "PRAGMA foreign_keys = ON;"
|
||||
S.execute_ conn "PRAGMA journal_mode = WAL;"
|
||||
S.withTransaction conn $ do
|
||||
version <- getSchemaVersion conn `catch` defaultVersion
|
||||
mapM_ (S.execute_ conn) $ drop version migrations
|
||||
S.execute_ conn "DELETE FROM schema_version;"
|
||||
S.execute conn "INSERT INTO schema_version (version) VALUES (?);" (S.Only $ length migrations)
|
||||
return conn
|
||||
|
||||
getSchemaVersion :: S.Connection -> IO Int
|
||||
getSchemaVersion conn = do
|
||||
[[v]] <- S.query_ conn "SELECT version FROM schema_version;"
|
||||
return v
|
||||
|
||||
defaultVersion :: SomeException -> IO Int
|
||||
defaultVersion _ = return 0
|
14
haskell/src/SpaceTraders/Database/Agents.hs
Normal file
14
haskell/src/SpaceTraders/Database/Agents.hs
Normal file
|
@ -0,0 +1,14 @@
|
|||
{-# LANGUAGE OverloadedStrings #-}
|
||||
{-# LANGUAGE QuasiQuotes #-}
|
||||
|
||||
module SpaceTraders.Database.Agents
|
||||
( setAgent
|
||||
) where
|
||||
|
||||
import Data.Aeson
|
||||
import qualified Database.SQLite.Simple as S
|
||||
|
||||
import SpaceTraders.Model.Agent
|
||||
|
||||
setAgent :: S.Connection -> Agent -> IO ()
|
||||
setAgent conn agent = S.execute conn "INSERT INTO agents(data) VALUES (json(?));" (S.Only (encode agent))
|
18
haskell/src/SpaceTraders/Database/Tokens.hs
Normal file
18
haskell/src/SpaceTraders/Database/Tokens.hs
Normal file
|
@ -0,0 +1,18 @@
|
|||
{-# LANGUAGE OverloadedStrings #-}
|
||||
{-# LANGUAGE QuasiQuotes #-}
|
||||
|
||||
module SpaceTraders.Database.Tokens
|
||||
( getToken
|
||||
, setToken
|
||||
) where
|
||||
|
||||
import qualified Database.SQLite.Simple as S
|
||||
import qualified Data.Text as T
|
||||
|
||||
getToken :: S.Connection -> IO (T.Text)
|
||||
getToken conn = do
|
||||
[[token]] <- S.query_ conn "SELECT data FROM tokens;"
|
||||
return token
|
||||
|
||||
setToken :: S.Connection -> T.Text -> IO ()
|
||||
setToken conn value = S.execute conn "INSERT INTO tokens(data) VALUES (?);" (S.Only value)
|
22
haskell/src/SpaceTraders/Model/Agent.hs
Normal file
22
haskell/src/SpaceTraders/Model/Agent.hs
Normal file
|
@ -0,0 +1,22 @@
|
|||
{-# LANGUAGE DeriveAnyClass #-}
|
||||
{-# LANGUAGE DeriveGeneric #-}
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
|
||||
module SpaceTraders.Model.Agent
|
||||
( Agent(accountId, credits, headquarters)
|
||||
, agentSymbol
|
||||
) where
|
||||
|
||||
import Data.Aeson
|
||||
import GHC.Generics
|
||||
import qualified Data.Text as T
|
||||
|
||||
data Agent = Agent { accountId :: T.Text
|
||||
, credits :: Integer
|
||||
, headquarters :: T.Text
|
||||
, startingFaction :: T.Text
|
||||
, symbol :: T.Text
|
||||
} deriving (FromJSON, Generic, Show, ToJSON)
|
||||
|
||||
agentSymbol :: Agent -> T.Text
|
||||
agentSymbol = symbol
|
Loading…
Add table
Add a link
Reference in a new issue