2 Module : Gargantext.API
3 Description : Server API
4 Copyright : (c) CNRS, 2017-Present
5 License : AGPL + CECILL v3
6 Maintainer : team@gargantext.org
7 Stability : experimental
10 Main REST API of Gargantext (both Server and Client sides)
12 TODO App type, the main monad in which the bot code is written with.
13 Provide config, state, logs and IO
14 type App m a = ( MonadState AppState m
16 , MonadLog (WithSeverity Doc) m
18 Thanks @yannEsposito for this.
21 {-# OPTIONS_GHC -fno-warn-name-shadowing #-}
24 {-# LANGUAGE NoImplicitPrelude #-}
25 {-# LANGUAGE DataKinds #-}
26 {-# LANGUAGE DeriveGeneric #-}
27 {-# LANGUAGE FlexibleInstances #-}
28 {-# LANGUAGE OverloadedStrings #-}
29 {-# LANGUAGE TemplateHaskell #-}
30 {-# LANGUAGE TypeOperators #-}
31 {-# LANGUAGE KindSignatures #-}
32 {-# LANGUAGE TypeFamilies #-}
33 {-# LANGUAGE UndecidableInstances #-}
35 ---------------------------------------------------------------------
38 ---------------------------------------------------------------------
39 import Gargantext.Prelude
41 import System.IO (FilePath)
43 import GHC.Generics (D1, Meta (..), Rep)
44 import GHC.TypeLits (AppendSymbol, Symbol)
47 import Data.Aeson.Encode.Pretty (encodePretty)
48 import qualified Data.ByteString.Lazy.Char8 as BL8
50 import Data.Text (Text)
51 import qualified Data.Text.IO as T
52 --import qualified Data.Set as Set
55 import Network.Wai.Handler.Warp hiding (defaultSettings)
58 import Servant.Mock (mock)
59 import Servant.Job.Server (WithCallbacks)
60 import Servant.Swagger
61 import Servant.Swagger.UI
62 -- import Servant.API.Stream
64 --import Gargantext.API.Swagger
65 import Gargantext.API.FrontEnd (FrontEndAPI, frontEndServer)
67 import Gargantext.API.Node ( Roots , roots
71 import Gargantext.API.Count ( CountAPI, count, Query)
72 import Gargantext.API.Orchestrator
73 import Gargantext.API.Orchestrator.Types
75 ---------------------------------------------------------------------
77 import GHC.Base (Applicative)
78 -- import Control.Lens
80 import Data.List (lookup)
81 import Data.Text.Encoding (encodeUtf8)
83 --import Network.Wai (Request, requestHeaders, responseLBS)
84 import Network.Wai (Request, requestHeaders)
85 --import qualified Network.Wai.Handler.Warp as Warp
86 import Network.Wai.Middleware.Cors
88 import Network.Wai.Middleware.RequestLogger
89 -- import qualified Network.Wai.Middleware.RequestLogger as RequestLogger
91 import Network.HTTP.Types hiding (Query)
94 import Gargantext.API.Settings
96 fireWall :: Applicative f => Request -> FireWall -> f Bool
98 let origin = lookup "Origin" (requestHeaders req)
99 let host = lookup "Host" (requestHeaders req)
101 let hostOk = Just (encodeUtf8 "localhost:3000")
102 let originOk = Just (encodeUtf8 "http://localhost:8008")
104 if origin == originOk
106 || (not $ unFireWall fw)
112 -- makeMockApp :: Env -> IO (Warp.Settings, Application)
113 makeMockApp :: MockEnv -> IO Application
115 let serverApp = appMock
117 -- logWare <- mkRequestLogger def { destination = RequestLogger.Logger $ env^.logger }
118 --logWare <- mkRequestLogger def { destination = RequestLogger.Logger "/tmp/logs.txt" }
119 let checkOriginAndHost app req resp = do
120 blocking <- fireWall req (env ^. menv_firewall)
123 False -> resp ( responseLBS status401 []
124 "Invalid Origin or Host header")
126 let corsMiddleware = cors $ \_ -> Just CorsResourcePolicy
127 -- { corsOrigins = Just ([env^.settings.allowedOrigin], False)
128 { corsOrigins = Nothing -- == /*
129 , corsMethods = [ methodGet , methodPost , methodPut
130 , methodDelete, methodOptions, methodHead]
131 , corsRequestHeaders = ["authorization", "content-type"]
132 , corsExposedHeaders = Nothing
133 , corsMaxAge = Just ( 60*60*24 ) -- one day
134 , corsVaryOrigin = False
135 , corsRequireOrigin = False
136 , corsIgnoreFailures = False
139 --let warpS = Warp.setPort (8008 :: Int) -- (env^.settings.appPort)
140 -- $ Warp.defaultSettings
142 --pure (warpS, logWare $ checkOriginAndHost $ corsMiddleware $ serverApp)
143 pure $ logStdoutDev $ checkOriginAndHost $ corsMiddleware $ serverApp
146 ---------------------------------------------------------------------
149 -- | API for serving @swagger.json@
150 type SwaggerAPI = SwaggerSchemaUI "swagger-ui" "swagger.json"
152 -- | API for serving main operational routes of @gargantext.org@
153 type GargAPI = "user" :> Summary "First user endpoint"
156 :<|> "node" :> Summary "Node endpoint"
157 :> Capture "id" Int :> NodeAPI
159 :<|> "corpus":> Summary "Corpus endpoint"
160 :> Capture "id" Int :> NodeAPI
162 :<|> "nodes" :> Summary "Nodes endpoint"
163 :> ReqBody '[JSON] [Int] :> NodesAPI
165 -- :<|> "counts" :> Stream GET NewLineFraming '[JSON] Count :> CountAPI
166 :<|> "count" :> Summary "Count endpoint"
167 :> ReqBody '[JSON] Query :> CountAPI
169 :<|> "scraper" :> WithCallbacks ScraperAPI
175 -- :<|> "list" :> Capture "id" Int :> NodeAPI
176 -- :<|> "ngrams" :> Capture "id" Int :> NodeAPI
177 -- :<|> "auth" :> Capture "id" Int :> NodeAPI
178 ---------------------------------------------------------------------
179 type SwaggerFrontAPI = SwaggerAPI :<|> FrontEndAPI
181 type API = SwaggerFrontAPI :<|> GargAPI
183 ---------------------------------------------------------------------
184 -- | Server declaration
185 server :: Env -> IO (Server API)
187 orchestrator <- scrapyOrchestrator env
196 conn = env ^. env_conn
198 ---------------------------------------------------------------------
199 swaggerFront :: Server SwaggerFrontAPI
200 swaggerFront = schemaUiServer swaggerDoc
203 gargMock :: Server GargAPI
204 gargMock = mock apiGarg Proxy
206 ---------------------------------------------------------------------
207 makeApp :: Env -> IO Application
208 makeApp = fmap (serve api) . server
210 appMock :: Application
211 appMock = serve api (swaggerFront :<|> gargMock)
213 ---------------------------------------------------------------------
217 apiGarg :: Proxy GargAPI
219 ---------------------------------------------------------------------
221 schemaUiServer :: (Server api ~ Handler Swagger)
222 => Swagger -> Server (SwaggerSchemaUI' dir api)
223 schemaUiServer = swaggerSchemaUIServer
226 -- Type Family for the Documentation
227 type family TypeName (x :: *) :: Symbol where
229 TypeName Text = "Text"
230 TypeName x = GenericTypeName x (Rep x ())
232 type family GenericTypeName t (r :: *) :: Symbol where
233 GenericTypeName t (D1 ('MetaData name mod pkg nt) f x) = name
235 type Desc t n = Description (AppendSymbol (TypeName t) (AppendSymbol " | " n))
238 -- | Swagger Specifications
239 swaggerDoc :: Swagger
240 swaggerDoc = toSwagger (Proxy :: Proxy GargAPI)
241 & info.title .~ "Gargantext"
242 & info.version .~ "0.1.0"
243 -- & info.base_url ?~ (URL "http://gargantext.org/")
244 & info.description ?~ "REST API specifications"
245 -- & tags .~ Set.fromList [Tag "Garg" (Just "Main perations") Nothing]
246 & applyTagsFor (subOperations (Proxy :: Proxy GargAPI)(Proxy :: Proxy GargAPI))
247 ["Garg" & description ?~ "Main operations"]
248 & info.license ?~ ("AGPLV3 (English) and CECILL (French)" & url ?~ URL urlLicence )
250 urlLicence = "https://gitlab.iscpif.fr/gargantext/haskell-gargantext/blob/master/LICENSE"
252 -- | Output generated @swagger.json@ file for the @'TodoAPI'@.
253 swaggerWriteJSON :: IO ()
254 swaggerWriteJSON = BL8.writeFile "swagger.json" (encodePretty swaggerDoc)
256 portRouteInfo :: PortNumber -> IO ()
257 portRouteInfo port = do
258 T.putStrLn " ----Main Routes----- "
259 T.putStrLn $ "http://localhost:" <> toUrlPiece port <> "/index.html"
260 T.putStrLn $ "http://localhost:" <> toUrlPiece port <> "/swagger-ui"
262 -- | startGargantext takes as parameters port number and Ini file.
263 startGargantext :: PortNumber -> FilePath -> IO ()
264 startGargantext port file = do
265 env <- newEnv port file
270 startGargantextMock :: PortNumber -> IO ()
271 startGargantextMock port = do
274 application <- makeMockApp . MockEnv $ FireWall False