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
73 import Gargantext.API.Count ( CountAPI, count, Query)
74 import Gargantext.API.Search ( SearchAPI, search, SearchQuery)
75 --import Gargantext.API.Orchestrator
76 --import Gargantext.API.Orchestrator.Types
78 ---------------------------------------------------------------------
80 import GHC.Base (Applicative)
81 -- import Control.Lens
83 import Data.List (lookup)
84 import Data.Text.Encoding (encodeUtf8)
86 --import Network.Wai (Request, requestHeaders, responseLBS)
87 import Network.Wai (Request, requestHeaders)
88 --import qualified Network.Wai.Handler.Warp as Warp
89 import Network.Wai.Middleware.Cors
91 import Network.Wai.Middleware.RequestLogger
92 -- import qualified Network.Wai.Middleware.RequestLogger as RequestLogger
94 import Network.HTTP.Types hiding (Query)
97 import Gargantext.API.Settings
99 fireWall :: Applicative f => Request -> FireWall -> f Bool
101 let origin = lookup "Origin" (requestHeaders req)
102 let host = lookup "Host" (requestHeaders req)
104 let hostOk = Just (encodeUtf8 "localhost:3000")
105 let originOk = Just (encodeUtf8 "http://localhost:8008")
107 if origin == originOk
109 || (not $ unFireWall fw)
115 -- makeMockApp :: Env -> IO (Warp.Settings, Application)
116 makeMockApp :: MockEnv -> IO Application
118 let serverApp = appMock
120 -- logWare <- mkRequestLogger def { destination = RequestLogger.Logger $ env^.logger }
121 --logWare <- mkRequestLogger def { destination = RequestLogger.Logger "/tmp/logs.txt" }
122 let checkOriginAndHost app req resp = do
123 blocking <- fireWall req (env ^. menv_firewall)
126 False -> resp ( responseLBS status401 []
127 "Invalid Origin or Host header")
129 let corsMiddleware = cors $ \_ -> Just CorsResourcePolicy
130 -- { corsOrigins = Just ([env^.settings.allowedOrigin], False)
131 { corsOrigins = Nothing -- == /*
132 , corsMethods = [ methodGet , methodPost , methodPut
133 , methodDelete, methodOptions, methodHead]
134 , corsRequestHeaders = ["authorization", "content-type"]
135 , corsExposedHeaders = Nothing
136 , corsMaxAge = Just ( 60*60*24 ) -- one day
137 , corsVaryOrigin = False
138 , corsRequireOrigin = False
139 , corsIgnoreFailures = False
142 --let warpS = Warp.setPort (8008 :: Int) -- (env^.settings.appPort)
143 -- $ Warp.defaultSettings
145 --pure (warpS, logWare $ checkOriginAndHost $ corsMiddleware $ serverApp)
146 pure $ logStdoutDev $ checkOriginAndHost $ corsMiddleware $ serverApp
150 makeDevApp :: Env -> IO Application
152 serverApp <- makeApp env
154 -- logWare <- mkRequestLogger def { destination = RequestLogger.Logger $ env^.logger }
155 --logWare <- mkRequestLogger def { destination = RequestLogger.Logger "/tmp/logs.txt" }
156 -- let checkOriginAndHost app req resp = do
157 -- blocking <- fireWall req (env ^. menv_firewall)
159 -- True -> app req resp
160 -- False -> resp ( responseLBS status401 []
161 -- "Invalid Origin or Host header")
163 let corsMiddleware = cors $ \_ -> Just CorsResourcePolicy
164 -- { corsOrigins = Just ([env^.settings.allowedOrigin], False)
165 { corsOrigins = Nothing -- == /*
166 , corsMethods = [ methodGet , methodPost , methodPut
167 , methodDelete, methodOptions, methodHead]
168 , corsRequestHeaders = ["authorization", "content-type"]
169 , corsExposedHeaders = Nothing
170 , corsMaxAge = Just ( 60*60*24 ) -- one day
171 , corsVaryOrigin = False
172 , corsRequireOrigin = False
173 , corsIgnoreFailures = False
176 --let warpS = Warp.setPort (8008 :: Int) -- (env^.settings.appPort)
177 -- $ Warp.defaultSettings
179 --pure (warpS, logWare $ checkOriginAndHost $ corsMiddleware $ serverApp)
180 pure $ logStdoutDev $ corsMiddleware $ serverApp
184 ---------------------------------------------------------------------
187 -- | API for serving @swagger.json@
188 type SwaggerAPI = SwaggerSchemaUI "swagger-ui" "swagger.json"
190 -- | API for serving main operational routes of @gargantext.org@
194 "user" :> Summary "First user endpoint"
199 :<|> "node" :> Summary "Node endpoint"
200 :> Capture "id" Int :> NodeAPI
204 :<|> "corpus":> Summary "Corpus endpoint"
205 :> Capture "id" Int :> NodeAPI
208 :<|> "nodes" :> Summary "Nodes endpoint"
209 :> ReqBody '[JSON] [Int] :> NodesAPI
211 -- :<|> "counts" :> Stream GET NewLineFraming '[JSON] Count :> CountAPI
213 :<|> "count" :> Summary "Count endpoint"
214 :> ReqBody '[JSON] Query :> CountAPI
217 :<|> "search":> Summary "Search endpoint"
218 :> ReqBody '[JSON] SearchQuery :> SearchAPI
220 :<|> "graph" :> Summary "Graph endpoint"
221 :> Capture "id" Int :> GraphAPI
223 -- :<|> "scraper" :> WithCallbacks ScraperAPI
229 -- :<|> "list" :> Capture "id" Int :> NodeAPI
230 -- :<|> "ngrams" :> Capture "id" Int :> NodeAPI
231 -- :<|> "auth" :> Capture "id" Int :> NodeAPI
232 ---------------------------------------------------------------------
233 type SwaggerFrontAPI = SwaggerAPI :<|> FrontEndAPI
235 type API = SwaggerFrontAPI :<|> GargAPI
237 ---------------------------------------------------------------------
238 -- | Server declaration
239 server :: Env -> IO (Server API)
241 -- orchestrator <- scrapyOrchestrator env
252 conn = env ^. env_conn
254 ---------------------------------------------------------------------
255 swaggerFront :: Server SwaggerFrontAPI
256 swaggerFront = schemaUiServer swaggerDoc
259 gargMock :: Server GargAPI
260 gargMock = mock apiGarg Proxy
262 ---------------------------------------------------------------------
263 makeApp :: Env -> IO Application
264 makeApp = fmap (serve api) . server
266 appMock :: Application
267 appMock = serve api (swaggerFront :<|> gargMock)
269 ---------------------------------------------------------------------
273 apiGarg :: Proxy GargAPI
275 ---------------------------------------------------------------------
277 schemaUiServer :: (Server api ~ Handler Swagger)
278 => Swagger -> Server (SwaggerSchemaUI' dir api)
279 schemaUiServer = swaggerSchemaUIServer
282 -- Type Family for the Documentation
283 type family TypeName (x :: *) :: Symbol where
285 TypeName Text = "Text"
286 TypeName x = GenericTypeName x (Rep x ())
288 type family GenericTypeName t (r :: *) :: Symbol where
289 GenericTypeName t (D1 ('MetaData name mod pkg nt) f x) = name
291 type Desc t n = Description (AppendSymbol (TypeName t) (AppendSymbol " | " n))
294 -- | Swagger Specifications
295 swaggerDoc :: Swagger
296 swaggerDoc = toSwagger (Proxy :: Proxy GargAPI)
297 & info.title .~ "Gargantext"
298 & info.version .~ "0.1.0"
299 -- & info.base_url ?~ (URL "http://gargantext.org/")
300 & info.description ?~ "REST API specifications"
301 -- & tags .~ Set.fromList [Tag "Garg" (Just "Main perations") Nothing]
302 & applyTagsFor (subOperations (Proxy :: Proxy GargAPI)(Proxy :: Proxy GargAPI))
303 ["Garg" & description ?~ "Main operations"]
304 & info.license ?~ ("AGPLV3 (English) and CECILL (French)" & url ?~ URL urlLicence )
306 urlLicence = "https://gitlab.iscpif.fr/gargantext/haskell-gargantext/blob/master/LICENSE"
308 -- | Output generated @swagger.json@ file for the @'TodoAPI'@.
309 swaggerWriteJSON :: IO ()
310 swaggerWriteJSON = BL8.writeFile "swagger.json" (encodePretty swaggerDoc)
312 portRouteInfo :: PortNumber -> IO ()
313 portRouteInfo port = do
314 T.putStrLn " ----Main Routes----- "
315 T.putStrLn $ "http://localhost:" <> toUrlPiece port <> "/index.html"
316 T.putStrLn $ "http://localhost:" <> toUrlPiece port <> "/swagger-ui"
318 -- | startGargantext takes as parameters port number and Ini file.
319 startGargantext :: PortNumber -> FilePath -> IO ()
320 startGargantext port file = do
321 env <- newEnv port file
323 app <- makeDevApp env
326 startGargantextMock :: PortNumber -> IO ()
327 startGargantextMock port = do
329 application <- makeMockApp . MockEnv $ FireWall False