2 Module : Gargantext.API
3 Description : REST API declaration
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.
14 Provide config, state, logs and IO
15 type App m a = ( MonadState AppState m
17 , MonadLog (WithSeverity Doc) m
19 Thanks @yannEsposito for this.
22 {-# OPTIONS_GHC -fno-warn-name-shadowing #-}
25 {-# LANGUAGE NoImplicitPrelude #-}
26 {-# LANGUAGE DataKinds #-}
27 {-# LANGUAGE DeriveGeneric #-}
28 {-# LANGUAGE FlexibleInstances #-}
29 {-# LANGUAGE OverloadedStrings #-}
30 {-# LANGUAGE TemplateHaskell #-}
31 {-# LANGUAGE TypeOperators #-}
32 {-# LANGUAGE KindSignatures #-}
33 {-# LANGUAGE TypeFamilies #-}
34 {-# LANGUAGE UndecidableInstances #-}
36 ---------------------------------------------------------------------
39 ---------------------------------------------------------------------
40 import Gargantext.Prelude
42 import System.IO (FilePath)
44 import GHC.Generics (D1, Meta (..), Rep)
45 import GHC.TypeLits (AppendSymbol, Symbol)
48 import Data.Aeson.Encode.Pretty (encodePretty)
49 import qualified Data.ByteString.Lazy.Char8 as BL8
51 import Data.Text (Text)
52 import qualified Data.Text.IO as T
53 --import qualified Data.Set as Set
56 import Network.Wai.Handler.Warp hiding (defaultSettings)
59 import Servant.Mock (mock)
60 --import Servant.Job.Server (WithCallbacks)
61 import Servant.Swagger
62 import Servant.Swagger.UI
63 -- import Servant.API.Stream
65 --import Gargantext.API.Swagger
66 import Gargantext.API.FrontEnd (FrontEndAPI, frontEndServer)
68 import Gargantext.API.Node ( Roots , roots
74 import Gargantext.API.Count ( CountAPI, count, Query)
75 import Gargantext.API.Search ( SearchAPI, search, SearchQuery)
76 --import Gargantext.API.Orchestrator
77 --import Gargantext.API.Orchestrator.Types
79 ---------------------------------------------------------------------
81 import GHC.Base (Applicative)
82 -- import Control.Lens
84 import Data.List (lookup)
85 import Data.Text.Encoding (encodeUtf8)
87 --import Network.Wai (Request, requestHeaders, responseLBS)
88 import Network.Wai (Request, requestHeaders)
89 --import qualified Network.Wai.Handler.Warp as Warp
90 import Network.Wai.Middleware.Cors
92 import Network.Wai.Middleware.RequestLogger
93 -- import qualified Network.Wai.Middleware.RequestLogger as RequestLogger
95 import Network.HTTP.Types hiding (Query)
98 import Gargantext.API.Settings
100 fireWall :: Applicative f => Request -> FireWall -> f Bool
102 let origin = lookup "Origin" (requestHeaders req)
103 let host = lookup "Host" (requestHeaders req)
105 let hostOk = Just (encodeUtf8 "localhost:3000")
106 let originOk = Just (encodeUtf8 "http://localhost:8008")
108 if origin == originOk
110 || (not $ unFireWall fw)
116 -- makeMockApp :: Env -> IO (Warp.Settings, Application)
117 makeMockApp :: MockEnv -> IO Application
119 let serverApp = appMock
121 -- logWare <- mkRequestLogger def { destination = RequestLogger.Logger $ env^.logger }
122 --logWare <- mkRequestLogger def { destination = RequestLogger.Logger "/tmp/logs.txt" }
123 let checkOriginAndHost app req resp = do
124 blocking <- fireWall req (env ^. menv_firewall)
127 False -> resp ( responseLBS status401 []
128 "Invalid Origin or Host header")
130 let corsMiddleware = cors $ \_ -> Just CorsResourcePolicy
131 -- { corsOrigins = Just ([env^.settings.allowedOrigin], False)
132 { corsOrigins = Nothing -- == /*
133 , corsMethods = [ methodGet , methodPost , methodPut
134 , methodDelete, methodOptions, methodHead]
135 , corsRequestHeaders = ["authorization", "content-type"]
136 , corsExposedHeaders = Nothing
137 , corsMaxAge = Just ( 60*60*24 ) -- one day
138 , corsVaryOrigin = False
139 , corsRequireOrigin = False
140 , corsIgnoreFailures = False
143 --let warpS = Warp.setPort (8008 :: Int) -- (env^.settings.appPort)
144 -- $ Warp.defaultSettings
146 --pure (warpS, logWare $ checkOriginAndHost $ corsMiddleware $ serverApp)
147 pure $ logStdoutDev $ checkOriginAndHost $ corsMiddleware $ serverApp
151 makeDevApp :: Env -> IO Application
153 serverApp <- makeApp env
155 -- logWare <- mkRequestLogger def { destination = RequestLogger.Logger $ env^.logger }
156 --logWare <- mkRequestLogger def { destination = RequestLogger.Logger "/tmp/logs.txt" }
157 -- let checkOriginAndHost app req resp = do
158 -- blocking <- fireWall req (env ^. menv_firewall)
160 -- True -> app req resp
161 -- False -> resp ( responseLBS status401 []
162 -- "Invalid Origin or Host header")
164 let corsMiddleware = cors $ \_ -> Just CorsResourcePolicy
165 -- { corsOrigins = Just ([env^.settings.allowedOrigin], False)
166 { corsOrigins = Nothing -- == /*
167 , corsMethods = [ methodGet , methodPost , methodPut
168 , methodDelete, methodOptions, methodHead]
169 , corsRequestHeaders = ["authorization", "content-type"]
170 , corsExposedHeaders = Nothing
171 , corsMaxAge = Just ( 60*60*24 ) -- one day
172 , corsVaryOrigin = False
173 , corsRequireOrigin = False
174 , corsIgnoreFailures = False
177 --let warpS = Warp.setPort (8008 :: Int) -- (env^.settings.appPort)
178 -- $ Warp.defaultSettings
180 --pure (warpS, logWare $ checkOriginAndHost $ corsMiddleware $ serverApp)
181 pure $ logStdoutDev $ corsMiddleware $ serverApp
185 ---------------------------------------------------------------------
188 -- | API for serving @swagger.json@
189 type SwaggerAPI = SwaggerSchemaUI "swagger-ui" "swagger.json"
191 -- | API for serving main operational routes of @gargantext.org@
195 "user" :> Summary "First user endpoint"
200 :<|> "node" :> Summary "Node endpoint"
201 :> Capture "id" Int :> NodeAPI
205 :<|> "corpus":> Summary "Corpus endpoint"
206 :> Capture "id" Int :> NodeAPI
209 :<|> "nodes" :> Summary "Nodes endpoint"
210 :> ReqBody '[JSON] [Int] :> NodesAPI
212 -- :<|> "counts" :> Stream GET NewLineFraming '[JSON] Count :> CountAPI
214 :<|> "count" :> Summary "Count endpoint"
215 :> ReqBody '[JSON] Query :> CountAPI
218 :<|> "search":> Summary "Search endpoint"
219 :> ReqBody '[JSON] SearchQuery :> SearchAPI
221 :<|> "graph" :> Summary "Graph endpoint"
222 :> Capture "id" Int :> GraphAPI
225 :<|> "tree" :> Summary "Tree endpoint"
226 :> Capture "id" Int :> TreeAPI
229 -- :<|> "scraper" :> WithCallbacks ScraperAPI
235 -- :<|> "list" :> Capture "id" Int :> NodeAPI
236 -- :<|> "ngrams" :> Capture "id" Int :> NodeAPI
237 -- :<|> "auth" :> Capture "id" Int :> NodeAPI
238 ---------------------------------------------------------------------
239 type SwaggerFrontAPI = SwaggerAPI :<|> FrontEndAPI
241 type API = SwaggerFrontAPI :<|> GargAPI
243 ---------------------------------------------------------------------
244 -- | Server declaration
245 server :: Env -> IO (Server API)
247 -- orchestrator <- scrapyOrchestrator env
259 conn = env ^. env_conn
261 ---------------------------------------------------------------------
262 swaggerFront :: Server SwaggerFrontAPI
263 swaggerFront = schemaUiServer swaggerDoc
266 gargMock :: Server GargAPI
267 gargMock = mock apiGarg Proxy
269 ---------------------------------------------------------------------
270 makeApp :: Env -> IO Application
271 makeApp = fmap (serve api) . server
273 appMock :: Application
274 appMock = serve api (swaggerFront :<|> gargMock)
276 ---------------------------------------------------------------------
280 apiGarg :: Proxy GargAPI
282 ---------------------------------------------------------------------
284 schemaUiServer :: (Server api ~ Handler Swagger)
285 => Swagger -> Server (SwaggerSchemaUI' dir api)
286 schemaUiServer = swaggerSchemaUIServer
289 -- Type Family for the Documentation
290 type family TypeName (x :: *) :: Symbol where
292 TypeName Text = "Text"
293 TypeName x = GenericTypeName x (Rep x ())
295 type family GenericTypeName t (r :: *) :: Symbol where
296 GenericTypeName t (D1 ('MetaData name mod pkg nt) f x) = name
298 type Desc t n = Description (AppendSymbol (TypeName t) (AppendSymbol " | " n))
301 -- | Swagger Specifications
302 swaggerDoc :: Swagger
303 swaggerDoc = toSwagger (Proxy :: Proxy GargAPI)
304 & info.title .~ "Gargantext"
305 & info.version .~ "0.1.0"
306 -- & info.base_url ?~ (URL "http://gargantext.org/")
307 & info.description ?~ "REST API specifications"
308 -- & tags .~ Set.fromList [Tag "Garg" (Just "Main perations") Nothing]
309 & applyTagsFor (subOperations (Proxy :: Proxy GargAPI)(Proxy :: Proxy GargAPI))
310 ["Garg" & description ?~ "Main operations"]
311 & info.license ?~ ("AGPLV3 (English) and CECILL (French)" & url ?~ URL urlLicence )
313 urlLicence = "https://gitlab.iscpif.fr/gargantext/haskell-gargantext/blob/master/LICENSE"
315 -- | Output generated @swagger.json@ file for the @'TodoAPI'@.
316 swaggerWriteJSON :: IO ()
317 swaggerWriteJSON = BL8.writeFile "swagger.json" (encodePretty swaggerDoc)
319 portRouteInfo :: PortNumber -> IO ()
320 portRouteInfo port = do
321 T.putStrLn " ----Main Routes----- "
322 T.putStrLn $ "http://localhost:" <> toUrlPiece port <> "/index.html"
323 T.putStrLn $ "http://localhost:" <> toUrlPiece port <> "/swagger-ui"
325 -- | startGargantext takes as parameters port number and Ini file.
326 startGargantext :: PortNumber -> FilePath -> IO ()
327 startGargantext port file = do
328 env <- newEnv port file
330 app <- makeDevApp env
333 startGargantextMock :: PortNumber -> IO ()
334 startGargantextMock port = do
336 application <- makeMockApp . MockEnv $ FireWall False