2 Module : Gargantext.API.Settings
3 Description : Settings of the API (Server and Client)
4 Copyright : (c) CNRS, 2017-Present
5 License : AGPL + CECILL v3
6 Maintainer : team@gargantext.org
7 Stability : experimental
12 {-# OPTIONS_GHC -fno-warn-name-shadowing #-}
14 {-# LANGUAGE NoImplicitPrelude #-}
15 {-# LANGUAGE DataKinds #-}
16 {-# LANGUAGE DeriveGeneric #-}
17 {-# LANGUAGE ScopedTypeVariables #-}
18 {-# LANGUAGE TemplateHaskell #-}
19 {-# LANGUAGE OverloadedStrings #-}
20 {-# LANGUAGE FlexibleContexts #-}
21 {-# LANGUAGE FlexibleInstances #-}
22 {-# LANGUAGE RankNTypes #-}
24 module Gargantext.API.Settings
27 import System.Directory
28 import System.Log.FastLogger
30 import GHC.Generics (Generic)
31 import Prelude (Bounded(), fail)
32 import System.Environment (lookupEnv)
33 import System.IO (FilePath)
34 import Database.PostgreSQL.Simple (Connection, connect)
35 import Network.HTTP.Client (Manager)
36 import Network.HTTP.Client.TLS (newTlsManager)
39 import Data.Maybe (fromMaybe)
40 import Data.Either (either)
41 import Data.JsonState (mkSaveState)
43 import Data.Text.Encoding (encodeUtf8)
44 import Data.Time.Units
45 import Data.ByteString.Lazy.Internal
48 import Servant.Client (BaseUrl, parseBaseUrl)
49 import Servant.Job.Async (newJobEnv, defaultSettings)
50 import Web.HttpApiData (parseUrlPiece)
51 import qualified Jose.Jwk as Jose
52 import qualified Jose.Jwa as Jose
54 import Control.Concurrent
55 import Control.Exception (finally)
56 import Control.Monad.Logger
57 import Control.Monad.Reader
59 import Gargantext.Prelude
60 import Gargantext.Database.Utils (databaseParameters, HasConnection(..), Cmd', runCmd)
61 import Gargantext.API.Ngrams (NgramsRepo, HasRepoVar(..), HasRepoSaver(..), initMockRepo, r_version, saveRepo)
62 import Gargantext.API.Orchestrator.Types
66 data SendEmailType = SendEmailViaAws
69 deriving (Show, Read, Enum, Bounded, Generic)
72 data Settings = Settings
73 { _allowedOrigin :: ByteString -- ^ allowed origin for CORS
74 , _allowedHost :: ByteString -- ^ allowed host for CORS
75 , _appPort :: PortNumber
76 , _logLevelLimit :: LogLevel -- ^ log level from the monad-logger package
77 -- , _dbServer :: Text
78 -- ^ this is not used yet
79 , _jwtSecret :: Jose.Jwk -- ^ key from the jose-jwt package
80 , _sendLoginEmails :: SendEmailType
81 , _scrapydUrl :: BaseUrl
87 parseJwk :: Text -> Jose.Jwk
88 parseJwk secretStr = jwk
90 secretBs = encodeUtf8 secretStr
91 jwk = Jose.SymmetricJwk secretBs
94 (Just $ Jose.Signed Jose.HS256)
96 devSettings :: Settings
97 devSettings = Settings
98 { _allowedOrigin = "http://localhost:8008"
99 , _allowedHost = "localhost:3000"
101 , _logLevelLimit = LevelDebug
102 -- , _dbServer = "localhost"
103 -- generate with dd if=/dev/urandom bs=1 count=32 | base64
104 -- make sure jwtSecret differs between development and production, because you do not want
105 -- your production key inside source control.
106 , _jwtSecret = parseJwk "MVg0YAPVSPiYQc/qIs/rV/X32EFR0zOJWfHFgMbszMw="
107 , _sendLoginEmails = LogEmailToConsole
108 , _scrapydUrl = fromMaybe (panic "Invalid scrapy URL") $ parseBaseUrl "http://localhost:6800"
113 reqSetting :: FromHttpApiData a => Text -> IO a
115 e <- fromMaybe (panic $ "Missing " <> name) <$> lookupEnv (unpack name)
116 pure $ either (panic $ "Unable to parse " <> name) identity $ parseUrlPiece $ pack e
118 optSetting :: FromHttpApiData a => Text -> a -> IO a
119 optSetting name d = do
120 me <- lookupEnv (unpack name)
123 Just e -> pure $ either (panic $ "Unable to parse " <> name) identity $ parseUrlPiece $ pack e
125 --settingsFromEnvironment :: IO Settings
126 --settingsFromEnvironment =
127 -- Settings <$> (encodeUtf8 <$> reqSetting "ALLOWED_ORIGIN")
128 -- <*> (encodeUtf8 <$> reqSetting "ALLOWED_HOST")
129 -- <*> optSetting "PORT" 3000
130 -- <*> (parseLogLevel <$> optSetting "LOG_LEVEL" "warn")
131 -- <*> reqSetting "DB_SERVER"
132 -- <*> (parseJwk <$> reqSetting "JWT_SECRET")
133 -- <*> optSetting "SEND_EMAIL" SendEmailViaAws
135 data FireWall = FireWall { unFireWall :: Bool }
138 { _env_settings :: !Settings
139 , _env_logger :: !LoggerSet
140 , _env_conn :: !Connection
141 , _env_repo_var :: !(MVar NgramsRepo)
142 , _env_repo_saver :: !(IO ())
143 , _env_manager :: !Manager
144 , _env_self_url :: !BaseUrl
145 , _env_scrapers :: !ScrapersEnv
151 instance HasConnection Env where
152 connection = env_conn
154 instance HasRepoVar Env where
155 repoVar = env_repo_var
157 instance HasRepoSaver Env where
158 repoSaver = env_repo_saver
160 data MockEnv = MockEnv
161 { _menv_firewall :: !FireWall
167 repoSnapshot :: FilePath
168 repoSnapshot = "repo.json"
170 readRepo :: IO (MVar NgramsRepo)
172 -- | Does file exist ? :: Bool
173 repoFile <- doesFileExist repoSnapshot
175 -- | Is file not empty ? :: Bool
176 repoExists <- if repoFile
177 then (>0) <$> getFileSize repoSnapshot
183 e_repo <- eitherDecodeFileStrict repoSnapshot
184 repo <- either fail pure e_repo
185 let archive = repoSnapshot <> ".v" <> show (repo ^. r_version)
186 copyFile repoSnapshot archive
191 mkRepoSaver :: MVar NgramsRepo -> IO (IO ())
192 mkRepoSaver repo_var = do
193 saveAction <- mkSaveState (10 :: Second) repoSnapshot
194 pure $ readMVar repo_var >>= saveAction
196 newEnv :: PortNumber -> FilePath -> IO Env
197 newEnv port file = do
198 manager <- newTlsManager
199 settings <- pure (devSettings & appPort .~ port) -- TODO read from 'file'
200 when (port /= settings ^. appPort) $
201 panic "TODO: conflicting settings of port"
203 self_url <- parseBaseUrl $ "http://0.0.0.0:" <> show port
204 param <- databaseParameters file
205 conn <- connect param
208 repo_saver <- mkRepoSaver repo_var
209 scrapers_env <- newJobEnv defaultSettings manager
210 logger <- newStderrLoggerSet defaultBufSize
213 { _env_settings = settings
214 , _env_logger = logger
216 , _env_repo_var = repo_var
217 , _env_repo_saver = repo_saver
218 , _env_manager = manager
219 , _env_scrapers = scrapers_env
220 , _env_self_url = self_url
224 { _dev_env_conn :: !Connection
225 , _dev_env_repo_var :: !(MVar NgramsRepo)
226 , _dev_env_repo_saver :: !(IO ())
231 instance HasConnection DevEnv where
232 connection = dev_env_conn
234 instance HasRepoVar DevEnv where
235 repoVar = dev_env_repo_var
237 instance HasRepoSaver DevEnv where
238 repoSaver = dev_env_repo_saver
240 newDevEnvWith :: FilePath -> IO DevEnv
241 newDevEnvWith file = do
242 param <- databaseParameters file
243 conn <- connect param
244 repo_var <- newMVar initMockRepo
245 repo_saver <- mkRepoSaver repo_var
247 { _dev_env_conn = conn
248 , _dev_env_repo_var = repo_var
249 , _dev_env_repo_saver = repo_saver
252 newDevEnv :: IO DevEnv
253 newDevEnv = newDevEnvWith "gargantext.ini"
256 -- In particular this writes the repo file after running
258 -- This function is constrained to the DevEnv rather than
259 -- using HasConnection and HasRepoVar.
260 runCmdDev :: Show err => DevEnv -> Cmd' DevEnv err a -> IO a
262 (either (fail . show) pure =<< runCmd env f)
264 runReaderT saveRepo env
267 runCmdDevNoErr :: DevEnv -> Cmd' DevEnv () a -> IO a
268 runCmdDevNoErr = runCmdDev
271 runCmdDevServantErr :: DevEnv -> Cmd' DevEnv ServantErr a -> IO a
272 runCmdDevServantErr = runCmdDev