I am trying to create a program to manage my Prosper investments for me as an intro to Haskell project. I created a post a few weeks ago for some of the simpler backend calculations: Recommend Next Note Purchase
Now I've started working on the web APIs in order to retrieve my account information from Prosper. Though it may not look it, the code below has already undergone some major refactoring to attempt to bring it to slightly readable.
This is my first attempt at any RESTful service interaction, JSON parsing, and usage of Lens. I have a few areas of this code I would really appreciate some feedback.
I feel like I really went overboard with the
let
bindings, but I couldn't figure out how to break up some absurdly long lines in another way.All my
maybeXXX
functions. I needed them to operate on the Maybe typeclass in the way I was expecting, but I feel like there is a more elegant use of Monads that could avoid these auxiliary functions.The creation of the
MResult
type was done just to parse the response body of the web API. There must be a way to use Lens to extract that field and parse it as a list ofNote
directly.
Any other feedback on the general structure or style of the code is greatly appreciated.
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DeriveGeneric #-}
import Prosper
import Network.Wreq
import Control.Lens
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy.Internal as BL
import qualified Data.Text as T
import Data.Text.Encoding
import Data.Aeson
import Data.Aeson.Lens
import GHC.Generics
data Note = Note {
loan_number :: Int,
prosper_rating :: T.Text
} deriving (Generic, Show)
instance FromJSON Note
data MResult = MResult {
result :: [Note]
} deriving (Generic, Show)
instance FromJSON MResult
-- String Constants
prosperAddress = "https://api.prosper.com/v1/"
authTarget = "security/oauth/token"
accountTarget = "accounts/prosper/"
notesTarget = "notes/"
clientID = "cicicicici"
clientSecret = "ssssssssssssssss"
userID = "uuuuuuuuuu"
password = "pppppppppp"
body :: BS.ByteString
body = strToBS $
"grant_type=password" ++
"&client_id=" ++ clientID ++
"&client_secret=" ++ clientSecret ++
"&username=" ++ userID ++
"&password=" ++ password
where strToBS = encodeUtf8 . T.pack
-- Retrives the OAuth2 token from the Prosper server
oauthToken :: IO T.Text
oauthToken = do
let opts = defaults & header "Accept" .~ ["application/json"]
& header "Content-type" .~ ["application/x-www- form-urlencoded"]
resp <- postWith opts (prosperAddress ++ authTarget) body
return $ resp ^. responseBody . key "access_token" . _String
-- Performs a GET request to the provided target using the provided OAuth token
getTarget target token = do
let tokenStr = encodeUtf8 $ T.append "bearer " token
let opts = defaults & header "Authorization" .~ [tokenStr]
getWith opts (prosperAddress ++ target)
-- Retrives the list of currently owned notes from Prosper
-- Note: The query responses are paginated to 25 results per response
getNotesList :: T.Text -> Int -> IO [T.Text]
getNotesList token offset = do
let target = notesTarget ++ "?offset=" ++ (show offset)
resp <- getTarget target token
let r = fmap result (decode $ resp ^. responseBody :: Maybe MResult)
let notes = fmap (map prosper_rating) r
if (maybeLength notes) /= 25 then
maybeReturn notes
else do
restOfNotes <- getNotesList token (offset+25)
return $ maybeAppend notes restOfNotes
maybeLength Nothing = 0
maybeLength (Just xs) = length xs
maybeReturn Nothing = return []
maybeReturn (Just xs) = return xs
maybeAppend :: Maybe [a] -> [a] -> [a]
maybeAppend Nothing xs = xs
maybeAppend (Just ys) xs = ys ++ xs
main = do
token <- oauthToken
notes <- getNotesList token 0
print $ recommendNote notes [0,0,0.2,0.2,0.25,0.30,0.05]