{-| Module : Gargantext.API.Server Description : REST API declaration Copyright : (c) CNRS, 2017-Present License : AGPL + CECILL v3 Maintainer : team@gargantext.org Stability : experimental Portability : POSIX -} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-} module Gargantext.API.Server where import Control.Monad.Catch (catch, throwM) import Data.ByteString.Lazy qualified as BL import Data.Text qualified as T import Data.Text.Encoding qualified as TE import Data.Version (showVersion) import Gargantext.API.Admin.Auth (auth, forgotPassword, forgotPasswordAsync) import Gargantext.API.Admin.Auth.Types (AuthContext) import Gargantext.API.Admin.EnvTypes (Env) import Gargantext.API.Admin.FrontEnd (frontEndServer) import Gargantext.API.Auth.PolicyCheck () import Gargantext.API.Errors import Gargantext.API.GraphQL qualified as GraphQL import Gargantext.API.Prelude (GargM, GargServer) import Gargantext.API.Public qualified as Public import Gargantext.API.Routes (API, GargVersion, GargAPI) import Gargantext.API.Swagger (swaggerDoc) import Gargantext.API.ThrowAll (serverPrivateGargAPI) import Gargantext.Database.Prelude (hasConfig) import Gargantext.Prelude hiding (Handler, catch) import Gargantext.Prelude.Config (gc_url_backend_api) import Gargantext.System.Logging (logLocM, LogLevel(..)) import Paths_gargantext qualified as PG -- cabal magic build module import Servant import Servant.Swagger.UI (swaggerSchemaUIServer) serverGargAPI :: Text -> ServerT GargAPI (GargM Env BackendInternalError) serverGargAPI baseUrl -- orchestrator = auth :<|> forgotPassword :<|> forgotPasswordAsync :<|> gargVersion :<|> serverPrivateGargAPI :<|> Public.api baseUrl -- :<|> orchestrator where gargVersion :: GargServer GargVersion gargVersion = pure (cs $ showVersion PG.version) -- | Server declarations server :: Env -> IO (Server API) server env = do -- orchestrator <- scrapyOrchestrator env pure $ \errScheme -> swaggerSchemaUIServer swaggerDoc :<|> hoistServerWithContext (Proxy :: Proxy GargAPI) (Proxy :: Proxy AuthContext) (transformJSON errScheme) (serverGargAPI (env ^. hasConfig . gc_url_backend_api)) :<|> hoistServerWithContext (Proxy :: Proxy GraphQL.API) (Proxy :: Proxy AuthContext) (transformJSONGQL errScheme) GraphQL.api :<|> frontEndServer where transformJSON :: forall a. GargErrorScheme -> GargM Env BackendInternalError a -> Handler a transformJSON GES_old = Handler . withExceptT showAsServantJSONErr . (`runReaderT` env) . logPanicErrors transformJSON GES_new = Handler . withExceptT (frontendErrorToServerError . backendErrorToFrontendError) . (`runReaderT` env) . handlePanicErrors transformJSONGQL :: forall a. GargErrorScheme -> GargM Env BackendInternalError a -> Handler a transformJSONGQL GES_old = Handler . withExceptT showAsServantJSONErr . (`runReaderT` env) . logPanicErrors transformJSONGQL GES_new = Handler . withExceptT (frontendErrorToGQLServerError . backendErrorToFrontendError) . (`runReaderT` env) . handlePanicErrors handlePanicErrors :: GargM Env BackendInternalError a -> GargM Env BackendInternalError a handlePanicErrors h = h `catch` handleSomeException where handleSomeException :: SomeException -> GargM Env BackendInternalError a handleSomeException se | Just ex@(WithStacktrace _ (_ :: UnexpectedPanic)) <- fromException se = do $(logLocM) ERROR $ T.pack $ displayException ex ReaderT $ \_ -> ExceptT $ pure $ Left $ InternalUnexpectedError se | Just (ber :: BackendInternalError) <- fromException se = throwError ber -- re-throw the uncaught exception via the 'MonadError' instance | otherwise = throwM se -- re-throw the uncaught exception. -- | Old compat-shim for the old error format, it just logs the exception properly -- but otherwise rethrows it /without/ the stacktrace (to not leak internal details). logPanicErrors :: GargM Env BackendInternalError a -> GargM Env BackendInternalError a logPanicErrors h = h `catch` handleSomeException where handleSomeException :: SomeException -> GargM Env BackendInternalError a handleSomeException se | Just ex@(WithStacktrace _ (UnexpectedPanic uex)) <- fromException se = do $(logLocM) ERROR $ T.pack $ displayException ex throwError $ InternalServerError $ err500 { errBody = BL.fromStrict $ TE.encodeUtf8 uex } | Just (ber :: BackendInternalError) <- fromException se = throwError ber -- re-throw the uncaught exception via the 'MonadError' instance | otherwise = throwM se -- re-throw the uncaught exception.