mirror of
https://github.com/Airsequel/AirGQL.git
synced 2025-06-18 17:21:15 +02:00
219 lines
4.7 KiB
Haskell
219 lines
4.7 KiB
Haskell
{-# LANGUAGE DeriveDataTypeable #-}
|
|
-- To look up git hash
|
|
{-# LANGUAGE TemplateHaskell #-}
|
|
|
|
-- Necessary for cmdArgs
|
|
{-# OPTIONS -Wno-partial-fields #-}
|
|
|
|
module Main (main) where
|
|
|
|
import Protolude (
|
|
Applicative (pure),
|
|
Eq ((==)),
|
|
FilePath,
|
|
IO,
|
|
Int,
|
|
Maybe (Just),
|
|
Semigroup ((<>)),
|
|
Show,
|
|
Text,
|
|
const,
|
|
putText,
|
|
repeat,
|
|
show,
|
|
($),
|
|
(&),
|
|
(||),
|
|
)
|
|
import Protolude qualified as P
|
|
|
|
import Data.Data (Data)
|
|
import Data.Text qualified as T
|
|
import Database.SQLite.Simple qualified as SS
|
|
import GitHash (giDirty, giTag, tGitInfoCwd)
|
|
import Network.HTTP.Client.MultipartFormData ()
|
|
import Network.Wai (Middleware)
|
|
import Network.Wai.Handler.Warp (
|
|
defaultSettings,
|
|
runSettings,
|
|
setOnException,
|
|
setPort,
|
|
)
|
|
import Network.Wai.Middleware.Cors (
|
|
cors,
|
|
corsMethods,
|
|
corsRequestHeaders,
|
|
simpleCorsResourcePolicy,
|
|
simpleMethods,
|
|
)
|
|
import System.Console.CmdArgs as CmdArgs (
|
|
Default (def),
|
|
args,
|
|
auto,
|
|
cmdArgs,
|
|
help,
|
|
modes,
|
|
program,
|
|
summary,
|
|
typ,
|
|
(&=),
|
|
)
|
|
|
|
import AirGQL.ExternalAppContext (getExternalAppContext)
|
|
import AirGQL.Utils (
|
|
getGraphiQLVersion,
|
|
getSqliteBinaryVersion,
|
|
getSqliteEmbeddedVersion,
|
|
withRetryConn,
|
|
)
|
|
import Server.Server (platformApp)
|
|
|
|
|
|
data Cli
|
|
= Help
|
|
| Version
|
|
| -- Start the AirGQL server
|
|
-- serving the GraphQL endpoint for the specified SQLite database
|
|
Serve
|
|
{ dbFilePath :: FilePath
|
|
-- TODO: , readOnly :: Bool
|
|
}
|
|
deriving (Show, Data)
|
|
|
|
|
|
cliHelp :: Cli
|
|
cliHelp =
|
|
Help
|
|
&= auto
|
|
|
|
|
|
cliVersion :: Cli
|
|
cliVersion = Version
|
|
|
|
|
|
cliServe :: Cli
|
|
cliServe =
|
|
Serve
|
|
{ dbFilePath = def &= typ "Path to database file" &= args
|
|
-- TODO: , readOnly = def
|
|
}
|
|
&= help "Serve database via GraphQL"
|
|
|
|
|
|
corsMiddleware :: Middleware
|
|
corsMiddleware =
|
|
let
|
|
policy =
|
|
simpleCorsResourcePolicy
|
|
{ corsRequestHeaders = ["Content-Type", "Authorization"]
|
|
, corsMethods = "PUT" : simpleMethods
|
|
}
|
|
in
|
|
cors (const $ Just policy)
|
|
|
|
|
|
-- | Imitates output from `git describe --always --dirty`
|
|
versionSlug :: Text
|
|
versionSlug =
|
|
T.pack $
|
|
giTag $$tGitInfoCwd
|
|
<> (if giDirty $$tGitInfoCwd then "-dirty" else "")
|
|
|
|
|
|
main :: IO ()
|
|
main = do
|
|
let
|
|
port :: Int = 4189
|
|
|
|
separatorLine = "\n" <> T.concat (P.take 80 $ repeat "=")
|
|
separatorLineThin = "\n" <> T.concat (P.take 80 $ repeat "-")
|
|
|
|
runWarp =
|
|
runSettings $
|
|
defaultSettings
|
|
& setPort port
|
|
& setOnException
|
|
( \_ exception -> do
|
|
let exceptionText :: Text = show exception
|
|
if (exceptionText == "Thread killed by timeout manager")
|
|
|| ( exceptionText
|
|
== "Warp: Client closed connection prematurely"
|
|
)
|
|
then pure ()
|
|
else do
|
|
putText exceptionText
|
|
)
|
|
|
|
buildBanner
|
|
:: Text
|
|
-> Text
|
|
-> Text
|
|
-> Text
|
|
-> Text
|
|
buildBanner
|
|
sqliteEmbeddedVersion
|
|
sqliteBinaryVersion
|
|
graphiQLVersion
|
|
baseUrl =
|
|
separatorLine
|
|
<> "\n\n"
|
|
<> "AirGQL Server\n"
|
|
<> separatorLineThin
|
|
<> "\n\n"
|
|
<> "Version:\t\t "
|
|
<> versionSlug
|
|
<> "\n\
|
|
\GraphQL URL:\t\t "
|
|
<> baseUrl
|
|
<> "/graphql"
|
|
<> "\n\
|
|
\\n\
|
|
\SQLite Embedded version: "
|
|
<> sqliteEmbeddedVersion
|
|
<> "\n\
|
|
\SQLite Binary version:\t "
|
|
<> sqliteBinaryVersion
|
|
<> "\n\
|
|
\GraphiQL version:\t "
|
|
<> graphiQLVersion
|
|
<> "\n"
|
|
<> separatorLine
|
|
<> "\n"
|
|
|
|
providedArgs <-
|
|
cmdArgs $
|
|
modes
|
|
[ cliHelp
|
|
, cliVersion
|
|
, cliServe
|
|
]
|
|
&= program "airgql"
|
|
&= summary (T.unpack versionSlug)
|
|
&= help "Automatic GraphQL API generation for SQLite databases"
|
|
|
|
case providedArgs of
|
|
Help ->
|
|
putText "Run `airgql --help` for detailed usage instructions"
|
|
----------
|
|
Version ->
|
|
putText versionSlug
|
|
----------
|
|
Serve{dbFilePath} -> do
|
|
withRetryConn dbFilePath $ \conn -> do
|
|
P.when (dbFilePath == "") $
|
|
P.die "ERROR: No database file path was specified"
|
|
|
|
let baseUrl :: Text = "http://localhost:" <> show port
|
|
ctx <- getExternalAppContext baseUrl
|
|
sqliteEmbeddedVersion <- getSqliteEmbeddedVersion conn
|
|
sqliteBinaryVersion <- getSqliteBinaryVersion ctx
|
|
graphiQLVersion <- getGraphiQLVersion
|
|
|
|
putText $
|
|
buildBanner
|
|
sqliteEmbeddedVersion
|
|
sqliteBinaryVersion
|
|
graphiQLVersion
|
|
baseUrl
|
|
|
|
runWarp $ corsMiddleware $ platformApp ctx dbFilePath
|