1
Fork 0
mirror of https://github.com/Airsequel/AirGQL.git synced 2025-07-22 16:14:04 +03:00
airgql/source/Server/Server.hs
prescientmoon 58e3f0bb7b Add insertonly endpoint to airgql server
I removed the `readonlyGqlPostHandler` thingy from airgql,
as it seemed to be a slightly worse version of the existing more
general gql post handler. The one difference is that it was trying
to use the readonly db directory, although the token already protected
the queries to reading only, so this didn't matter. Moreover, the
writeonly endpoint was also using the readonly directory, which seemed
like a mistake (I removed that handler as well in favour of passing the
`writeonly` access mode to the existing handler).

I also fixed a TODO, making it so the readonly airsequel-cli handler
takes the edition into account.
2024-12-07 16:47:58 +01:00

195 lines
5.1 KiB
Haskell

-- Necessary for servant-docs instances
{-# OPTIONS_GHC -Wno-orphans #-}
{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
module Server.Server (platformAPI, platformApp)
where
import Protolude (
Int,
Monoid (mempty),
Proxy (Proxy),
($),
)
import Protolude qualified as P
import Data.Aeson (Object, Value, object)
import Data.Aeson.KeyMap qualified as KeyMap
import Data.ByteString.Lazy qualified as BL
import Data.Text (Text)
import Data.Text qualified as T
import Network.Wai (Application)
import Network.Wai.Parse (
defaultParseRequestBodyOptions,
setMaxRequestFilesSize,
setMaxRequestNumFiles,
)
import Servant (Context (EmptyContext, (:.)), NoContent)
import Servant.API (
Capture,
Get,
JSON,
PlainText,
Post,
ReqBody,
(:<|>) ((:<|>)),
(:>),
)
import Servant.Docs (
DocCapture (DocCapture),
ToCapture (toCapture),
ToSample,
singleSample,
toSamples,
)
import Servant.HTML.Blaze (HTML)
import Servant.Multipart (
MultipartData (MultipartData),
MultipartOptions (generalOptions),
Tmp,
ToMultipartSample (toMultipartSamples),
defaultMultipartOptions,
)
import Servant.Server (Server)
import Servant.Server qualified as Servant
import Text.Blaze.Internal (MarkupM)
import AirGQL.Config (Config (maxDbSize), defaultConfig)
import AirGQL.ExternalAppContext (ExternalAppContext)
import AirGQL.Lib (SQLPost, insertOnly, readOnly, writeOnly)
import AirGQL.Servant.Database (
apiDatabaseSchemaGetHandler,
apiDatabaseVacuumPostHandler,
)
import AirGQL.Servant.GraphQL (
gqlQueryGetHandler,
gqlQueryPostHandler,
playgroundDefaultQueryHandler,
)
import AirGQL.Servant.SqlQuery (sqlQueryPostHandler)
import AirGQL.Types.SchemaConf (SchemaConf (accessMode, pragmaConf), defaultSchemaConf)
import AirGQL.Types.SqlQueryPostResult (SqlQueryPostResult)
import AirGQL.Types.Types (GQLPost)
{- FOURMOLU_DISABLE -}
-- ATTENTION: Order of handlers matters!
type PlatformAPI =
-- gqlQueryGetHandler
-- Redirect to GraphiQL playground
"graphql" :> Get '[HTML] NoContent
-- gqlQueryPostHandler
:<|> "graphql"
:> ReqBody '[JSON] GQLPost
:> Post '[JSON] Object
:<|> "readonly" :> "graphql"
:> ReqBody '[JSON] GQLPost
:> Post '[JSON] Object
:<|> "writeonly" :> "graphql"
:> ReqBody '[JSON] GQLPost
:> Post '[JSON] Object
:<|> "insertonly" :> "graphql"
:> ReqBody '[JSON] GQLPost
:> Post '[JSON] Object
-- playgroundDefaultQueryHandler
:<|> "playground" :> "default-query"
:> Get '[PlainText] Text
-- apiDatabaseSchemaGetHandler
:<|> "schema" :> Get '[PlainText] Text
-- apiDatabaseVacuumPostHandler
:<|> "vacuum" :> Post '[JSON] Object
-- sqlQueryPostHandler
:<|> "sql"
:> ReqBody '[JSON] SQLPost
:> Post '[JSON] SqlQueryPostResult
{- FOURMOLU_ENABLE -}
-- | Instances for automatic documentation generation via servant-docs
instance ToSample (MultipartData Tmp) where
toSamples _ = singleSample $ MultipartData mempty mempty
instance ToMultipartSample Tmp (MultipartData Tmp) where
toMultipartSamples _ = []
instance ToSample Value where
toSamples _ = singleSample $ object []
instance ToSample (KeyMap.KeyMap Value) where
toSamples _ = singleSample $ KeyMap.fromList []
instance ToSample (MarkupM ()) where
toSamples _ = singleSample mempty
instance ToSample BL.ByteString where
toSamples _ = singleSample mempty
instance ToSample Text where
toSamples _ = singleSample mempty
instance ToSample P.ByteString where
toSamples _ = singleSample mempty
instance ToCapture (Capture "readonlyId" Text) where
toCapture _ = DocCapture "readonlyId" "Read-only ID of the database"
instance ToCapture (Capture "dbId" Text) where
toCapture _ = DocCapture "dbId" "ID of the database to be served"
platformAPI :: Proxy PlatformAPI
platformAPI = Proxy
platformServer :: ExternalAppContext -> P.FilePath -> Server PlatformAPI
platformServer ctx filePath = do
let dbPath = T.pack filePath
gqlQueryGetHandler dbPath
:<|> gqlQueryPostHandler defaultSchemaConf dbPath
:<|> gqlQueryPostHandler defaultSchemaConf{accessMode = readOnly} dbPath
:<|> gqlQueryPostHandler defaultSchemaConf{accessMode = writeOnly} dbPath
:<|> gqlQueryPostHandler defaultSchemaConf{accessMode = insertOnly} dbPath
:<|> playgroundDefaultQueryHandler dbPath
:<|> apiDatabaseSchemaGetHandler ctx dbPath
:<|> apiDatabaseVacuumPostHandler dbPath
:<|> sqlQueryPostHandler defaultSchemaConf.pragmaConf dbPath
platformApp :: ExternalAppContext -> P.FilePath -> Application
platformApp ctx filePath = do
let
maxFileSizeInByte :: Int = defaultConfig.maxDbSize
multipartOpts :: MultipartOptions Tmp
multipartOpts =
(defaultMultipartOptions (Proxy :: Proxy Tmp))
{ generalOptions =
setMaxRequestNumFiles 1 $
setMaxRequestFilesSize
(P.fromIntegral maxFileSizeInByte)
defaultParseRequestBodyOptions
}
context :: Context '[MultipartOptions Tmp]
context =
multipartOpts :. EmptyContext
Servant.serveWithContext platformAPI context $ platformServer ctx filePath