]> Git — Sourcephile - gargantext.git/blob - src/Gargantext/API.hs
[DBFLOW] /api/v1.0/node/{id}/table ok
[gargantext.git] / src / Gargantext / API.hs
1 {-|
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
8 Portability : POSIX
9
10 Main REST API of Gargantext (both Server and Client sides)
11
12 TODO App type, the main monad in which the bot code is written with.
13
14 Provide config, state, logs and IO
15 type App m a = ( MonadState AppState m
16 , MonadReader Conf m
17 , MonadLog (WithSeverity Doc) m
18 , MonadIO m) => m a
19 Thanks @yannEsposito for this.
20 -}
21
22 {-# OPTIONS_GHC -fno-warn-name-shadowing #-}
23
24
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 #-}
35
36 ---------------------------------------------------------------------
37 module Gargantext.API
38 where
39 ---------------------------------------------------------------------
40 import Gargantext.Prelude
41
42 import System.IO (FilePath)
43
44 import GHC.Generics (D1, Meta (..), Rep)
45 import GHC.TypeLits (AppendSymbol, Symbol)
46
47 import Control.Lens
48 import Data.Aeson (Value)
49 import Data.Aeson.Encode.Pretty (encodePretty)
50 import qualified Data.ByteString.Lazy.Char8 as BL8
51 import Data.Swagger
52 import Data.Text (Text)
53 import qualified Data.Text.IO as T
54 --import qualified Data.Set as Set
55
56 import Network.Wai
57 import Network.Wai.Handler.Warp hiding (defaultSettings)
58
59 import Servant
60 import Servant.Mock (mock)
61 --import Servant.Job.Server (WithCallbacks)
62 import Servant.Swagger
63 import Servant.Swagger.UI
64 -- import Servant.API.Stream
65
66 --import Gargantext.API.Swagger
67 import Gargantext.API.FrontEnd (FrontEndAPI, frontEndServer)
68
69 import Gargantext.API.Node ( Roots , roots
70 , NodeAPI , nodeAPI
71 , NodesAPI , nodesAPI
72 , GraphAPI , graphAPI
73 , TreeAPI , treeAPI
74 , HyperdataCorpus
75 )
76 import Gargantext.Database.Types.Node ()
77 import Gargantext.API.Count ( CountAPI, count, Query)
78 import Gargantext.API.Search ( SearchAPI, search, SearchQuery)
79 --import Gargantext.API.Orchestrator
80 --import Gargantext.API.Orchestrator.Types
81
82 ---------------------------------------------------------------------
83
84 import GHC.Base (Applicative)
85 -- import Control.Lens
86
87 import Data.List (lookup)
88 import Data.Text.Encoding (encodeUtf8)
89
90 --import Network.Wai (Request, requestHeaders, responseLBS)
91 import Network.Wai (Request, requestHeaders)
92 --import qualified Network.Wai.Handler.Warp as Warp
93 import Network.Wai.Middleware.Cors
94
95 import Network.Wai.Middleware.RequestLogger
96 -- import qualified Network.Wai.Middleware.RequestLogger as RequestLogger
97
98 import Network.HTTP.Types hiding (Query)
99
100
101 import Gargantext.API.Settings
102
103 fireWall :: Applicative f => Request -> FireWall -> f Bool
104 fireWall req fw = do
105 let origin = lookup "Origin" (requestHeaders req)
106 let host = lookup "Host" (requestHeaders req)
107
108 let hostOk = Just (encodeUtf8 "localhost:3000")
109 let originOk = Just (encodeUtf8 "http://localhost:8008")
110
111 if origin == originOk
112 && host == hostOk
113 || (not $ unFireWall fw)
114
115 then pure True
116 else pure False
117
118
119 -- makeMockApp :: Env -> IO (Warp.Settings, Application)
120 makeMockApp :: MockEnv -> IO Application
121 makeMockApp env = do
122 let serverApp = appMock
123
124 -- logWare <- mkRequestLogger def { destination = RequestLogger.Logger $ env^.logger }
125 --logWare <- mkRequestLogger def { destination = RequestLogger.Logger "/tmp/logs.txt" }
126 let checkOriginAndHost app req resp = do
127 blocking <- fireWall req (env ^. menv_firewall)
128 case blocking of
129 True -> app req resp
130 False -> resp ( responseLBS status401 []
131 "Invalid Origin or Host header")
132
133 let corsMiddleware = cors $ \_ -> Just CorsResourcePolicy
134 -- { corsOrigins = Just ([env^.settings.allowedOrigin], False)
135 { corsOrigins = Nothing -- == /*
136 , corsMethods = [ methodGet , methodPost , methodPut
137 , methodDelete, methodOptions, methodHead]
138 , corsRequestHeaders = ["authorization", "content-type"]
139 , corsExposedHeaders = Nothing
140 , corsMaxAge = Just ( 60*60*24 ) -- one day
141 , corsVaryOrigin = False
142 , corsRequireOrigin = False
143 , corsIgnoreFailures = False
144 }
145
146 --let warpS = Warp.setPort (8008 :: Int) -- (env^.settings.appPort)
147 -- $ Warp.defaultSettings
148
149 --pure (warpS, logWare $ checkOriginAndHost $ corsMiddleware $ serverApp)
150 pure $ logStdoutDev $ checkOriginAndHost $ corsMiddleware $ serverApp
151
152
153 --
154 makeDevApp :: Env -> IO Application
155 makeDevApp env = do
156 serverApp <- makeApp env
157
158 -- logWare <- mkRequestLogger def { destination = RequestLogger.Logger $ env^.logger }
159 --logWare <- mkRequestLogger def { destination = RequestLogger.Logger "/tmp/logs.txt" }
160 -- let checkOriginAndHost app req resp = do
161 -- blocking <- fireWall req (env ^. menv_firewall)
162 -- case blocking of
163 -- True -> app req resp
164 -- False -> resp ( responseLBS status401 []
165 -- "Invalid Origin or Host header")
166 --
167 let corsMiddleware = cors $ \_ -> Just CorsResourcePolicy
168 -- { corsOrigins = Just ([env^.settings.allowedOrigin], False)
169 { corsOrigins = Nothing -- == /*
170 , corsMethods = [ methodGet , methodPost , methodPut
171 , methodDelete, methodOptions, methodHead]
172 , corsRequestHeaders = ["authorization", "content-type"]
173 , corsExposedHeaders = Nothing
174 , corsMaxAge = Just ( 60*60*24 ) -- one day
175 , corsVaryOrigin = False
176 , corsRequireOrigin = False
177 , corsIgnoreFailures = False
178 }
179
180 --let warpS = Warp.setPort (8008 :: Int) -- (env^.settings.appPort)
181 -- $ Warp.defaultSettings
182
183 --pure (warpS, logWare $ checkOriginAndHost $ corsMiddleware $ serverApp)
184 pure $ logStdoutDev $ corsMiddleware $ serverApp
185
186 --
187
188 ---------------------------------------------------------------------
189 -- | API Global
190
191 -- | API for serving @swagger.json@
192 type SwaggerAPI = SwaggerSchemaUI "swagger-ui" "swagger.json"
193
194 -- | API for serving main operational routes of @gargantext.org@
195
196
197 type GargAPI = "api" :> Summary "API " :> GargAPIVersion
198 -- | TODO :<|> Summary "Latest API" :> GargAPI'
199
200
201 type GargAPIVersion = "v1.0" :> Summary "v1.0: " :> GargAPI'
202
203 type GargAPI' =
204
205 -- Roots endpoint
206 "user" :> Summary "First user endpoint"
207 :> Roots
208
209
210 -- Node endpoint
211 :<|> "node" :> Summary "Node endpoint"
212 :> Capture "id" Int :> NodeAPI Value
213
214
215 -- Corpus endpoint
216 :<|> "corpus":> Summary "Corpus endpoint"
217 :> Capture "id" Int :> NodeAPI HyperdataCorpus
218
219 -- Corpus endpoint
220 :<|> "nodes" :> Summary "Nodes endpoint"
221 :> ReqBody '[JSON] [Int] :> NodesAPI
222
223 -- :<|> "counts" :> Stream GET NewLineFraming '[JSON] Count :> CountAPI
224 -- Corpus endpoint
225 :<|> "count" :> Summary "Count endpoint"
226 :> ReqBody '[JSON] Query :> CountAPI
227
228 -- Corpus endpoint
229 :<|> "search":> Summary "Search endpoint"
230 :> ReqBody '[JSON] SearchQuery :> SearchAPI
231
232 :<|> "graph" :> Summary "Graph endpoint"
233 :> Capture "id" Int :> GraphAPI
234
235 -- Tree endpoint
236 :<|> "tree" :> Summary "Tree endpoint"
237 :> Capture "id" Int :> TreeAPI
238
239
240 -- :<|> "scraper" :> WithCallbacks ScraperAPI
241
242 -- /mv/<id>/<id>
243 -- /merge/<id>/<id>
244 -- /rename/<id>
245 -- :<|> "static"
246 -- :<|> "list" :> Capture "id" Int :> NodeAPI
247 -- :<|> "ngrams" :> Capture "id" Int :> NodeAPI
248 -- :<|> "auth" :> Capture "id" Int :> NodeAPI
249 ---------------------------------------------------------------------
250 type SwaggerFrontAPI = SwaggerAPI :<|> FrontEndAPI
251
252 type API = SwaggerFrontAPI :<|> GargAPI
253
254 ---------------------------------------------------------------------
255 -- | Server declaration
256 server :: Env -> IO (Server API)
257 server env = do
258 -- orchestrator <- scrapyOrchestrator env
259 pure $ swaggerFront
260 :<|> roots conn
261 :<|> nodeAPI conn (Proxy :: Proxy Value)
262 :<|> nodeAPI conn (Proxy :: Proxy HyperdataCorpus)
263 :<|> nodesAPI conn
264 :<|> count -- TODO: undefined
265 :<|> search conn
266 :<|> graphAPI conn -- TODO: mock
267 :<|> treeAPI conn
268 -- :<|> orchestrator
269 where
270 conn = env ^. env_conn
271
272 ---------------------------------------------------------------------
273 swaggerFront :: Server SwaggerFrontAPI
274 swaggerFront = schemaUiServer swaggerDoc
275 :<|> frontEndServer
276
277 gargMock :: Server GargAPI
278 gargMock = mock apiGarg Proxy
279
280 ---------------------------------------------------------------------
281 makeApp :: Env -> IO Application
282 makeApp = fmap (serve api) . server
283
284 appMock :: Application
285 appMock = serve api (swaggerFront :<|> gargMock)
286
287 ---------------------------------------------------------------------
288 api :: Proxy API
289 api = Proxy
290
291 apiGarg :: Proxy GargAPI
292 apiGarg = Proxy
293 ---------------------------------------------------------------------
294
295 schemaUiServer :: (Server api ~ Handler Swagger)
296 => Swagger -> Server (SwaggerSchemaUI' dir api)
297 schemaUiServer = swaggerSchemaUIServer
298
299
300 -- Type Family for the Documentation
301 type family TypeName (x :: *) :: Symbol where
302 TypeName Int = "Int"
303 TypeName Text = "Text"
304 TypeName x = GenericTypeName x (Rep x ())
305
306 type family GenericTypeName t (r :: *) :: Symbol where
307 GenericTypeName t (D1 ('MetaData name mod pkg nt) f x) = name
308
309 type Desc t n = Description (AppendSymbol (TypeName t) (AppendSymbol " | " n))
310
311
312 -- | Swagger Specifications
313 swaggerDoc :: Swagger
314 swaggerDoc = toSwagger (Proxy :: Proxy GargAPI)
315 & info.title .~ "Gargantext"
316 & info.version .~ "0.1.0"
317 -- & info.base_url ?~ (URL "http://gargantext.org/")
318 & info.description ?~ "REST API specifications"
319 -- & tags .~ Set.fromList [Tag "Garg" (Just "Main perations") Nothing]
320 & applyTagsFor (subOperations (Proxy :: Proxy GargAPI)(Proxy :: Proxy GargAPI))
321 ["Gargantext" & description ?~ "Main operations"]
322 & info.license ?~ ("AGPLV3 (English) and CECILL (French)" & url ?~ URL urlLicence )
323 where
324 urlLicence = "https://gitlab.iscpif.fr/gargantext/haskell-gargantext/blob/master/LICENSE"
325
326 -- | Output generated @swagger.json@ file for the @'TodoAPI'@.
327 swaggerWriteJSON :: IO ()
328 swaggerWriteJSON = BL8.writeFile "swagger.json" (encodePretty swaggerDoc)
329
330 portRouteInfo :: PortNumber -> IO ()
331 portRouteInfo port = do
332 T.putStrLn " ----Main Routes----- "
333 T.putStrLn $ "http://localhost:" <> toUrlPiece port <> "/index.html"
334 T.putStrLn $ "http://localhost:" <> toUrlPiece port <> "/swagger-ui"
335
336 -- | startGargantext takes as parameters port number and Ini file.
337 startGargantext :: PortNumber -> FilePath -> IO ()
338 startGargantext port file = do
339 env <- newEnv port file
340 portRouteInfo port
341 app <- makeDevApp env
342 run port app
343
344 startGargantextMock :: PortNumber -> IO ()
345 startGargantextMock port = do
346 portRouteInfo port
347 application <- makeMockApp . MockEnv $ FireWall False
348 run port application
349