2019-12-11 04:04:49 +03:00
|
|
|
{-# LANGUAGE CPP #-}
|
|
|
|
{-# LANGUAGE DataKinds #-}
|
2018-06-27 16:11:32 +03:00
|
|
|
|
|
|
|
module Hasura.Server.App where
|
|
|
|
|
2020-03-05 20:59:26 +03:00
|
|
|
import Control.Concurrent.MVar.Lifted
|
2019-05-16 10:45:29 +03:00
|
|
|
import Control.Exception (IOException, try)
|
2020-01-30 02:03:49 +03:00
|
|
|
import Control.Lens (view, _2)
|
2019-11-26 15:14:21 +03:00
|
|
|
import Control.Monad.Stateless
|
2020-03-20 09:46:45 +03:00
|
|
|
import Control.Monad.Trans.Control (MonadBaseControl)
|
2018-07-20 10:22:46 +03:00
|
|
|
import Data.Aeson hiding (json)
|
2019-12-03 22:18:10 +03:00
|
|
|
import Data.Either (isRight)
|
2019-07-10 15:01:52 +03:00
|
|
|
import Data.Int (Int64)
|
2018-11-23 16:02:46 +03:00
|
|
|
import Data.IORef
|
2020-04-09 10:41:24 +03:00
|
|
|
import Data.Time.Clock (UTCTime, getCurrentTime)
|
2019-07-10 15:01:52 +03:00
|
|
|
import Data.Time.Clock.POSIX (getPOSIXTime)
|
2019-05-16 10:45:29 +03:00
|
|
|
import Network.Mime (defaultMimeLookup)
|
2019-03-25 11:56:29 +03:00
|
|
|
import System.Exit (exitFailure)
|
2019-05-16 10:45:29 +03:00
|
|
|
import System.FilePath (joinPath, takeFileName)
|
2019-11-26 15:14:21 +03:00
|
|
|
import Web.Spock.Core ((<//>))
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2019-12-11 04:04:49 +03:00
|
|
|
import qualified Control.Concurrent.Async.Lifted.Safe as LA
|
2018-11-23 16:02:46 +03:00
|
|
|
import qualified Data.ByteString.Lazy as BL
|
2020-03-20 09:46:45 +03:00
|
|
|
import qualified Data.CaseInsensitive as CI
|
2018-11-23 16:02:46 +03:00
|
|
|
import qualified Data.HashMap.Strict as M
|
2019-02-28 16:53:03 +03:00
|
|
|
import qualified Data.HashSet as S
|
2018-11-23 16:02:46 +03:00
|
|
|
import qualified Data.Text as T
|
2019-11-26 15:14:21 +03:00
|
|
|
import qualified Database.PG.Query as Q
|
2018-07-20 10:22:46 +03:00
|
|
|
import qualified Network.HTTP.Client as HTTP
|
2019-11-26 15:14:21 +03:00
|
|
|
import qualified Network.HTTP.Types as HTTP
|
2018-11-23 16:02:46 +03:00
|
|
|
import qualified Network.Wai as Wai
|
|
|
|
import qualified Network.Wai.Handler.WebSockets as WS
|
|
|
|
import qualified Network.WebSockets as WS
|
2019-07-10 15:01:52 +03:00
|
|
|
import qualified System.Metrics as EKG
|
|
|
|
import qualified System.Metrics.Json as EKG
|
2018-11-23 16:02:46 +03:00
|
|
|
import qualified Text.Mustache as M
|
2019-11-26 15:14:21 +03:00
|
|
|
import qualified Web.Spock.Core as Spock
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2019-03-18 19:22:21 +03:00
|
|
|
import Hasura.EncJSON
|
2018-07-20 10:22:46 +03:00
|
|
|
import Hasura.Prelude hiding (get, put)
|
2019-08-14 02:34:37 +03:00
|
|
|
import Hasura.RQL.DDL.Schema
|
2018-06-27 16:11:32 +03:00
|
|
|
import Hasura.RQL.Types
|
2019-12-09 01:17:39 +03:00
|
|
|
import Hasura.RQL.Types.Run
|
2019-12-13 00:46:33 +03:00
|
|
|
import Hasura.Server.Auth (AuthMode (..), UserAuthentication (..))
|
2019-09-19 15:54:40 +03:00
|
|
|
import Hasura.Server.Compression
|
2019-06-11 16:29:03 +03:00
|
|
|
import Hasura.Server.Config (runGetConfig)
|
2019-06-04 13:10:28 +03:00
|
|
|
import Hasura.Server.Context
|
2019-02-14 08:58:38 +03:00
|
|
|
import Hasura.Server.Cors
|
2018-06-27 16:11:32 +03:00
|
|
|
import Hasura.Server.Init
|
|
|
|
import Hasura.Server.Logging
|
2019-02-14 08:58:38 +03:00
|
|
|
import Hasura.Server.Middleware (corsMiddleware)
|
2020-04-09 10:41:24 +03:00
|
|
|
import Hasura.Server.Migrate (migrateCatalog)
|
2018-06-27 16:11:32 +03:00
|
|
|
import Hasura.Server.Query
|
|
|
|
import Hasura.Server.Utils
|
2018-07-03 18:34:25 +03:00
|
|
|
import Hasura.Server.Version
|
2018-06-27 16:11:32 +03:00
|
|
|
import Hasura.SQL.Types
|
|
|
|
|
2019-11-26 15:14:21 +03:00
|
|
|
import qualified Hasura.GraphQL.Execute as E
|
|
|
|
import qualified Hasura.GraphQL.Execute.LiveQuery as EL
|
|
|
|
import qualified Hasura.GraphQL.Explain as GE
|
|
|
|
import qualified Hasura.GraphQL.Transport.HTTP as GH
|
|
|
|
import qualified Hasura.GraphQL.Transport.HTTP.Protocol as GH
|
|
|
|
import qualified Hasura.GraphQL.Transport.WebSocket as WS
|
|
|
|
import qualified Hasura.Logging as L
|
|
|
|
import qualified Hasura.Server.PGDump as PGD
|
2019-01-28 16:55:28 +03:00
|
|
|
|
2018-09-14 16:27:46 +03:00
|
|
|
|
2019-03-12 08:46:27 +03:00
|
|
|
data SchemaCacheRef
|
|
|
|
= SchemaCacheRef
|
2019-04-17 12:48:41 +03:00
|
|
|
{ _scrLock :: MVar ()
|
2020-03-05 20:59:26 +03:00
|
|
|
-- ^ The idea behind explicit locking here is to
|
|
|
|
--
|
|
|
|
-- 1. Allow maximum throughput for serving requests (/v1/graphql) (as each
|
|
|
|
-- request reads the current schemacache)
|
|
|
|
-- 2. We don't want to process more than one request at any point of time
|
2020-03-20 09:46:45 +03:00
|
|
|
-- which would modify the schema cache as such queries are expensive.
|
2020-03-05 20:59:26 +03:00
|
|
|
--
|
|
|
|
-- Another option is to consider removing this lock in place of `_scrCache ::
|
|
|
|
-- MVar ...` if it's okay or in fact correct to block during schema update in
|
|
|
|
-- e.g. _wseGCtxMap. Vamshi says: It is theoretically possible to have a
|
|
|
|
-- situation (in between building new schemacache and before writing it to
|
|
|
|
-- the IORef) where we serve a request with a stale schemacache but I guess
|
|
|
|
-- it is an okay trade-off to pay for a higher throughput (I remember doing a
|
2020-03-20 09:46:45 +03:00
|
|
|
-- bunch of benchmarks to test this hypothesis).
|
2019-11-20 21:21:30 +03:00
|
|
|
, _scrCache :: IORef (RebuildableSchemaCache Run, SchemaCacheVer)
|
2019-04-17 12:48:41 +03:00
|
|
|
, _scrOnChange :: IO ()
|
2020-03-05 20:59:26 +03:00
|
|
|
-- ^ an action to run when schemacache changes
|
2019-03-12 08:46:27 +03:00
|
|
|
}
|
|
|
|
|
2018-06-27 16:11:32 +03:00
|
|
|
data ServerCtx
|
|
|
|
= ServerCtx
|
2019-05-16 09:13:25 +03:00
|
|
|
{ scPGExecCtx :: !PGExecCtx
|
|
|
|
, scConnInfo :: !Q.ConnInfo
|
2019-11-26 15:14:21 +03:00
|
|
|
, scLogger :: !(L.Logger L.Hasura)
|
2019-05-16 09:13:25 +03:00
|
|
|
, scCacheRef :: !SchemaCacheRef
|
|
|
|
, scAuthMode :: !AuthMode
|
|
|
|
, scManager :: !HTTP.Manager
|
|
|
|
, scSQLGenCtx :: !SQLGenCtx
|
|
|
|
, scEnabledAPIs :: !(S.HashSet API)
|
|
|
|
, scInstanceId :: !InstanceId
|
|
|
|
, scPlanCache :: !E.PlanCache
|
|
|
|
, scLQState :: !EL.LiveQueriesState
|
|
|
|
, scEnableAllowlist :: !Bool
|
2019-07-10 15:01:52 +03:00
|
|
|
, scEkgStore :: !EKG.Store
|
2018-06-27 16:11:32 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
data HandlerCtx
|
|
|
|
= HandlerCtx
|
2019-05-16 09:13:25 +03:00
|
|
|
{ hcServerCtx :: !ServerCtx
|
|
|
|
, hcUser :: !UserInfo
|
2019-11-26 15:14:21 +03:00
|
|
|
, hcReqHeaders :: ![HTTP.Header]
|
2019-07-11 08:37:06 +03:00
|
|
|
, hcRequestId :: !RequestId
|
2018-06-27 16:11:32 +03:00
|
|
|
}
|
|
|
|
|
2019-11-26 15:14:21 +03:00
|
|
|
type Handler m = ExceptT QErr (ReaderT HandlerCtx m)
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2019-04-30 11:34:08 +03:00
|
|
|
data APIResp
|
2019-06-04 13:10:28 +03:00
|
|
|
= JSONResp !(HttpResponse EncJSON)
|
2019-07-11 08:37:06 +03:00
|
|
|
| RawResp !(HttpResponse BL.ByteString)
|
2019-04-30 11:34:08 +03:00
|
|
|
|
2019-11-26 15:14:21 +03:00
|
|
|
data APIHandler m a
|
|
|
|
= AHGet !(Handler m APIResp)
|
|
|
|
| AHPost !(a -> Handler m APIResp)
|
2019-07-11 08:37:06 +03:00
|
|
|
|
2019-11-26 15:14:21 +03:00
|
|
|
|
|
|
|
boolToText :: Bool -> T.Text
|
|
|
|
boolToText = bool "false" "true"
|
|
|
|
|
|
|
|
isAdminSecretSet :: AuthMode -> T.Text
|
|
|
|
isAdminSecretSet AMNoAuth = boolToText False
|
|
|
|
isAdminSecretSet _ = boolToText True
|
|
|
|
|
|
|
|
getSCFromRef :: (MonadIO m) => SchemaCacheRef -> m SchemaCache
|
2019-11-20 21:21:30 +03:00
|
|
|
getSCFromRef scRef = lastBuiltSchemaCache . fst <$> liftIO (readIORef $ _scrCache scRef)
|
2019-11-26 15:14:21 +03:00
|
|
|
|
2019-11-27 01:49:42 +03:00
|
|
|
logInconsObjs :: L.Logger L.Hasura -> [InconsistentMetadata] -> IO ()
|
2019-11-26 15:14:21 +03:00
|
|
|
logInconsObjs logger objs =
|
|
|
|
unless (null objs) $ L.unLogger logger $ mkInconsMetadataLog objs
|
|
|
|
|
|
|
|
withSCUpdate
|
2020-03-05 20:59:26 +03:00
|
|
|
:: (MonadIO m, MonadBaseControl IO m)
|
2019-11-20 21:21:30 +03:00
|
|
|
=> SchemaCacheRef -> L.Logger L.Hasura -> m (a, RebuildableSchemaCache Run) -> m a
|
2019-11-26 15:14:21 +03:00
|
|
|
withSCUpdate scr logger action = do
|
2020-03-05 20:59:26 +03:00
|
|
|
withMVarMasked lk $ \()-> do
|
|
|
|
(!res, !newSC) <- action
|
|
|
|
liftIO $ do
|
|
|
|
-- update schemacache in IO reference
|
2020-03-20 09:46:45 +03:00
|
|
|
modifyIORef' cacheRef $ \(_, prevVer) ->
|
2020-03-05 20:59:26 +03:00
|
|
|
let !newVer = incSchemaCacheVer prevVer
|
|
|
|
in (newSC, newVer)
|
|
|
|
-- log any inconsistent objects
|
|
|
|
logInconsObjs logger $ scInconsistentObjs $ lastBuiltSchemaCache newSC
|
|
|
|
onChange
|
|
|
|
return res
|
2019-11-26 15:14:21 +03:00
|
|
|
where
|
|
|
|
SchemaCacheRef lk cacheRef onChange = scr
|
|
|
|
|
|
|
|
mkGetHandler :: Handler m APIResp -> APIHandler m ()
|
2019-07-11 08:37:06 +03:00
|
|
|
mkGetHandler = AHGet
|
|
|
|
|
2019-11-26 15:14:21 +03:00
|
|
|
mkPostHandler :: (a -> Handler m APIResp) -> APIHandler m a
|
2019-07-11 08:37:06 +03:00
|
|
|
mkPostHandler = AHPost
|
|
|
|
|
2019-11-26 15:14:21 +03:00
|
|
|
mkAPIRespHandler :: (Functor m) => (a -> Handler m (HttpResponse EncJSON)) -> (a -> Handler m APIResp)
|
2019-07-11 08:37:06 +03:00
|
|
|
mkAPIRespHandler = (fmap . fmap) JSONResp
|
2019-04-30 11:34:08 +03:00
|
|
|
|
2019-02-28 16:53:03 +03:00
|
|
|
isMetadataEnabled :: ServerCtx -> Bool
|
|
|
|
isMetadataEnabled sc = S.member METADATA $ scEnabledAPIs sc
|
|
|
|
|
|
|
|
isGraphQLEnabled :: ServerCtx -> Bool
|
|
|
|
isGraphQLEnabled sc = S.member GRAPHQL $ scEnabledAPIs sc
|
|
|
|
|
2019-04-30 11:34:08 +03:00
|
|
|
isPGDumpEnabled :: ServerCtx -> Bool
|
|
|
|
isPGDumpEnabled sc = S.member PGDUMP $ scEnabledAPIs sc
|
|
|
|
|
2019-06-11 16:29:03 +03:00
|
|
|
isConfigEnabled :: ServerCtx -> Bool
|
|
|
|
isConfigEnabled sc = S.member CONFIG $ scEnabledAPIs sc
|
|
|
|
|
2019-04-30 08:15:23 +03:00
|
|
|
isDeveloperAPIEnabled :: ServerCtx -> Bool
|
|
|
|
isDeveloperAPIEnabled sc = S.member DEVELOPER $ scEnabledAPIs sc
|
|
|
|
|
2018-07-20 10:22:46 +03:00
|
|
|
-- {-# SCC parseBody #-}
|
2019-07-11 08:37:06 +03:00
|
|
|
parseBody :: (FromJSON a, MonadError QErr m) => BL.ByteString -> m a
|
|
|
|
parseBody reqBody =
|
|
|
|
case eitherDecode' reqBody of
|
|
|
|
Left e -> throw400 InvalidJSON (T.pack e)
|
|
|
|
Right jVal -> decodeValue jVal
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2019-11-26 15:14:21 +03:00
|
|
|
onlyAdmin :: (Monad m) => Handler m ()
|
2018-06-27 16:11:32 +03:00
|
|
|
onlyAdmin = do
|
2018-07-20 10:22:46 +03:00
|
|
|
uRole <- asks (userRole . hcUser)
|
2018-06-27 16:11:32 +03:00
|
|
|
when (uRole /= adminRole) $
|
|
|
|
throw400 AccessDenied "You have to be an admin to access this endpoint"
|
|
|
|
|
2019-11-26 15:14:21 +03:00
|
|
|
buildQCtx :: (MonadIO m) => Handler m QCtx
|
2018-06-27 16:11:32 +03:00
|
|
|
buildQCtx = do
|
|
|
|
scRef <- scCacheRef . hcServerCtx <$> ask
|
2018-07-20 10:22:46 +03:00
|
|
|
userInfo <- asks hcUser
|
2019-11-20 21:21:30 +03:00
|
|
|
cache <- getSCFromRef scRef
|
2019-04-17 19:29:39 +03:00
|
|
|
sqlGenCtx <- scSQLGenCtx . hcServerCtx <$> ask
|
|
|
|
return $ QCtx userInfo cache sqlGenCtx
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2020-03-20 09:46:45 +03:00
|
|
|
setHeader :: MonadIO m => HTTP.Header -> Spock.ActionT m ()
|
|
|
|
setHeader (headerName, headerValue) =
|
|
|
|
Spock.setHeader (bsToTxt $ CI.original headerName) (bsToTxt headerValue)
|
|
|
|
|
2019-11-26 15:14:21 +03:00
|
|
|
-- | Typeclass representing the metadata API authorization effect
|
|
|
|
class MetadataApiAuthorization m where
|
|
|
|
authorizeMetadataApi :: RQLQuery -> UserInfo -> Handler m ()
|
2019-06-04 13:10:28 +03:00
|
|
|
|
2018-06-27 16:11:32 +03:00
|
|
|
mkSpockAction
|
2020-01-23 00:55:55 +03:00
|
|
|
:: (HasVersion, MonadIO m, FromJSON a, ToJSON a, UserAuthentication m, HttpLog m)
|
2019-11-26 15:14:21 +03:00
|
|
|
=> ServerCtx
|
|
|
|
-> (Bool -> QErr -> Value)
|
|
|
|
-- ^ `QErr` JSON encoder function
|
2019-05-10 09:05:11 +03:00
|
|
|
-> (QErr -> QErr)
|
2019-11-26 15:14:21 +03:00
|
|
|
-- ^ `QErr` modifier
|
|
|
|
-> APIHandler m a
|
|
|
|
-> Spock.ActionT m ()
|
|
|
|
mkSpockAction serverCtx qErrEncoder qErrModifier apiHandler = do
|
|
|
|
req <- Spock.request
|
2020-01-16 04:56:57 +03:00
|
|
|
-- Bytes are actually read from the socket here. Time this.
|
|
|
|
(ioWaitTime, reqBody) <- withElapsedTime $ liftIO $ Wai.strictRequestBody req
|
2019-11-26 15:14:21 +03:00
|
|
|
let headers = Wai.requestHeaders req
|
|
|
|
authMode = scAuthMode serverCtx
|
|
|
|
manager = scManager serverCtx
|
|
|
|
|
|
|
|
requestId <- getRequestId headers
|
|
|
|
|
2019-12-11 04:04:49 +03:00
|
|
|
userInfoE <- fmap fst <$> lift (resolveUserInfo logger manager headers authMode)
|
2019-11-26 15:14:21 +03:00
|
|
|
userInfo <- either (logErrorAndResp Nothing requestId req (Left reqBody) False headers . qErrModifier)
|
2019-12-11 04:04:49 +03:00
|
|
|
return userInfoE
|
2019-11-26 15:14:21 +03:00
|
|
|
|
|
|
|
let handlerState = HandlerCtx serverCtx userInfo headers requestId
|
|
|
|
curRole = userRole userInfo
|
|
|
|
|
2020-01-16 04:56:57 +03:00
|
|
|
(serviceTime, (result, q)) <- withElapsedTime $ case apiHandler of
|
2019-11-26 15:14:21 +03:00
|
|
|
AHGet handler -> do
|
|
|
|
res <- lift $ runReaderT (runExceptT handler) handlerState
|
|
|
|
return (res, Nothing)
|
|
|
|
AHPost handler -> do
|
|
|
|
parsedReqE <- runExceptT $ parseBody reqBody
|
|
|
|
parsedReq <- either (logErrorAndResp (Just userInfo) requestId req (Left reqBody) (isAdmin curRole) headers . qErrModifier)
|
|
|
|
return parsedReqE
|
|
|
|
res <- lift $ runReaderT (runExceptT $ handler parsedReq) handlerState
|
|
|
|
return (res, Just parsedReq)
|
|
|
|
|
|
|
|
-- apply the error modifier
|
|
|
|
let modResult = fmapL qErrModifier result
|
|
|
|
|
|
|
|
-- log and return result
|
|
|
|
case modResult of
|
|
|
|
Left err -> let jErr = maybe (Left reqBody) (Right . toJSON) q
|
|
|
|
in logErrorAndResp (Just userInfo) requestId req jErr (isAdmin curRole) headers err
|
2020-01-06 22:09:27 +03:00
|
|
|
Right res -> logSuccessAndResp (Just userInfo) requestId req (fmap toJSON q) res (Just (ioWaitTime, serviceTime)) headers
|
2019-11-26 15:14:21 +03:00
|
|
|
|
|
|
|
where
|
|
|
|
logger = scLogger serverCtx
|
|
|
|
|
|
|
|
logErrorAndResp
|
|
|
|
:: (MonadIO m, HttpLog m)
|
|
|
|
=> Maybe UserInfo
|
|
|
|
-> RequestId
|
|
|
|
-> Wai.Request
|
|
|
|
-> Either BL.ByteString Value
|
|
|
|
-> Bool
|
|
|
|
-> [HTTP.Header]
|
|
|
|
-> QErr
|
|
|
|
-> Spock.ActionCtxT ctx m a
|
|
|
|
logErrorAndResp userInfo reqId req reqBody includeInternal headers qErr = do
|
|
|
|
lift $ logHttpError logger userInfo reqId req reqBody qErr headers
|
|
|
|
Spock.setStatus $ qeStatus qErr
|
|
|
|
Spock.json $ qErrEncoder includeInternal qErr
|
|
|
|
|
2019-12-17 00:02:05 +03:00
|
|
|
logSuccessAndResp userInfo reqId req reqBody result qTime reqHeaders =
|
2019-11-26 15:14:21 +03:00
|
|
|
case result of
|
|
|
|
JSONResp (HttpResponse encJson h) ->
|
2019-12-17 00:02:05 +03:00
|
|
|
possiblyCompressedLazyBytes userInfo reqId req reqBody qTime (encJToLBS encJson)
|
2020-03-20 09:46:45 +03:00
|
|
|
(pure jsonHeader <> h) reqHeaders
|
2019-11-26 15:14:21 +03:00
|
|
|
RawResp (HttpResponse rawBytes h) ->
|
2020-03-20 09:46:45 +03:00
|
|
|
possiblyCompressedLazyBytes userInfo reqId req reqBody qTime rawBytes h reqHeaders
|
2019-11-26 15:14:21 +03:00
|
|
|
|
2019-12-17 00:02:05 +03:00
|
|
|
possiblyCompressedLazyBytes userInfo reqId req reqBody qTime respBytes respHeaders reqHeaders = do
|
2019-11-26 15:14:21 +03:00
|
|
|
let (compressedResp, mEncodingHeader, mCompressionType) =
|
|
|
|
compressResponse (Wai.requestHeaders req) respBytes
|
|
|
|
encodingHeader = maybe [] pure mEncodingHeader
|
2020-03-20 09:46:45 +03:00
|
|
|
reqIdHeader = (requestIdHeader, txtToBs $ unRequestId reqId)
|
2019-11-26 15:14:21 +03:00
|
|
|
allRespHeaders = pure reqIdHeader <> encodingHeader <> respHeaders
|
2019-12-17 00:02:05 +03:00
|
|
|
lift $ logHttpSuccess logger userInfo reqId req reqBody respBytes compressedResp qTime mCompressionType reqHeaders
|
2020-03-20 09:46:45 +03:00
|
|
|
mapM_ setHeader allRespHeaders
|
2019-11-26 15:14:21 +03:00
|
|
|
Spock.lazyBytes compressedResp
|
|
|
|
|
2020-03-20 09:46:45 +03:00
|
|
|
v1QueryHandler
|
|
|
|
:: (HasVersion, MonadIO m, MonadBaseControl IO m, MetadataApiAuthorization m)
|
2020-03-05 20:59:26 +03:00
|
|
|
=> RQLQuery -> Handler m (HttpResponse EncJSON)
|
2018-06-27 16:11:32 +03:00
|
|
|
v1QueryHandler query = do
|
2019-11-26 15:14:21 +03:00
|
|
|
userInfo <- asks hcUser
|
|
|
|
authorizeMetadataApi query userInfo
|
2019-03-12 08:46:27 +03:00
|
|
|
scRef <- scCacheRef . hcServerCtx <$> ask
|
2019-04-29 09:22:48 +03:00
|
|
|
logger <- scLogger . hcServerCtx <$> ask
|
2019-07-08 08:51:41 +03:00
|
|
|
res <- bool (fst <$> dbAction) (withSCUpdate scRef logger dbAction) $
|
2020-01-09 02:19:02 +03:00
|
|
|
queryModifiesSchemaCache query
|
2020-03-20 09:46:45 +03:00
|
|
|
return $ HttpResponse res []
|
2018-06-27 16:11:32 +03:00
|
|
|
where
|
|
|
|
-- Hit postgres
|
|
|
|
dbAction = do
|
2018-07-20 10:22:46 +03:00
|
|
|
userInfo <- asks hcUser
|
2018-06-27 16:11:32 +03:00
|
|
|
scRef <- scCacheRef . hcServerCtx <$> ask
|
2019-04-17 12:48:41 +03:00
|
|
|
schemaCache <- fmap fst $ liftIO $ readIORef $ _scrCache scRef
|
2018-11-23 16:02:46 +03:00
|
|
|
httpMgr <- scManager . hcServerCtx <$> ask
|
2019-04-17 19:29:39 +03:00
|
|
|
sqlGenCtx <- scSQLGenCtx . hcServerCtx <$> ask
|
2019-04-17 12:48:41 +03:00
|
|
|
pgExecCtx <- scPGExecCtx . hcServerCtx <$> ask
|
2019-03-12 08:46:27 +03:00
|
|
|
instanceId <- scInstanceId . hcServerCtx <$> ask
|
2019-10-11 08:13:57 +03:00
|
|
|
runQuery pgExecCtx instanceId userInfo schemaCache httpMgr sqlGenCtx (SystemDefined False) query
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2020-01-23 00:55:55 +03:00
|
|
|
v1Alpha1GQHandler :: (HasVersion, MonadIO m) => GH.GQLBatchedReqs GH.GQLQueryText -> Handler m (HttpResponse EncJSON)
|
2018-06-27 16:11:32 +03:00
|
|
|
v1Alpha1GQHandler query = do
|
2018-07-20 10:22:46 +03:00
|
|
|
userInfo <- asks hcUser
|
2018-11-23 16:02:46 +03:00
|
|
|
reqHeaders <- asks hcReqHeaders
|
|
|
|
manager <- scManager . hcServerCtx <$> ask
|
2018-06-27 16:11:32 +03:00
|
|
|
scRef <- scCacheRef . hcServerCtx <$> ask
|
2019-04-17 12:48:41 +03:00
|
|
|
(sc, scVer) <- liftIO $ readIORef $ _scrCache scRef
|
|
|
|
pgExecCtx <- scPGExecCtx . hcServerCtx <$> ask
|
2019-04-17 19:29:39 +03:00
|
|
|
sqlGenCtx <- scSQLGenCtx . hcServerCtx <$> ask
|
2019-04-17 12:48:41 +03:00
|
|
|
planCache <- scPlanCache . hcServerCtx <$> ask
|
2019-07-11 08:37:06 +03:00
|
|
|
enableAL <- scEnableAllowlist . hcServerCtx <$> ask
|
|
|
|
logger <- scLogger . hcServerCtx <$> ask
|
|
|
|
requestId <- asks hcRequestId
|
|
|
|
let execCtx = E.ExecutionCtx logger sqlGenCtx pgExecCtx planCache
|
2019-11-20 21:21:30 +03:00
|
|
|
(lastBuiltSchemaCache sc) scVer manager enableAL
|
2019-12-20 19:04:02 +03:00
|
|
|
flip runReaderT execCtx $ GH.runGQBatched requestId userInfo reqHeaders query
|
2019-07-11 08:37:06 +03:00
|
|
|
|
|
|
|
v1GQHandler
|
2020-01-23 00:55:55 +03:00
|
|
|
:: (HasVersion, MonadIO m)
|
2019-12-20 19:04:02 +03:00
|
|
|
=> GH.GQLBatchedReqs GH.GQLQueryText
|
2019-11-26 15:14:21 +03:00
|
|
|
-> Handler m (HttpResponse EncJSON)
|
2019-05-10 09:05:11 +03:00
|
|
|
v1GQHandler = v1Alpha1GQHandler
|
|
|
|
|
2019-11-26 15:14:21 +03:00
|
|
|
gqlExplainHandler :: (MonadIO m) => GE.GQLExplain -> Handler m (HttpResponse EncJSON)
|
2018-10-19 05:15:28 +03:00
|
|
|
gqlExplainHandler query = do
|
|
|
|
onlyAdmin
|
|
|
|
scRef <- scCacheRef . hcServerCtx <$> ask
|
2019-11-20 21:21:30 +03:00
|
|
|
sc <- getSCFromRef scRef
|
2019-04-17 12:48:41 +03:00
|
|
|
pgExecCtx <- scPGExecCtx . hcServerCtx <$> ask
|
2019-04-17 19:29:39 +03:00
|
|
|
sqlGenCtx <- scSQLGenCtx . hcServerCtx <$> ask
|
2019-05-16 09:13:25 +03:00
|
|
|
enableAL <- scEnableAllowlist . hcServerCtx <$> ask
|
2019-06-04 13:10:28 +03:00
|
|
|
res <- GE.explainGQLQuery pgExecCtx sc sqlGenCtx enableAL query
|
2020-03-20 09:46:45 +03:00
|
|
|
return $ HttpResponse res []
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2019-11-26 15:14:21 +03:00
|
|
|
v1Alpha1PGDumpHandler :: (MonadIO m) => PGD.PGDumpReqBody -> Handler m APIResp
|
2019-04-30 11:34:08 +03:00
|
|
|
v1Alpha1PGDumpHandler b = do
|
|
|
|
onlyAdmin
|
|
|
|
ci <- scConnInfo . hcServerCtx <$> ask
|
|
|
|
output <- PGD.execPGDump b ci
|
2020-03-20 09:46:45 +03:00
|
|
|
return $ RawResp $ HttpResponse output [sqlHeader]
|
2019-05-16 10:45:29 +03:00
|
|
|
|
2019-11-26 15:14:21 +03:00
|
|
|
consoleAssetsHandler
|
|
|
|
:: (MonadIO m, HttpLog m)
|
|
|
|
=> L.Logger L.Hasura
|
|
|
|
-> Text
|
|
|
|
-> FilePath
|
|
|
|
-> Spock.ActionT m ()
|
2019-05-16 14:28:51 +03:00
|
|
|
consoleAssetsHandler logger dir path = do
|
2019-11-26 15:14:21 +03:00
|
|
|
req <- Spock.request
|
|
|
|
let reqHeaders = Wai.requestHeaders req
|
2019-05-16 10:45:29 +03:00
|
|
|
-- '..' in paths need not be handed as it is resolved in the url by
|
|
|
|
-- spock's routing. we get the expanded path.
|
|
|
|
eFileContents <- liftIO $ try $ BL.readFile $
|
|
|
|
joinPath [T.unpack dir, path]
|
2019-11-26 15:14:21 +03:00
|
|
|
either (onError reqHeaders) onSuccess eFileContents
|
2019-05-16 10:45:29 +03:00
|
|
|
where
|
2019-05-16 14:28:51 +03:00
|
|
|
onSuccess c = do
|
2020-03-20 09:46:45 +03:00
|
|
|
mapM_ setHeader headers
|
2019-11-26 15:14:21 +03:00
|
|
|
Spock.lazyBytes c
|
|
|
|
onError :: (MonadIO m, HttpLog m) => [HTTP.Header] -> IOException -> Spock.ActionT m ()
|
|
|
|
onError hdrs = raiseGenericApiError logger hdrs . err404 NotFound . T.pack . show
|
2019-05-16 10:45:29 +03:00
|
|
|
fn = T.pack $ takeFileName path
|
|
|
|
-- set gzip header if the filename ends with .gz
|
|
|
|
(fileName, encHeader) = case T.stripSuffix ".gz" fn of
|
|
|
|
Just v -> (v, [gzipHeader])
|
|
|
|
Nothing -> (fn, [])
|
2020-03-20 09:46:45 +03:00
|
|
|
mimeType = defaultMimeLookup fileName
|
2019-05-16 10:45:29 +03:00
|
|
|
headers = ("Content-Type", mimeType) : encHeader
|
|
|
|
|
2019-11-26 15:14:21 +03:00
|
|
|
class (Monad m) => ConsoleRenderer m where
|
2020-01-23 00:55:55 +03:00
|
|
|
renderConsole :: HasVersion => T.Text -> AuthMode -> Bool -> Maybe Text -> m (Either String Text)
|
2019-11-26 15:14:21 +03:00
|
|
|
|
|
|
|
renderHtmlTemplate :: M.Template -> Value -> Either String Text
|
|
|
|
renderHtmlTemplate template jVal =
|
2019-05-16 14:28:51 +03:00
|
|
|
bool (Left errMsg) (Right res) $ null errs
|
2019-05-16 10:45:29 +03:00
|
|
|
where
|
2019-11-26 15:14:21 +03:00
|
|
|
errMsg = "template rendering failed: " ++ show errs
|
|
|
|
(errs, res) = M.checkedSubstitute template jVal
|
|
|
|
|
|
|
|
newtype LegacyQueryParser m
|
2019-09-19 07:47:36 +03:00
|
|
|
= LegacyQueryParser
|
2019-11-26 15:14:21 +03:00
|
|
|
{ getLegacyQueryParser :: QualifiedTable -> Object -> Handler m RQLQueryV1 }
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2019-11-26 15:14:21 +03:00
|
|
|
queryParsers :: (Monad m) => M.HashMap T.Text (LegacyQueryParser m)
|
2018-06-27 16:11:32 +03:00
|
|
|
queryParsers =
|
|
|
|
M.fromList
|
2019-09-19 07:47:36 +03:00
|
|
|
[ ("select", mkLegacyQueryParser RQSelect)
|
|
|
|
, ("insert", mkLegacyQueryParser RQInsert)
|
|
|
|
, ("update", mkLegacyQueryParser RQUpdate)
|
|
|
|
, ("delete", mkLegacyQueryParser RQDelete)
|
|
|
|
, ("count", mkLegacyQueryParser RQCount)
|
2018-06-27 16:11:32 +03:00
|
|
|
]
|
|
|
|
where
|
2019-09-19 07:47:36 +03:00
|
|
|
mkLegacyQueryParser f =
|
|
|
|
LegacyQueryParser $ \qt obj -> do
|
2018-06-27 16:11:32 +03:00
|
|
|
let val = Object $ M.insert "table" (toJSON qt) obj
|
|
|
|
q <- decodeValue val
|
|
|
|
return $ f q
|
|
|
|
|
2019-11-26 15:14:21 +03:00
|
|
|
legacyQueryHandler
|
2020-03-05 20:59:26 +03:00
|
|
|
:: (HasVersion, MonadIO m, MonadBaseControl IO m, MetadataApiAuthorization m)
|
2019-11-26 15:14:21 +03:00
|
|
|
=> TableName -> T.Text -> Object
|
|
|
|
-> Handler m (HttpResponse EncJSON)
|
2019-07-11 08:37:06 +03:00
|
|
|
legacyQueryHandler tn queryType req =
|
2018-06-27 16:11:32 +03:00
|
|
|
case M.lookup queryType queryParsers of
|
2019-11-26 15:14:21 +03:00
|
|
|
Just queryParser -> getLegacyQueryParser queryParser qt req >>= v1QueryHandler . RQV1
|
2018-07-20 10:22:46 +03:00
|
|
|
Nothing -> throw404 "No such resource exists"
|
2018-06-27 16:11:32 +03:00
|
|
|
where
|
2019-01-25 06:31:54 +03:00
|
|
|
qt = QualifiedObject publicSchema tn
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2019-03-25 11:56:29 +03:00
|
|
|
initErrExit :: QErr -> IO a
|
|
|
|
initErrExit e = do
|
|
|
|
putStrLn $
|
|
|
|
"failed to build schema-cache because of inconsistent metadata: "
|
2020-02-13 20:38:23 +03:00
|
|
|
<> (show e)
|
2019-03-25 11:56:29 +03:00
|
|
|
exitFailure
|
2018-11-23 16:02:46 +03:00
|
|
|
|
2019-09-09 23:26:04 +03:00
|
|
|
data HasuraApp
|
|
|
|
= HasuraApp
|
|
|
|
{ _hapApplication :: !Wai.Application
|
|
|
|
, _hapSchemaRef :: !SchemaCacheRef
|
|
|
|
, _hapCacheBuildTime :: !(Maybe UTCTime)
|
|
|
|
, _hapShutdown :: !(IO ())
|
|
|
|
}
|
|
|
|
|
2018-07-20 10:22:46 +03:00
|
|
|
mkWaiApp
|
2019-12-11 04:04:49 +03:00
|
|
|
:: forall m.
|
2020-01-23 00:55:55 +03:00
|
|
|
( HasVersion
|
|
|
|
, MonadIO m
|
2019-11-26 15:14:21 +03:00
|
|
|
, MonadStateless IO m
|
|
|
|
, ConsoleRenderer m
|
|
|
|
, HttpLog m
|
|
|
|
, UserAuthentication m
|
|
|
|
, MetadataApiAuthorization m
|
2019-12-11 04:04:49 +03:00
|
|
|
, LA.Forall (LA.Pure m)
|
2019-11-26 15:14:21 +03:00
|
|
|
)
|
|
|
|
=> Q.TxIsolation
|
2019-11-28 12:03:14 +03:00
|
|
|
-> L.Logger L.Hasura
|
2019-05-16 09:13:25 +03:00
|
|
|
-> SQLGenCtx
|
|
|
|
-> Bool
|
|
|
|
-> Q.PGPool
|
|
|
|
-> Q.ConnInfo
|
|
|
|
-> HTTP.Manager
|
|
|
|
-> AuthMode
|
|
|
|
-> CorsConfig
|
|
|
|
-> Bool
|
2019-05-16 10:45:29 +03:00
|
|
|
-> Maybe Text
|
2019-05-16 09:13:25 +03:00
|
|
|
-> Bool
|
|
|
|
-> InstanceId
|
|
|
|
-> S.HashSet API
|
2019-08-28 15:19:21 +03:00
|
|
|
-> EL.LiveQueriesOptions
|
2019-11-25 20:12:23 +03:00
|
|
|
-> E.PlanCacheOptions
|
2019-11-26 15:14:21 +03:00
|
|
|
-> m HasuraApp
|
2019-11-28 12:03:14 +03:00
|
|
|
mkWaiApp isoLevel logger sqlGenCtx enableAL pool ci httpManager mode corsCfg enableConsole consoleAssetsDir
|
2019-11-26 15:14:21 +03:00
|
|
|
enableTelemetry instanceId apis lqOpts planCacheOptions = do
|
2019-07-11 08:37:06 +03:00
|
|
|
|
2020-04-09 10:41:24 +03:00
|
|
|
(planCache, schemaCacheRef, cacheBuiltTime) <- migrateAndInitialiseSchemaCache
|
|
|
|
let getSchemaCache = first lastBuiltSchemaCache <$> readIORef (_scrCache schemaCacheRef)
|
2018-07-20 10:22:46 +03:00
|
|
|
|
2019-04-17 12:48:41 +03:00
|
|
|
let corsPolicy = mkDefaultCorsPolicy corsCfg
|
2020-04-09 10:41:24 +03:00
|
|
|
pgExecCtx = PGExecCtx pool isoLevel
|
2019-11-26 15:14:21 +03:00
|
|
|
lqState <- liftIO $ EL.initLiveQueriesState lqOpts pgExecCtx
|
2019-11-20 21:21:30 +03:00
|
|
|
wsServerEnv <- WS.createWSServerEnv logger pgExecCtx lqState getSchemaCache httpManager
|
|
|
|
corsPolicy sqlGenCtx enableAL planCache
|
2019-11-26 15:14:21 +03:00
|
|
|
|
|
|
|
ekgStore <- liftIO EKG.newStore
|
|
|
|
|
2020-04-09 10:41:24 +03:00
|
|
|
let serverCtx = ServerCtx
|
2019-11-26 15:14:21 +03:00
|
|
|
{ scPGExecCtx = pgExecCtx
|
|
|
|
, scConnInfo = ci
|
|
|
|
, scLogger = logger
|
|
|
|
, scCacheRef = schemaCacheRef
|
|
|
|
, scAuthMode = mode
|
|
|
|
, scManager = httpManager
|
|
|
|
, scSQLGenCtx = sqlGenCtx
|
|
|
|
, scEnabledAPIs = apis
|
|
|
|
, scInstanceId = instanceId
|
|
|
|
, scPlanCache = planCache
|
|
|
|
, scLQState = lqState
|
|
|
|
, scEnableAllowlist = enableAL
|
|
|
|
, scEkgStore = ekgStore
|
|
|
|
}
|
2019-07-10 15:01:52 +03:00
|
|
|
|
|
|
|
when (isDeveloperAPIEnabled serverCtx) $ do
|
2019-11-26 15:14:21 +03:00
|
|
|
liftIO $ EKG.registerGcMetrics ekgStore
|
|
|
|
liftIO $ EKG.registerCounter "ekg.server_timestamp_ms" getTimeMs ekgStore
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2019-11-26 15:14:21 +03:00
|
|
|
spockApp <- liftWithStateless $ \lowerIO ->
|
|
|
|
Spock.spockAsApp $ Spock.spockT lowerIO $ httpApp corsCfg serverCtx enableConsole consoleAssetsDir enableTelemetry
|
2019-03-04 10:46:53 +03:00
|
|
|
|
2018-07-20 10:22:46 +03:00
|
|
|
let wsServerApp = WS.createWSServerApp mode wsServerEnv
|
2019-09-09 23:26:04 +03:00
|
|
|
stopWSServer = WS.stopWSServerApp wsServerEnv
|
|
|
|
|
2019-12-11 04:04:49 +03:00
|
|
|
waiApp <- liftWithStateless $ \lowerIO ->
|
|
|
|
pure $ WS.websocketsOr WS.defaultConnectionOptions (lowerIO . wsServerApp) spockApp
|
|
|
|
|
|
|
|
return $ HasuraApp waiApp schemaCacheRef cacheBuiltTime stopWSServer
|
2019-07-10 15:01:52 +03:00
|
|
|
where
|
|
|
|
getTimeMs :: IO Int64
|
|
|
|
getTimeMs = (round . (* 1000)) `fmap` getPOSIXTime
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2020-04-09 10:41:24 +03:00
|
|
|
migrateAndInitialiseSchemaCache :: m (E.PlanCache, SchemaCacheRef, Maybe UTCTime)
|
|
|
|
migrateAndInitialiseSchemaCache = do
|
|
|
|
let pgExecCtx = PGExecCtx pool Q.Serializable
|
|
|
|
adminRunCtx = RunCtx adminUserInfo httpManager sqlGenCtx
|
|
|
|
currentTime <- liftIO getCurrentTime
|
|
|
|
initialiseResult <- runExceptT $ peelRun adminRunCtx pgExecCtx Q.ReadWrite do
|
|
|
|
(,) <$> migrateCatalog currentTime <*> liftTx fetchLastUpdate
|
|
|
|
|
|
|
|
((migrationResult, schemaCache), lastUpdateEvent) <-
|
|
|
|
initialiseResult `onLeft` \err -> do
|
|
|
|
L.unLogger logger StartupLog
|
|
|
|
{ slLogLevel = L.LevelError
|
|
|
|
, slKind = "db_migrate"
|
|
|
|
, slInfo = toJSON err
|
|
|
|
}
|
|
|
|
liftIO exitFailure
|
|
|
|
L.unLogger logger migrationResult
|
|
|
|
|
|
|
|
cacheLock <- liftIO $ newMVar ()
|
|
|
|
cacheCell <- liftIO $ newIORef (schemaCache, initSchemaCacheVer)
|
|
|
|
planCache <- liftIO $ E.initPlanCache planCacheOptions
|
|
|
|
let cacheRef = SchemaCacheRef cacheLock cacheCell (E.clearPlanCache planCache)
|
|
|
|
|
|
|
|
pure (planCache, cacheRef, view _2 <$> lastUpdateEvent)
|
2019-11-26 15:14:21 +03:00
|
|
|
|
|
|
|
httpApp
|
2020-03-05 20:59:26 +03:00
|
|
|
:: (HasVersion, MonadIO m, MonadBaseControl IO m, ConsoleRenderer m, HttpLog m, UserAuthentication m, MetadataApiAuthorization m)
|
2019-11-26 15:14:21 +03:00
|
|
|
=> CorsConfig
|
|
|
|
-> ServerCtx
|
|
|
|
-> Bool
|
|
|
|
-> Maybe Text
|
|
|
|
-> Bool
|
|
|
|
-> Spock.SpockT m ()
|
2019-05-16 10:45:29 +03:00
|
|
|
httpApp corsCfg serverCtx enableConsole consoleAssetsDir enableTelemetry = do
|
2019-11-26 15:14:21 +03:00
|
|
|
|
2018-06-27 16:11:32 +03:00
|
|
|
-- cors middleware
|
2019-02-14 08:58:38 +03:00
|
|
|
unless (isCorsDisabled corsCfg) $
|
2019-11-26 15:14:21 +03:00
|
|
|
Spock.middleware $ corsMiddleware (mkDefaultCorsPolicy corsCfg)
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2018-06-29 14:05:09 +03:00
|
|
|
-- API Console and Root Dir
|
2019-07-08 08:51:41 +03:00
|
|
|
when (enableConsole && enableMetadata) serveApiConsole
|
2018-06-29 14:05:09 +03:00
|
|
|
|
2019-04-17 19:29:39 +03:00
|
|
|
-- Health check endpoint
|
2019-11-26 15:14:21 +03:00
|
|
|
Spock.get "healthz" $ do
|
2019-11-20 21:21:30 +03:00
|
|
|
sc <- getSCFromRef $ scCacheRef serverCtx
|
2019-12-03 22:18:10 +03:00
|
|
|
dbOk <- checkDbConnection
|
|
|
|
if dbOk
|
|
|
|
then Spock.setStatus HTTP.status200 >> (Spock.text $ if null (scInconsistentObjs sc)
|
|
|
|
then "OK"
|
|
|
|
else "WARN: inconsistent objects in schema")
|
|
|
|
else Spock.setStatus HTTP.status500 >> Spock.text "ERROR"
|
2019-04-17 19:29:39 +03:00
|
|
|
|
2019-11-26 15:14:21 +03:00
|
|
|
Spock.get "v1/version" $ do
|
2020-03-20 09:46:45 +03:00
|
|
|
setHeader jsonHeader
|
2019-11-26 15:14:21 +03:00
|
|
|
Spock.lazyBytes $ encode $ object [ "version" .= currentVersion ]
|
2018-07-03 18:34:25 +03:00
|
|
|
|
2019-02-28 16:53:03 +03:00
|
|
|
when enableMetadata $ do
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2020-01-22 13:50:49 +03:00
|
|
|
Spock.post "v1/graphql/explain" gqlExplainAction
|
|
|
|
|
|
|
|
Spock.post "v1alpha1/graphql/explain" gqlExplainAction
|
|
|
|
|
2019-11-26 15:14:21 +03:00
|
|
|
Spock.post "v1/query" $ spockAction encodeQErr id $
|
2019-07-11 08:37:06 +03:00
|
|
|
mkPostHandler $ mkAPIRespHandler v1QueryHandler
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2019-11-26 15:14:21 +03:00
|
|
|
Spock.post ("api/1/table" <//> Spock.var <//> Spock.var) $ \tableName queryType ->
|
|
|
|
mkSpockAction serverCtx encodeQErr id $ mkPostHandler $
|
2019-07-11 08:37:06 +03:00
|
|
|
mkAPIRespHandler $ legacyQueryHandler (TableName tableName) queryType
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2019-04-30 11:34:08 +03:00
|
|
|
when enablePGDump $
|
2020-01-22 13:50:49 +03:00
|
|
|
|
2019-11-26 15:14:21 +03:00
|
|
|
Spock.post "v1alpha1/pg_dump" $ spockAction encodeQErr id $
|
2019-07-11 08:37:06 +03:00
|
|
|
mkPostHandler v1Alpha1PGDumpHandler
|
2019-04-30 11:34:08 +03:00
|
|
|
|
2019-06-11 16:29:03 +03:00
|
|
|
when enableConfig $
|
2019-11-26 15:14:21 +03:00
|
|
|
Spock.get "v1alpha1/config" $ spockAction encodeQErr id $
|
2019-07-11 08:37:06 +03:00
|
|
|
mkGetHandler $ do
|
2019-06-11 16:29:03 +03:00
|
|
|
onlyAdmin
|
2020-04-06 07:53:58 +03:00
|
|
|
let res = encJFromJValue $ runGetConfig
|
|
|
|
(scAuthMode serverCtx)
|
|
|
|
(scEnableAllowlist serverCtx)
|
|
|
|
(EL._lqsOptions $ scLQState serverCtx)
|
|
|
|
|
2020-03-20 09:46:45 +03:00
|
|
|
return $ JSONResp $ HttpResponse res []
|
2019-06-11 16:29:03 +03:00
|
|
|
|
2019-02-28 16:53:03 +03:00
|
|
|
when enableGraphQL $ do
|
2019-11-26 15:14:21 +03:00
|
|
|
Spock.post "v1alpha1/graphql" $ spockAction GH.encodeGQErr id $
|
2019-07-11 08:37:06 +03:00
|
|
|
mkPostHandler $ mkAPIRespHandler v1Alpha1GQHandler
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2019-11-26 15:14:21 +03:00
|
|
|
Spock.post "v1/graphql" $ spockAction GH.encodeGQErr allMod200 $
|
2019-07-11 08:37:06 +03:00
|
|
|
mkPostHandler $ mkAPIRespHandler v1GQHandler
|
2019-05-10 09:05:11 +03:00
|
|
|
|
2019-04-30 08:15:23 +03:00
|
|
|
when (isDeveloperAPIEnabled serverCtx) $ do
|
2019-11-26 15:14:21 +03:00
|
|
|
Spock.get "dev/ekg" $ spockAction encodeQErr id $
|
2019-07-11 08:37:06 +03:00
|
|
|
mkGetHandler $ do
|
2019-07-10 15:01:52 +03:00
|
|
|
onlyAdmin
|
|
|
|
respJ <- liftIO $ EKG.sampleAll $ scEkgStore serverCtx
|
2020-03-20 09:46:45 +03:00
|
|
|
return $ JSONResp $ HttpResponse (encJFromJValue $ EKG.sampleToJson respJ) []
|
2019-11-26 15:14:21 +03:00
|
|
|
Spock.get "dev/plan_cache" $ spockAction encodeQErr id $
|
2019-07-11 08:37:06 +03:00
|
|
|
mkGetHandler $ do
|
2019-04-30 11:34:08 +03:00
|
|
|
onlyAdmin
|
|
|
|
respJ <- liftIO $ E.dumpPlanCache $ scPlanCache serverCtx
|
2020-03-20 09:46:45 +03:00
|
|
|
return $ JSONResp $ HttpResponse (encJFromJValue respJ) []
|
2019-11-26 15:14:21 +03:00
|
|
|
Spock.get "dev/subscriptions" $ spockAction encodeQErr id $
|
2019-07-11 08:37:06 +03:00
|
|
|
mkGetHandler $ do
|
2019-04-30 11:34:08 +03:00
|
|
|
onlyAdmin
|
|
|
|
respJ <- liftIO $ EL.dumpLiveQueriesState False $ scLQState serverCtx
|
2020-03-20 09:46:45 +03:00
|
|
|
return $ JSONResp $ HttpResponse (encJFromJValue respJ) []
|
2019-11-26 15:14:21 +03:00
|
|
|
Spock.get "dev/subscriptions/extended" $ spockAction encodeQErr id $
|
2019-07-11 08:37:06 +03:00
|
|
|
mkGetHandler $ do
|
2019-04-30 11:34:08 +03:00
|
|
|
onlyAdmin
|
|
|
|
respJ <- liftIO $ EL.dumpLiveQueriesState True $ scLQState serverCtx
|
2020-03-20 09:46:45 +03:00
|
|
|
return $ JSONResp $ HttpResponse (encJFromJValue respJ) []
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2019-11-26 15:14:21 +03:00
|
|
|
forM_ [Spock.GET, Spock.POST] $ \m -> Spock.hookAny m $ \_ -> do
|
|
|
|
req <- Spock.request
|
|
|
|
let headers = Wai.requestHeaders req
|
2018-07-06 08:16:42 +03:00
|
|
|
let qErr = err404 NotFound "resource does not exist"
|
2019-11-26 15:14:21 +03:00
|
|
|
raiseGenericApiError logger headers qErr
|
2018-06-27 16:11:32 +03:00
|
|
|
|
|
|
|
where
|
2019-05-16 14:28:51 +03:00
|
|
|
logger = scLogger serverCtx
|
2019-07-11 08:37:06 +03:00
|
|
|
|
2019-11-26 15:14:21 +03:00
|
|
|
spockAction
|
|
|
|
:: (FromJSON a, ToJSON a, MonadIO m, UserAuthentication m, HttpLog m)
|
|
|
|
=> (Bool -> QErr -> Value) -> (QErr -> QErr) -> APIHandler m a -> Spock.ActionT m ()
|
|
|
|
spockAction = mkSpockAction serverCtx
|
|
|
|
|
|
|
|
|
2019-05-10 09:05:11 +03:00
|
|
|
-- all graphql errors should be of type 200
|
2019-11-26 15:14:21 +03:00
|
|
|
allMod200 qe = qe { qeStatus = HTTP.status200 }
|
2019-05-10 09:05:11 +03:00
|
|
|
|
|
|
|
gqlExplainAction =
|
2019-11-26 15:14:21 +03:00
|
|
|
spockAction encodeQErr id $ mkPostHandler $
|
2019-07-11 08:37:06 +03:00
|
|
|
mkAPIRespHandler gqlExplainHandler
|
2019-05-10 09:05:11 +03:00
|
|
|
|
2019-02-28 16:53:03 +03:00
|
|
|
enableGraphQL = isGraphQLEnabled serverCtx
|
|
|
|
enableMetadata = isMetadataEnabled serverCtx
|
2019-04-30 11:34:08 +03:00
|
|
|
enablePGDump = isPGDumpEnabled serverCtx
|
2019-06-11 16:29:03 +03:00
|
|
|
enableConfig = isConfigEnabled serverCtx
|
2019-07-11 08:37:06 +03:00
|
|
|
|
2019-12-03 22:18:10 +03:00
|
|
|
checkDbConnection = do
|
|
|
|
e <- liftIO $ runExceptT $ runLazyTx' (scPGExecCtx serverCtx) select1Query
|
|
|
|
pure $ isRight e
|
|
|
|
where
|
|
|
|
select1Query :: (MonadTx m) => m Int
|
|
|
|
select1Query = liftTx $ runIdentity . Q.getRow <$> Q.withQE defaultTxErrorHandler
|
|
|
|
[Q.sql| SELECT 1 |] () False
|
|
|
|
|
2018-12-18 12:39:01 +03:00
|
|
|
serveApiConsole = do
|
2019-05-16 10:45:29 +03:00
|
|
|
-- redirect / to /console
|
2019-11-26 15:14:21 +03:00
|
|
|
Spock.get Spock.root $ Spock.redirect "console"
|
2019-05-16 10:45:29 +03:00
|
|
|
|
|
|
|
-- serve static files if consoleAssetsDir is set
|
|
|
|
onJust consoleAssetsDir $ \dir ->
|
2019-11-26 15:14:21 +03:00
|
|
|
Spock.get ("console/assets" <//> Spock.wildcard) $ \path ->
|
2019-05-16 14:28:51 +03:00
|
|
|
consoleAssetsHandler logger dir (T.unpack path)
|
2019-05-16 10:45:29 +03:00
|
|
|
|
|
|
|
-- serve console html
|
2019-11-26 15:14:21 +03:00
|
|
|
Spock.get ("console" <//> Spock.wildcard) $ \path -> do
|
|
|
|
req <- Spock.request
|
|
|
|
let headers = Wai.requestHeaders req
|
|
|
|
let authMode = scAuthMode serverCtx
|
|
|
|
consoleHtml <- lift $ renderConsole path authMode enableTelemetry consoleAssetsDir
|
|
|
|
either (raiseGenericApiError logger headers . err500 Unexpected . T.pack) Spock.html consoleHtml
|
|
|
|
|
|
|
|
raiseGenericApiError
|
|
|
|
:: (MonadIO m, HttpLog m)
|
|
|
|
=> L.Logger L.Hasura
|
|
|
|
-> [HTTP.Header]
|
|
|
|
-> QErr
|
|
|
|
-> Spock.ActionT m ()
|
|
|
|
raiseGenericApiError logger headers qErr = do
|
|
|
|
req <- Spock.request
|
|
|
|
reqBody <- liftIO $ Wai.strictRequestBody req
|
|
|
|
reqId <- getRequestId $ Wai.requestHeaders req
|
|
|
|
lift $ logHttpError logger Nothing reqId req (Left reqBody) qErr headers
|
2020-03-20 09:46:45 +03:00
|
|
|
setHeader jsonHeader
|
2019-11-26 15:14:21 +03:00
|
|
|
Spock.setStatus $ qeStatus qErr
|
|
|
|
Spock.lazyBytes $ encode qErr
|