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 || (not $ 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 = Nothing -- == /*
125 , corsMethods = [ methodGet , methodPost , methodPut
126 , methodDelete, methodOptions, methodHead]
127 , corsRequestHeaders = ["authorization", "content-type"]
128 , corsExposedHeaders = Nothing
129 , corsMaxAge = Just ( 60*60*24 ) -- one day
130 , corsVaryOrigin = False
131 , corsRequireOrigin = False
132 , corsIgnoreFailures = False
135 --let warpS = Warp.setPort (8008 :: Int) -- (env^.settings.appPort)
136 -- $ Warp.defaultSettings
138 --pure (warpS, logWare $ checkOriginAndHost $ corsMiddleware $ serverApp)
139 pure $ checkOriginAndHost $ corsMiddleware $ serverApp
143 ---------------------------------------------------------------------
144 type PortNumber = Int
145 ---------------------------------------------------------------------
148 -- | API for serving @swagger.json@
149 type SwaggerAPI = SwaggerSchemaUI "swagger-ui" "swagger.json"
151 -- | API for serving main operational routes of @gargantext.org@
152 type GargAPI = "user" :> Summary "First user endpoint"
155 :<|> "node" :> Summary "Node endpoint"
156 :> Capture "id" Int :> NodeAPI
158 :<|> "corpus":> Summary "Corpus endpoint"
159 :> Capture "id" Int :> NodeAPI
161 :<|> "nodes" :> Summary "Nodes endpoint"
162 :> ReqBody '[JSON] [Int] :> NodesAPI
164 -- :<|> "counts" :> Stream GET NewLineFraming '[JSON] Count :> CountAPI
165 :<|> "count" :> Summary "Count endpoint"
166 :> ReqBody '[JSON] Query :> CountAPI
172 -- :<|> "list" :> Capture "id" Int :> NodeAPI
173 -- :<|> "ngrams" :> Capture "id" Int :> NodeAPI
174 -- :<|> "auth" :> Capture "id" Int :> NodeAPI
175 ---------------------------------------------------------------------
176 type SwaggerFrontAPI = SwaggerAPI :<|> FrontEndAPI
178 type API = SwaggerFrontAPI :<|> GargAPI
180 ---------------------------------------------------------------------
181 -- | Server declaration
182 server :: Connection -> Server API
183 server conn = swaggerFront
190 ---------------------------------------------------------------------
191 swaggerFront :: Server SwaggerFrontAPI
192 swaggerFront = schemaUiServer swaggerDoc
195 gargMock :: Server GargAPI
196 gargMock = mock apiGarg Proxy
198 ---------------------------------------------------------------------
199 app :: Connection -> Application
200 app = serve api . server
202 appMock :: Application
203 appMock = serve api (swaggerFront :<|> gargMock)
205 ---------------------------------------------------------------------
209 apiGarg :: Proxy GargAPI
211 ---------------------------------------------------------------------
213 schemaUiServer :: (Server api ~ Handler Swagger)
214 => Swagger -> Server (SwaggerSchemaUI' dir api)
215 schemaUiServer = swaggerSchemaUIServer
218 -- Type Familiy for the Documentation
219 type family TypeName (x :: *) :: Symbol where
221 TypeName Text = "Text"
222 TypeName x = GenericTypeName x (Rep x ())
224 type family GenericTypeName t (r :: *) :: Symbol where
225 GenericTypeName t (D1 ('MetaData name mod pkg nt) f x) = name
227 type Desc t n = Description (AppendSymbol (TypeName t) (AppendSymbol " | " n))
230 -- | Swagger Specifications
231 swaggerDoc :: Swagger
232 swaggerDoc = toSwagger (Proxy :: Proxy GargAPI)
233 & info.title .~ "Gargantext"
234 & info.version .~ "0.1.0"
235 -- & info.base_url ?~ (URL "http://gargantext.org/")
236 & info.description ?~ "REST API specifications"
237 -- & tags .~ Set.fromList [Tag "Garg" (Just "Main perations") Nothing]
238 & applyTagsFor (subOperations (Proxy :: Proxy GargAPI)(Proxy :: Proxy GargAPI))
239 ["Garg" & description ?~ "Main operations"]
240 & info.license ?~ ("AGPLV3 (English) and CECILL (French)" & url ?~ URL urlLicence )
242 urlLicence = "https://gitlab.iscpif.fr/gargantext/haskell-gargantext/blob/master/LICENSE"
244 -- | Output generated @swagger.json@ file for the @'TodoAPI'@.
245 swaggerWriteJSON :: IO ()
246 swaggerWriteJSON = BL8.writeFile "swagger.json" (encodePretty swaggerDoc)
248 portRouteInfo :: PortNumber -> IO ()
249 portRouteInfo port = do
250 print (pack " ----Main Routes----- ")
251 print ("http://localhost:" <> show port <> "/index.html")
252 print ("http://localhost:" <> show port <> "/swagger-ui")
254 -- | startGargantext takes as parameters port number and Ini file.
255 startGargantext :: PortNumber -> FilePath -> IO ()
256 startGargantext port file = do
258 param <- databaseParameters file
259 conn <- connect param
264 startGargantextMock :: PortNumber -> IO ()
265 startGargantextMock port = do
268 application <- makeApp (FireWall False)