2 Module : Gargantext.API.Node.Corpus.New
3 Description : New corpus API
4 Copyright : (c) CNRS, 2017-Present
5 License : AGPL + CECILL v3
6 Maintainer : team@gargantext.org
7 Stability : experimental
10 New corpus means either:
12 - new data in existing corpus
15 {-# LANGUAGE TemplateHaskell #-}
16 {-# LANGUAGE TypeOperators #-}
18 module Gargantext.API.Node.Corpus.New
21 import Control.Lens hiding (elements, Empty)
23 import Data.Aeson.TH (deriveJSON)
25 import Data.Maybe (fromMaybe)
27 import Data.Text (Text)
28 import qualified Data.Text as T
29 import GHC.Generics (Generic)
31 import Servant.Job.Utils (jsonOptions)
32 -- import Servant.Multipart
33 -- import Test.QuickCheck (elements)
34 import Test.QuickCheck.Arbitrary
36 import Gargantext.Prelude
38 import Gargantext.API.Admin.Orchestrator.Types (JobLog(..), AsyncJobs)
39 import qualified Gargantext.API.Admin.Orchestrator.Types as T
40 import Gargantext.API.Admin.Types (HasSettings)
41 import Gargantext.API.Node.Corpus.New.File
42 import Gargantext.API.Node.Types
43 import Gargantext.Core (Lang(..){-, allLangs-})
44 import Gargantext.Database.Action.Mail (sendMail)
45 import Gargantext.Core.Types.Individu (User(..))
46 import Gargantext.Core.Utils.Prefix (unPrefix, unPrefixSwagger)
47 import Gargantext.Database.Action.Flow (FlowCmdM, flowCorpus, getDataText, flowDataText, TermType(..), DataOrigin(..){-, allDataOrigins-})
48 import Gargantext.Database.Action.User (getUserId)
49 import Gargantext.Database.Action.Node (mkNodeWithParent)
50 import Gargantext.Database.Admin.Types.Hyperdata
51 import Gargantext.Database.Admin.Types.Node (CorpusId, NodeType(..), UserId)
52 import Gargantext.Database.Query.Table.Node (getNodeWith)
53 import Gargantext.Database.Query.Table.Node.UpdateOpaleye (updateHyperdata)
54 import Gargantext.Database.Schema.Node (node_hyperdata)
55 import qualified Gargantext.Prelude.GargDB as GargDB
56 import qualified Gargantext.Core.Text.Corpus.API as API
57 import qualified Gargantext.Core.Text.Corpus.Parsers as Parser (FileFormat(..), parseFormat)
59 ------------------------------------------------------------------------
61 data Query = Query { query_query :: Text
62 , query_node_id :: Int
64 , query_databases :: [DataOrigin]
66 deriving (Eq, Generic)
68 deriveJSON (unPrefix "query_") 'Query
70 instance Arbitrary Query where
71 arbitrary = elements [ Query q n la fs
72 | q <- ["honeybee* AND collapse"
77 , fs <- take 3 $ repeat allDataOrigins
80 instance ToSchema Query where
81 declareNamedSchema = genericDeclareNamedSchema (unPrefixSwagger "query_")
84 ------------------------------------------------------------------------
90 type PostApi = Summary "New Corpus endpoint"
91 :> ReqBody '[JSON] Query
92 :> Post '[JSON] CorpusId
93 type GetApi = Get '[JSON] ApiInfo
96 -- | TODO manage several apis
98 -- TODO this is only the POST
100 api :: (FlowCmdM env err m) => UserId -> Query -> m CorpusId
101 api uid (Query q _ as) = do
102 cId <- case head as of
103 Nothing -> flowCorpusSearchInDatabase (UserDBId uid) EN q
104 Just API.All -> flowCorpusSearchInDatabase (UserDBId uid) EN q
106 docs <- liftBase $ API.get a q (Just 1000)
107 cId' <- flowCorpus (UserDBId uid) (Left q) (Multi EN) [docs]
113 ------------------------------------------------
114 -- TODO use this route for Client implementation
115 data ApiInfo = ApiInfo { api_info :: [API.ExternalAPIs]}
117 instance Arbitrary ApiInfo where
118 arbitrary = ApiInfo <$> arbitrary
120 deriveJSON (unPrefix "") 'ApiInfo
122 instance ToSchema ApiInfo
124 info :: FlowCmdM env err m => UserId -> m ApiInfo
125 info _u = pure $ ApiInfo API.externalAPIs
127 ------------------------------------------------------------------------
129 data Database = Empty
134 deriving (Eq, Show, Generic)
136 deriveJSON (unPrefix "") ''Database
137 instance ToSchema Database
139 database2origin :: Database -> DataOrigin
140 database2origin Empty = InternalOrigin T.IsTex
141 database2origin PubMed = ExternalOrigin T.PubMed
142 database2origin HAL = ExternalOrigin T.HAL
143 database2origin IsTex = ExternalOrigin T.IsTex
144 database2origin Isidore = ExternalOrigin T.Isidore
146 ------------------------------------------------------------------------
147 data WithQuery = WithQuery
149 , _wq_databases :: !Database
151 , _wq_node_id :: !Int
155 makeLenses ''WithQuery
156 instance FromJSON WithQuery where
157 parseJSON = genericParseJSON $ jsonOptions "_wq_"
158 instance ToJSON WithQuery where
159 toJSON = genericToJSON $ jsonOptions "_wq_"
160 instance ToSchema WithQuery where
161 declareNamedSchema = genericDeclareNamedSchema (unPrefixSwagger "_wq_")
163 ------------------------------------------------------------------------
165 type AddWithQuery = Summary "Add with Query to corpus endpoint"
167 :> Capture "corpus_id" CorpusId
169 :> AsyncJobs JobLog '[JSON] WithQuery JobLog
172 type AddWithFile = Summary "Add with MultipartData to corpus endpoint"
174 :> Capture "corpus_id" CorpusId
177 :> MultipartForm Mem (MultipartData Mem)
178 :> QueryParam "fileType" FileType
180 :> AsyncJobs JobLog '[JSON] () JobLog
184 ------------------------------------------------------------------------
185 -- TODO WithQuery also has a corpus id
186 addToCorpusWithQuery :: FlowCmdM env err m
193 addToCorpusWithQuery user cid (WithQuery q dbs l _nid) maybeLimit logStatus = do
195 logStatus JobLog { _scst_succeeded = Just 0
196 , _scst_failed = Just 0
197 , _scst_remaining = Just 5
198 , _scst_events = Just []
200 printDebug "addToCorpusWithQuery" (cid, dbs)
202 -- TODO if cid is folder -> create Corpus
203 -- if cid is corpus -> add to corpus
204 -- if cid is root -> create corpus in Private
205 txts <- mapM (\db -> getDataText db (Multi l) q maybeLimit) [database2origin dbs]
207 logStatus JobLog { _scst_succeeded = Just 2
208 , _scst_failed = Just 0
209 , _scst_remaining = Just 1
210 , _scst_events = Just []
213 cids <- mapM (\txt -> flowDataText user txt (Multi l) cid) txts
214 printDebug "corpus id" cids
215 printDebug "sending email" ("xxxxxxxxxxxxxxxxxxxxx" :: Text)
218 pure JobLog { _scst_succeeded = Just 3
219 , _scst_failed = Just 0
220 , _scst_remaining = Just 0
221 , _scst_events = Just []
225 type AddWithForm = Summary "Add with FormUrlEncoded to corpus endpoint"
227 :> Capture "corpus_id" CorpusId
231 :> AsyncJobs JobLog '[FormUrlEncoded] NewWithForm JobLog
233 addToCorpusWithForm :: FlowCmdM env err m
239 addToCorpusWithForm user cid (NewWithForm ft d l _n) logStatus = do
241 printDebug "[addToCorpusWithForm] Parsing corpus: " cid
242 printDebug "[addToCorpusWithForm] fileType" ft
243 logStatus JobLog { _scst_succeeded = Just 0
244 , _scst_failed = Just 0
245 , _scst_remaining = Just 2
246 , _scst_events = Just []
250 CSV_HAL -> Parser.parseFormat Parser.CsvHal
251 CSV -> Parser.parseFormat Parser.CsvGargV3
252 WOS -> Parser.parseFormat Parser.WOS
253 PresseRIS -> Parser.parseFormat Parser.RisPresse
255 -- TODO granularity of the logStatus
256 docs <- liftBase $ splitEvery 500
260 printDebug "Parsing corpus finished : " cid
261 logStatus JobLog { _scst_succeeded = Just 1
262 , _scst_failed = Just 0
263 , _scst_remaining = Just 1
264 , _scst_events = Just []
268 printDebug "Starting extraction : " cid
269 -- TODO granularity of the logStatus
270 _cid' <- flowCorpus user
272 (Multi $ fromMaybe EN l)
273 (map (map toHyperdataDocument) docs)
275 printDebug "Extraction finished : " cid
276 printDebug "sending email" ("xxxxxxxxxxxxxxxxxxxxx" :: Text)
279 pure JobLog { _scst_succeeded = Just 2
280 , _scst_failed = Just 0
281 , _scst_remaining = Just 0
282 , _scst_events = Just []
286 addToCorpusWithFile :: FlowCmdM env err m
292 addToCorpusWithFile cid input filetype logStatus = do
293 logStatus JobLog { _scst_succeeded = Just 10
294 , _scst_failed = Just 2
295 , _scst_remaining = Just 138
296 , _scst_events = Just []
298 printDebug "addToCorpusWithFile" cid
299 _h <- postUpload cid filetype input
301 pure JobLog { _scst_succeeded = Just 137
302 , _scst_failed = Just 13
303 , _scst_remaining = Just 0
304 , _scst_events = Just []
310 type AddWithFile = Summary "Add with FileUrlEncoded to corpus endpoint"
312 :> Capture "corpus_id" CorpusId
316 :> AsyncJobs JobLog '[FormUrlEncoded] NewWithFile JobLog
318 addToCorpusWithFile :: (HasSettings env, FlowCmdM env err m)
324 addToCorpusWithFile user cid nwf@(NewWithFile _d _l fName) logStatus = do
326 printDebug "[addToCorpusWithFile] Uploading file to corpus: " cid
327 logStatus JobLog { _scst_succeeded = Just 0
328 , _scst_failed = Just 0
329 , _scst_remaining = Just 1
330 , _scst_events = Just []
333 fPath <- GargDB.writeFile nwf
334 printDebug "[addToCorpusWithFile] File saved as: " fPath
336 uId <- getUserId user
337 nIds <- mkNodeWithParent NodeFile (Just cid) uId fName
341 node <- getNodeWith nId (Proxy :: Proxy HyperdataFile)
342 let hl = node ^. node_hyperdata
343 _ <- updateHyperdata nId $ hl { _hff_name = fName
344 , _hff_path = T.pack fPath }
346 printDebug "[addToCorpusWithFile] Created node with id: " nId
349 printDebug "[addToCorpusWithFile] File upload to corpus finished: " cid
351 printDebug "sending email" ("xxxxxxxxxxxxxxxxxxxxx" :: Text)
354 pure $ JobLog { _scst_succeeded = Just 1
355 , _scst_failed = Just 0
356 , _scst_remaining = Just 0
357 , _scst_events = Just []