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 #-}
23 {-# LANGUAGE DataKinds #-}
24 {-# LANGUAGE DeriveGeneric #-}
25 {-# LANGUAGE FlexibleInstances #-}
26 {-# LANGUAGE OverloadedStrings #-}
27 {-# LANGUAGE TemplateHaskell #-}
28 {-# LANGUAGE TypeOperators #-}
29 {-# LANGUAGE KindSignatures #-}
30 {-# LANGUAGE TypeFamilies #-}
31 {-# LANGUAGE UndecidableInstances #-}
33 ---------------------------------------------------------------------
36 ---------------------------------------------------------------------
37 import Gargantext.Prelude
39 import System.IO (FilePath, print)
41 import GHC.Generics (D1, Meta (..), Rep)
42 import GHC.TypeLits (AppendSymbol, Symbol)
45 import Data.Aeson.Encode.Pretty (encodePretty)
46 import qualified Data.ByteString.Lazy.Char8 as BL8
48 import Data.Text (Text, pack)
49 --import qualified Data.Set as Set
51 import Database.PostgreSQL.Simple (Connection, connect)
54 import Network.Wai.Handler.Warp
57 import Servant.Mock (mock)
58 import Servant.Swagger
59 import Servant.Swagger.UI
60 -- import Servant.API.Stream
62 --import Gargantext.API.Swagger
63 import Gargantext.API.FrontEnd (FrontEndAPI, frontEndServer)
65 import Gargantext.API.Node ( Roots , roots
69 import Gargantext.API.Count ( CountAPI, count, Query)
70 import Gargantext.Database.Utils (databaseParameters)
72 ---------------------------------------------------------------------
75 import GHC.Base (Applicative)
76 -- import Control.Lens
78 import Data.List (lookup)
79 import Data.Text.Encoding (encodeUtf8)
81 --import Network.Wai (Request, requestHeaders, responseLBS)
82 import Network.Wai (Request, requestHeaders)
83 --import qualified Network.Wai.Handler.Warp as Warp
84 import Network.Wai.Middleware.Cors
86 -- import Network.Wai.Middleware.RequestLogger
87 -- import qualified Network.Wai.Middleware.RequestLogger as RequestLogger
89 import Network.HTTP.Types hiding (Query)
92 -- import Gargantext.API.Settings
94 data FireWall = FireWall { unFireWall :: Bool }
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 && host == hostOk || unFireWall fw
109 -- makeApp :: Env -> IO (Warp.Settings, Application)
110 makeApp :: FireWall -> IO Application
112 let serverApp = appMock
114 -- logWare <- mkRequestLogger def { destination = RequestLogger.Logger $ env^.logger }
116 let checkOriginAndHost app req resp = do
117 blocking <- fireWall req fw
120 False -> resp ( responseLBS status401 [] "Invalid Origin or Host header" )
122 let corsMiddleware = cors $ \_ -> Just CorsResourcePolicy
123 -- { corsOrigins = Just ([env^.settings.allowedOrigin], False)
124 { corsOrigins = Just (["http://localhost:8008"], False)
125 , corsMethods = [methodGet, methodPost, methodPut, methodDelete]
126 , corsRequestHeaders = ["authorization", "content-type"]
127 , corsExposedHeaders = Nothing
128 , corsMaxAge = Just ( 60*60*24 ) -- one day
129 , corsVaryOrigin = False
130 , corsRequireOrigin = True
131 , corsIgnoreFailures = False
134 --let warpS = Warp.setPort (8008 :: Int) -- (env^.settings.appPort)
135 -- $ Warp.defaultSettings
137 --pure (warpS, logWare $ checkOriginAndHost $ corsMiddleware $ serverApp)
138 pure $ checkOriginAndHost $ corsMiddleware $ serverApp
142 ---------------------------------------------------------------------
143 type PortNumber = Int
144 ---------------------------------------------------------------------
147 -- | API for serving @swagger.json@
148 type SwaggerAPI = SwaggerSchemaUI "swagger-ui" "swagger.json"
150 -- | API for serving main operational routes of @gargantext.org@
151 type GargAPI = "user" :> Summary "First user endpoint"
154 :<|> "node" :> Summary "Node endpoint"
155 :> Capture "id" Int :> NodeAPI
157 :<|> "corpus":> Summary "Corpus endpoint"
158 :> Capture "id" Int :> NodeAPI
160 :<|> "nodes" :> Summary "Nodes endpoint"
161 :> ReqBody '[JSON] [Int] :> NodesAPI
163 -- :<|> "counts" :> Stream GET NewLineFraming '[JSON] Count :> CountAPI
164 :<|> "count" :> Summary "Count endpoint"
165 :> ReqBody '[JSON] Query :> CountAPI
171 -- :<|> "list" :> Capture "id" Int :> NodeAPI
172 -- :<|> "ngrams" :> Capture "id" Int :> NodeAPI
173 -- :<|> "auth" :> Capture "id" Int :> NodeAPI
174 ---------------------------------------------------------------------
175 type SwaggerFrontAPI = SwaggerAPI :<|> FrontEndAPI
177 type API = SwaggerFrontAPI :<|> GargAPI
179 ---------------------------------------------------------------------
180 -- | Server declaration
181 server :: Connection -> Server API
182 server conn = swaggerFront
189 ---------------------------------------------------------------------
190 swaggerFront :: Server SwaggerFrontAPI
191 swaggerFront = schemaUiServer swaggerDoc
194 gargMock :: Server GargAPI
195 gargMock = mock apiGarg Proxy
197 ---------------------------------------------------------------------
198 app :: Connection -> Application
199 app = serve api . server
201 appMock :: Application
202 appMock = serve api (swaggerFront :<|> gargMock)
204 ---------------------------------------------------------------------
208 apiGarg :: Proxy GargAPI
210 ---------------------------------------------------------------------
212 schemaUiServer :: (Server api ~ Handler Swagger)
213 => Swagger -> Server (SwaggerSchemaUI' dir api)
214 schemaUiServer = swaggerSchemaUIServer
217 -- Type Familiy for the Documentation
218 type family TypeName (x :: *) :: Symbol where
220 TypeName Text = "Text"
221 TypeName x = GenericTypeName x (Rep x ())
223 type family GenericTypeName t (r :: *) :: Symbol where
224 GenericTypeName t (D1 ('MetaData name mod pkg nt) f x) = name
226 type Desc t n = Description (AppendSymbol (TypeName t) (AppendSymbol " | " n))
229 -- | Swagger Specifications
230 swaggerDoc :: Swagger
231 swaggerDoc = toSwagger (Proxy :: Proxy GargAPI)
232 & info.title .~ "Gargantext"
233 & info.version .~ "0.1.0"
234 -- & info.base_url ?~ (URL "http://gargantext.org/")
235 & info.description ?~ "REST API specifications"
236 -- & tags .~ Set.fromList [Tag "Garg" (Just "Main perations") Nothing]
237 & applyTagsFor (subOperations (Proxy :: Proxy GargAPI)(Proxy :: Proxy GargAPI))
238 ["Garg" & description ?~ "Main operations"]
239 & info.license ?~ ("AGPLV3 (English) and CECILL (French)" & url ?~ URL urlLicence )
241 urlLicence = "https://gitlab.iscpif.fr/gargantext/haskell-gargantext/blob/master/LICENSE"
243 -- | Output generated @swagger.json@ file for the @'TodoAPI'@.
244 swaggerWriteJSON :: IO ()
245 swaggerWriteJSON = BL8.writeFile "swagger.json" (encodePretty swaggerDoc)
247 portRouteInfo :: PortNumber -> IO ()
248 portRouteInfo port = do
249 print (pack " ----Main Routes----- ")
250 print ("http://localhost:" <> show port <> "/index.html")
251 print ("http://localhost:" <> show port <> "/swagger-ui")
253 -- | startGargantext takes as parameters port number and Ini file.
254 startGargantext :: PortNumber -> FilePath -> IO ()
255 startGargantext port file = do
257 param <- databaseParameters file
258 conn <- connect param
263 startGargantextMock :: PortNumber -> IO ()
264 startGargantextMock port = do
267 application <- makeApp (FireWall False)