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
147 makeDevApp :: Env -> IO Application
149 serverApp <- makeApp env
151 -- logWare <- mkRequestLogger def { destination = RequestLogger.Logger $ env^.logger }
152 --logWare <- mkRequestLogger def { destination = RequestLogger.Logger "/tmp/logs.txt" }
153 -- let checkOriginAndHost app req resp = do
154 -- blocking <- fireWall req (env ^. menv_firewall)
156 -- True -> app req resp
157 -- False -> resp ( responseLBS status401 []
158 -- "Invalid Origin or Host header")
160 let corsMiddleware = cors $ \_ -> Just CorsResourcePolicy
161 -- { corsOrigins = Just ([env^.settings.allowedOrigin], False)
162 { corsOrigins = Nothing -- == /*
163 , corsMethods = [ methodGet , methodPost , methodPut
164 , methodDelete, methodOptions, methodHead]
165 , corsRequestHeaders = ["authorization", "content-type"]
166 , corsExposedHeaders = Nothing
167 , corsMaxAge = Just ( 60*60*24 ) -- one day
168 , corsVaryOrigin = False
169 , corsRequireOrigin = False
170 , corsIgnoreFailures = False
173 --let warpS = Warp.setPort (8008 :: Int) -- (env^.settings.appPort)
174 -- $ Warp.defaultSettings
176 --pure (warpS, logWare $ checkOriginAndHost $ corsMiddleware $ serverApp)
177 pure $ logStdoutDev $ corsMiddleware $ serverApp
181 ---------------------------------------------------------------------
184 -- | API for serving @swagger.json@
185 type SwaggerAPI = SwaggerSchemaUI "swagger-ui" "swagger.json"
187 -- | API for serving main operational routes of @gargantext.org@
188 type GargAPI = "user" :> Summary "First user endpoint"
191 :<|> "node" :> Summary "Node endpoint"
192 :> Capture "id" Int :> NodeAPI
194 :<|> "corpus":> Summary "Corpus endpoint"
195 :> Capture "id" Int :> NodeAPI
197 :<|> "nodes" :> Summary "Nodes endpoint"
198 :> ReqBody '[JSON] [Int] :> NodesAPI
200 -- :<|> "counts" :> Stream GET NewLineFraming '[JSON] Count :> CountAPI
201 :<|> "count" :> Summary "Count endpoint"
202 :> ReqBody '[JSON] Query :> CountAPI
204 -- :<|> "scraper" :> WithCallbacks ScraperAPI
210 -- :<|> "list" :> Capture "id" Int :> NodeAPI
211 -- :<|> "ngrams" :> Capture "id" Int :> NodeAPI
212 -- :<|> "auth" :> Capture "id" Int :> NodeAPI
213 ---------------------------------------------------------------------
214 type SwaggerFrontAPI = SwaggerAPI :<|> FrontEndAPI
216 type API = SwaggerFrontAPI :<|> GargAPI
218 ---------------------------------------------------------------------
219 -- | Server declaration
220 server :: Env -> IO (Server API)
222 -- orchestrator <- scrapyOrchestrator env
231 conn = env ^. env_conn
233 ---------------------------------------------------------------------
234 swaggerFront :: Server SwaggerFrontAPI
235 swaggerFront = schemaUiServer swaggerDoc
238 gargMock :: Server GargAPI
239 gargMock = mock apiGarg Proxy
241 ---------------------------------------------------------------------
242 makeApp :: Env -> IO Application
243 makeApp = fmap (serve api) . server
245 appMock :: Application
246 appMock = serve api (swaggerFront :<|> gargMock)
248 ---------------------------------------------------------------------
252 apiGarg :: Proxy GargAPI
254 ---------------------------------------------------------------------
256 schemaUiServer :: (Server api ~ Handler Swagger)
257 => Swagger -> Server (SwaggerSchemaUI' dir api)
258 schemaUiServer = swaggerSchemaUIServer
261 -- Type Family for the Documentation
262 type family TypeName (x :: *) :: Symbol where
264 TypeName Text = "Text"
265 TypeName x = GenericTypeName x (Rep x ())
267 type family GenericTypeName t (r :: *) :: Symbol where
268 GenericTypeName t (D1 ('MetaData name mod pkg nt) f x) = name
270 type Desc t n = Description (AppendSymbol (TypeName t) (AppendSymbol " | " n))
273 -- | Swagger Specifications
274 swaggerDoc :: Swagger
275 swaggerDoc = toSwagger (Proxy :: Proxy GargAPI)
276 & info.title .~ "Gargantext"
277 & info.version .~ "0.1.0"
278 -- & info.base_url ?~ (URL "http://gargantext.org/")
279 & info.description ?~ "REST API specifications"
280 -- & tags .~ Set.fromList [Tag "Garg" (Just "Main perations") Nothing]
281 & applyTagsFor (subOperations (Proxy :: Proxy GargAPI)(Proxy :: Proxy GargAPI))
282 ["Garg" & description ?~ "Main operations"]
283 & info.license ?~ ("AGPLV3 (English) and CECILL (French)" & url ?~ URL urlLicence )
285 urlLicence = "https://gitlab.iscpif.fr/gargantext/haskell-gargantext/blob/master/LICENSE"
287 -- | Output generated @swagger.json@ file for the @'TodoAPI'@.
288 swaggerWriteJSON :: IO ()
289 swaggerWriteJSON = BL8.writeFile "swagger.json" (encodePretty swaggerDoc)
291 portRouteInfo :: PortNumber -> IO ()
292 portRouteInfo port = do
293 T.putStrLn " ----Main Routes----- "
294 T.putStrLn $ "http://localhost:" <> toUrlPiece port <> "/index.html"
295 T.putStrLn $ "http://localhost:" <> toUrlPiece port <> "/swagger-ui"
297 -- | startGargantext takes as parameters port number and Ini file.
298 startGargantext :: PortNumber -> FilePath -> IO ()
299 startGargantext port file = do
300 env <- newEnv port file
302 app <- makeDevApp env
305 startGargantextMock :: PortNumber -> IO ()
306 startGargantextMock port = do
308 application <- makeMockApp . MockEnv $ FireWall False