hledger/hledger-api/hledger-api.hs

276 lines
9.7 KiB
Haskell
Raw Normal View History

{-# LANGUAGE CPP #-}
2016-01-18 20:09:09 +03:00
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
2016-01-10 18:09:25 +03:00
module Main where
import Lens.Micro ((&), (.~))
import Control.Monad
import Data.Aeson
import qualified Data.ByteString.Lazy.Char8 as BL8
2016-01-10 19:32:42 +03:00
import Data.Decimal
import Data.Default
2016-01-10 19:58:40 +03:00
import qualified Data.Map as M
import Data.Proxy
import Data.String (fromString)
import Data.Swagger
import Data.Text hiding (map,reverse)
import Network.Wai as Wai
import Network.Wai.Handler.Warp as Warp
2016-09-22 21:56:38 +03:00
import Network.Wai.Middleware.RequestLogger
2016-01-10 18:09:25 +03:00
import Safe
import Servant
import Servant.Swagger
2016-01-10 18:09:25 +03:00
import System.Console.Docopt
import System.Environment (getArgs)
2016-01-10 19:58:40 +03:00
import System.Exit
import Text.Printf
import Hledger.Query
2016-01-10 18:09:25 +03:00
import Hledger.Cli hiding (Reader, version)
#ifdef VERSION
hledgerApiVersion = VERSION
#else
hledgerApiVersion = "dev build"
#endif
2016-01-10 18:09:25 +03:00
-- https://github.com/docopt/docopt.hs#readme
-- XXX VERSION is "X.Y" and the quotes appear in the output
2016-01-10 18:09:25 +03:00
doc :: Docopt
doc = [docopt|
hledger-api VERSION
2016-01-18 20:37:34 +03:00
Serves hledger data and reports as a JSON web API.
2016-01-10 18:09:25 +03:00
Usage:
hledger-api [options]
start API server
hledger-api --swagger
print API docs in Swagger 2.0 format
2016-01-10 18:09:25 +03:00
hledger-api --version
hledger-api -h|--help
2016-01-10 18:09:25 +03:00
Options:
-f --file FILE use a different input file
(default: $LEDGER_FILE or ~/.hledger.journal)
-d --static-dir DIR serve files from a different directory
(default: .)
--host IPADDR listen on this IP address (default: 127.0.0.1)
-p --port PORT listen on this TCP port (default: 8001)
--version show version
-h --help show usage
2016-01-10 18:09:25 +03:00
|]
swaggerSpec :: Swagger
swaggerSpec = toSwagger (Proxy :: Proxy HledgerApi)
& info.title .~ "hledger API"
& info.version .~ pack hledgerApiVersion
& info.description .~ Just "This is the API provided by hledger-api for reading hledger data"
& info.license .~ Just (License "GPLv3+" (Nothing))
2016-01-10 18:09:25 +03:00
main :: IO ()
main = do
args <- getArgs >>= parseArgsOrExit doc
when (isPresent args (shortOption 'h') || isPresent args (longOption "help")) $ exitWithUsage doc
when (isPresent args (longOption "version")) $ putStrLn ("hledger-api " ++ hledgerApiVersion) >> exitSuccess
when (isPresent args (longOption "swagger")) $ BL8.putStrLn (encode swaggerSpec) >> exitSuccess
let
defh = "127.0.0.1"
h = getArgWithDefault args defh (longOption "host")
defp = "8001"
2016-01-10 18:09:25 +03:00
p <- case readMay $ getArgWithDefault args defp (longOption "port") of
Nothing -> exitWithUsage doc
Just n -> return n
deff <- defaultJournalPath
let f = getArgWithDefault args deff (longOption "file")
requireJournalFileExists f
2016-01-18 20:37:34 +03:00
let
defd = "."
2016-01-18 20:37:34 +03:00
d = getArgWithDefault args defd (longOption "static-dir")
readJournalFile def f >>= either error' (serveApi h p d f)
2016-01-10 18:09:25 +03:00
serveApi :: String -> Int -> FilePath -> FilePath -> Journal -> IO ()
serveApi h p d f j = do
printf "Starting web api http://%s:%d/api/v1 for %s\n" h p f
printf "and file server http://%s:%d for %s/\n" h p d
printf "Press ctrl-c to quit\n"
let warpsettings = defaultSettings
& setHost (fromString h)
& setPort p
Warp.runSettings warpsettings $
2016-09-22 21:56:38 +03:00
logStdout $
hledgerApiApp d j
2016-01-10 18:09:25 +03:00
type HledgerApi =
"api" :> "v1" :>
(
"accountnames" :> Get '[JSON] [AccountName]
:<|> "transactions" :> Get '[JSON] [Transaction]
:<|> "prices" :> Get '[JSON] [MarketPrice]
:<|> "commodities" :> Get '[JSON] [CommoditySymbol]
:<|> "accounts" :> Get '[JSON] [Account]
:<|> "accounts" :> Capture "acct" AccountName :> Get '[JSON] AccountTransactionsReport
)
type HledgerSwaggerApi =
"swagger.json" :> Get '[JSON] Swagger
:<|> HledgerApi
type HledgerSwaggerFilesApi =
HledgerSwaggerApi
:<|> Raw
2016-01-18 20:37:34 +03:00
hledgerApiApp :: FilePath -> Journal -> Wai.Application
hledgerApiApp staticdir j = Servant.serve api server
where
api :: Proxy HledgerSwaggerFilesApi
api = Proxy
server :: Server HledgerSwaggerFilesApi
server =
(
return swaggerSpec
--
:<|> accountnamesH
:<|> transactionsH
:<|> pricesH
:<|> commoditiesH
:<|> accountsH
:<|> accounttransactionsH
)
--
:<|> serveDirectoryFileServer staticdir
where
accountnamesH = return $ journalAccountNames j
transactionsH = return $ jtxns j
pricesH = return $ map priceDirectiveToMarketPrice $ jpricedirectives j
commoditiesH = return $ (M.keys . jinferredcommodities) j
accountsH = return $ ledgerTopAccounts $ ledgerFromJournal Hledger.Query.Any j
accounttransactionsH (a::AccountName) = do
-- d <- liftIO getCurrentDay
let
ropts = defreportopts
-- ropts' = ropts {depth_=Nothing
-- ,balancetype_=HistoricalBalance
-- }
q = Hledger.Query.Any --filterQuery (not . queryIsDepth) $ queryFromOpts d ropts'
thisacctq = Acct $ accountNameToAccountRegex a -- includes subs
return $ accountTransactionsReport ropts j q thisacctq
2016-01-10 19:32:42 +03:00
-- avoiding https://github.com/bos/aeson/issues/290 - no longer needed ?
--instance ToJSON Status where toJSON = genericToJSON defaultOptions -- avoiding https://github.com/bos/aeson/issues/290
--instance ToJSON GenericSourcePos where toJSON = genericToJSON defaultOptions
--instance ToJSON Decimal where toJSON = toJSON . show
--instance ToJSON Amount where toJSON = genericToJSON defaultOptions
--instance ToJSON AmountStyle where toJSON = genericToJSON defaultOptions
--instance ToJSON Side where toJSON = genericToJSON defaultOptions
--instance ToJSON DigitGroupStyle where toJSON = genericToJSON defaultOptions
--instance ToJSON MixedAmount where toJSON = genericToJSON defaultOptions
--instance ToJSON BalanceAssertion where toJSON = genericToJSON defaultOptions
--instance ToJSON AmountPrice where toJSON = genericToJSON defaultOptions
--instance ToJSON MarketPrice where toJSON = genericToJSON defaultOptions
--instance ToJSON PostingType where toJSON = genericToJSON defaultOptions
--instance ToJSON Posting where
-- toJSON Posting{..} =
-- object
-- ["pdate" .= toJSON pdate
-- ,"pdate2" .= toJSON pdate2
-- ,"pstatus" .= toJSON pstatus
-- ,"paccount" .= toJSON paccount
-- ,"pamount" .= toJSON pamount
-- ,"pcomment" .= toJSON pcomment
-- ,"ptype" .= toJSON ptype
-- ,"ptags" .= toJSON ptags
-- ,"pbalanceassertion" .= toJSON pbalanceassertion
-- ,"ptransactionidx" .= toJSON (maybe "" (show.tindex) ptransaction)
-- ]
--instance ToJSON Transaction where toJSON = genericToJSON defaultOptions
--instance ToJSON Account where
-- toJSON a =
-- object
-- ["aname" .= toJSON (aname a)
-- ,"aebalance" .= toJSON (aebalance a)
-- ,"aibalance" .= toJSON (aibalance a)
-- ,"anumpostings" .= toJSON (anumpostings a)
-- ,"aboring" .= toJSON (aboring a)
-- ,"aparentname" .= toJSON (maybe "" aname $ aparent a)
-- ,"asubs" .= toJSON (map toJSON $ asubs a)
-- ]
-- Convert things to JSON for serving to clients
instance ToJSON Status
instance ToJSON GenericSourcePos
instance ToJSON Decimal where toJSON = toJSON . show
instance ToJSON Amount
instance ToJSON AmountStyle
instance ToJSON Side
instance ToJSON DigitGroupStyle
instance ToJSON MixedAmount
instance ToJSON BalanceAssertion
instance ToJSON AmountPrice
instance ToJSON MarketPrice
instance ToJSON PostingType
2016-01-17 22:07:26 +03:00
instance ToJSON Posting where
toJSON Posting{..} =
object
["pdate" .= toJSON pdate
,"pdate2" .= toJSON pdate2
,"pstatus" .= toJSON pstatus
,"paccount" .= toJSON paccount
,"pamount" .= toJSON pamount
,"pcomment" .= toJSON pcomment
,"ptype" .= toJSON ptype
,"ptags" .= toJSON ptags
,"pbalanceassertion" .= toJSON pbalanceassertion
,"ptransactionidx" .= toJSON (maybe "" (show.tindex) ptransaction)
]
instance ToJSON Transaction
2016-01-17 22:07:26 +03:00
instance ToJSON Account where
toJSON a =
object
["aname" .= toJSON (aname a)
,"aebalance" .= toJSON (aebalance a)
,"aibalance" .= toJSON (aibalance a)
,"anumpostings" .= toJSON (anumpostings a)
,"aboring" .= toJSON (aboring a)
,"aparentname" .= toJSON (maybe "" aname $ aparent a)
,"asubs" .= toJSON (map toJSON $ asubs a)
]
-- convert things to Schema for swagger API description
instance ToSchema Status
instance ToSchema GenericSourcePos
instance ToSchema Decimal
where
declareNamedSchema _proxy = pure $ NamedSchema (Just "Decimal") schema
where
schema = mempty
& type_ .~ SwaggerNumber
& example .~ Just (toJSON (100 :: Decimal))
instance ToSchema Amount
instance ToSchema AmountStyle
instance ToSchema Side
instance ToSchema DigitGroupStyle
instance ToSchema MixedAmount
instance ToSchema BalanceAssertion
instance ToSchema AmountPrice
#if MIN_VERSION_swagger2(2,1,5)
where declareNamedSchema = genericDeclareNamedSchemaUnrestricted defaultSchemaOptions
#endif
instance ToSchema MarketPrice
instance ToSchema PostingType
instance ToSchema Posting
instance ToSchema Transaction
instance ToSchema Account
2019-01-25 01:39:38 +03:00
instance ToSchema AccountDeclarationInfo
-- instance ToSchema AccountTransactionsReport