2 Module : Gargantext.API.Node
3 Description : Server API
4 Copyright : (c) CNRS, 2017-Present
5 License : AGPL + CECILL v3
6 Maintainer : team@gargantext.org
7 Stability : experimental
13 {-# OPTIONS_GHC -fno-warn-name-shadowing -fno-warn-orphans #-}
15 {-# LANGUAGE DataKinds #-}
16 {-# LANGUAGE DeriveGeneric #-}
17 {-# LANGUAGE FlexibleContexts #-}
18 {-# LANGUAGE NoImplicitPrelude #-}
19 {-# LANGUAGE OverloadedStrings #-}
20 {-# LANGUAGE TemplateHaskell #-}
21 {-# LANGUAGE TypeOperators #-}
23 -------------------------------------------------------------------
24 module Gargantext.API.Node
25 ( module Gargantext.API.Node
27 , HyperdataAnnuaire(..)
29 , HyperdataResource(..)
31 , HyperdataDocument(..)
32 , HyperdataDocumentV3(..)
34 -------------------------------------------------------------------
35 import Control.Lens (prism', set)
36 import Control.Monad.IO.Class (liftIO)
37 import Control.Monad ((>>))
38 --import System.IO (putStrLn, readFile)
40 import Data.Aeson (FromJSON, ToJSON)
41 --import Data.Text (Text(), pack)
42 import Data.Text (Text())
44 import Data.Time (UTCTime)
46 import Database.PostgreSQL.Simple (Connection)
48 import GHC.Generics (Generic)
51 import Gargantext.API.Ngrams (TabType(..), TableNgramsApi, TableNgramsApiGet, tableNgramsPatch, getTableNgrams, NgramsIdPatchsFeed, NgramsIdPatchsBack)
52 import Gargantext.Prelude
53 import Gargantext.Database.Types.Node
54 import Gargantext.Database.Utils (runCmd)
55 import Gargantext.Database.Schema.Node ( getNodesWithParentId, getNode, deleteNode, deleteNodes, mk, JSONB)
56 import Gargantext.Database.Node.Children (getChildren)
57 import qualified Gargantext.Database.Node.Update as U (update, Update(..))
58 import Gargantext.Database.Facet (FacetDoc , runViewDocuments', OrderBy(..),FacetChart,runViewAuthorsDoc)
59 import Gargantext.Database.Tree (treeDB, HasTreeError(..), TreeError(..))
60 import Gargantext.Database.Schema.NodeNode (nodesToFavorite, nodesToTrash)
61 import Gargantext.API.Search ( SearchAPI, searchIn, SearchInQuery)
63 --import Gargantext.Text.Flow
64 import Gargantext.Viz.Graph hiding (Node)-- (Graph(_graph_metadata),LegendField(..), GraphMetadata(..),readGraphFromJson,defaultGraph)
65 -- import Gargantext.Core (Lang(..))
66 import Gargantext.Core.Types (Offset, Limit)
67 import Gargantext.Core.Types.Main (Tree, NodeTree, ListId, CorpusId, ContactId)
68 -- import Gargantext.Text.Terms (TermType(..))
70 import Test.QuickCheck (elements)
71 import Test.QuickCheck.Arbitrary (Arbitrary, arbitrary)
73 -------------------------------------------------------------------
74 -- | TODO : access by admin only
75 type NodesAPI = Delete '[JSON] Int
78 -- Be careful: really delete nodes
79 -- Access by admin only
80 nodesAPI :: Connection -> [NodeId] -> Server NodesAPI
81 nodesAPI conn ids = deleteNodes' conn ids
83 ------------------------------------------------------------------------
84 -- | TODO: access by admin only
85 -- To manager the Users roots
86 type Roots = Get '[JSON] [NodeAny]
87 :<|> Post '[JSON] Int -- TODO
88 :<|> Put '[JSON] Int -- TODO
89 :<|> Delete '[JSON] Int -- TODO
91 -- | TODO: access by admin only
92 roots :: Connection -> Server Roots
93 roots conn = liftIO (putStrLn ( "/user" :: Text) >> getNodesWithParentId 0 Nothing conn)
94 :<|> pure (panic "not implemented yet") -- TODO
95 :<|> pure (panic "not implemented yet") -- TODO
96 :<|> pure (panic "not implemented yet") -- TODO
98 -------------------------------------------------------------------
99 -- | Node API Types management
100 -- TODO : access by users
101 type NodeAPI a = Get '[JSON] (Node a)
102 :<|> "rename" :> RenameApi
105 :<|> Delete '[JSON] Int
106 :<|> "children" :> ChildrenApi a
109 :<|> "table" :> TableApi
110 :<|> "list" :> TableNgramsApi
111 :<|> "listGet" :> TableNgramsApiGet
112 :<|> "pairing" :> PairingApi
114 :<|> "chart" :> ChartApi
115 :<|> "favorites" :> FavApi
116 :<|> "documents" :> DocsApi
117 :<|> "search":> Summary "Node Search"
118 :> ReqBody '[JSON] SearchInQuery
119 :> QueryParam "offset" Int
120 :> QueryParam "limit" Int
121 :> QueryParam "order" OrderBy
124 type RenameApi = Summary " RenameNode Node"
125 :> ReqBody '[JSON] RenameNode
128 type PostNodeApi = Summary " PostNode Node with ParentId as {id}"
129 :> ReqBody '[JSON] PostNode
130 :> Post '[JSON] [Int]
132 type ChildrenApi a = Summary " Summary children"
133 :> QueryParam "type" NodeType
134 :> QueryParam "offset" Int
135 :> QueryParam "limit" Int
136 :> Get '[JSON] [Node a]
137 ------------------------------------------------------------------------
138 -- TODO: make the NodeId type indexed by `a`, then we no longer need the proxy.
139 nodeAPI :: JSONB a => Connection -> proxy a -> NodeId -> Server (NodeAPI a)
141 = liftIO (getNode conn id p)
143 :<|> postNode conn id
145 :<|> deleteNode' conn id
146 :<|> getChildren' conn id p
149 :<|> getTable conn id
150 :<|> tableNgramsPatch' conn id
151 :<|> getTableNgrams conn id
152 :<|> getPairing conn id
154 :<|> getChart conn id
157 :<|> searchIn conn id
162 ------------------------------------------------------------------------
163 data RenameNode = RenameNode { r_name :: Text }
166 instance FromJSON RenameNode
167 instance ToJSON RenameNode
168 instance ToSchema RenameNode
169 instance Arbitrary RenameNode where
170 arbitrary = elements [RenameNode "test"]
171 ------------------------------------------------------------------------
172 data PostNode = PostNode { pn_name :: Text
173 , pn_typename :: NodeType}
176 instance FromJSON PostNode
177 instance ToJSON PostNode
178 instance ToSchema PostNode
179 instance Arbitrary PostNode where
180 arbitrary = elements [PostNode "Node test" NodeCorpus]
182 ------------------------------------------------------------------------
183 type DocsApi = Summary "Docs : Move to trash"
184 :> ReqBody '[JSON] Documents
185 :> Delete '[JSON] [Int]
187 data Documents = Documents { documents :: [NodeId]}
190 instance FromJSON Documents
191 instance ToJSON Documents
192 instance ToSchema Documents
194 delDocs :: Connection -> CorpusId -> Documents -> Handler [Int]
195 delDocs c cId ds = liftIO $ nodesToTrash c
196 $ map (\n -> (cId, n, True)) $ documents ds
198 ------------------------------------------------------------------------
199 type FavApi = Summary " Favorites label"
200 :> ReqBody '[JSON] Favorites
202 :<|> Summary " Favorites unlabel"
203 :> ReqBody '[JSON] Favorites
204 :> Delete '[JSON] [Int]
206 data Favorites = Favorites { favorites :: [NodeId]}
209 instance FromJSON Favorites
210 instance ToJSON Favorites
211 instance ToSchema Favorites
213 putFav :: Connection -> CorpusId -> Favorites -> Handler [Int]
214 putFav c cId fs = liftIO $ nodesToFavorite c
215 $ map (\n -> (cId, n, True)) $ favorites fs
217 delFav :: Connection -> CorpusId -> Favorites -> Handler [Int]
218 delFav c cId fs = liftIO $ nodesToFavorite c
219 $ map (\n -> (cId, n, False)) $ favorites fs
221 favApi :: Connection -> CorpusId -> (Favorites -> Handler [Int])
222 :<|> (Favorites -> Handler [Int])
223 favApi c cId = putFav c cId :<|> delFav c cId
225 ------------------------------------------------------------------------
226 type TableApi = Summary " Table API"
227 :> QueryParam "view" TabType
228 :> QueryParam "offset" Int
229 :> QueryParam "limit" Int
230 :> QueryParam "order" OrderBy
231 :> Get '[JSON] [FacetDoc]
233 -- TODO adapt FacetDoc -> ListDoc (and add type of document as column)
234 type PairingApi = Summary " Pairing API"
235 :> QueryParam "view" TabType -- TODO change TabType -> DocType (CorpusId for pairing)
236 :> QueryParam "offset" Int
237 :> QueryParam "limit" Int
238 :> QueryParam "order" OrderBy
239 :> Get '[JSON] [FacetDoc]
241 ------------------------------------------------------------------------
242 type ChartApi = Summary " Chart API"
243 :> QueryParam "from" UTCTime
244 :> QueryParam "to" UTCTime
245 :> Get '[JSON] [FacetChart]
247 -- Depending on the Type of the Node, we could post
248 -- New documents for a corpus
249 -- New map list terms
250 -- :<|> "process" :> MultipartForm MultipartData :> Post '[JSON] Text
252 -- To launch a query and update the corpus
253 -- :<|> "query" :> Capture "string" Text :> Get '[JSON] Text
255 ------------------------------------------------------------------------
256 type GraphAPI = Get '[JSON] Graph
257 graphAPI :: Connection -> NodeId -> Server GraphAPI
258 graphAPI c nId = liftIO $ graphAPI' c nId
260 graphAPI' :: Connection -> NodeId -> IO Graph
263 nodeGraph <- getNode c nId HyperdataGraph
264 let title = "IMT - Scientific publications - 1982-2017 - English"
265 let metadata = GraphMetadata title [maybe 0 identity $ _node_parentId nodeGraph] [ LegendField 6 "#FFF" "Data processing"
266 , LegendField 7 "#FFF" "Networks"
267 , LegendField 1 "#FFF" "Material science"
268 , LegendField 5 "#FFF" "Energy / Environment"
270 -- (map (\n -> LegendField n "#FFFFFF" (pack $ show n)) [1..10])
272 graph <- set graph_metadata (Just metadata) <$> maybe defaultGraph identity <$> readGraphFromJson "purescript-gargantext/dist/examples/imtNew.json"
277 -- t <- textFlow (Mono EN) (Contexts contextText)
278 -- liftIO $ liftIO $ pure $ maybe t identity maybeGraph
279 -- TODO what do we get about the node? to replace contextText
281 -- TODO(orphan): There should be a proper APIError data type with a case TreeError.
282 instance HasTreeError ServantErr where
283 _TreeError = prism' mk (const Nothing) -- Note a prism
285 mk NoRoot = err404 { errBody = "Root node not found" }
286 mk EmptyRoot = err500 { errBody = "Root node should not be empty" }
287 mk TooManyRoots = err500 { errBody = "Too many root nodes" }
289 type TreeAPI = Get '[JSON] (Tree NodeTree)
290 treeAPI :: Connection -> NodeId -> Server TreeAPI
293 ------------------------------------------------------------------------
294 -- | Check if the name is less than 255 char
295 rename :: Connection -> NodeId -> RenameNode -> Handler [Int]
296 rename c nId (RenameNode name) = liftIO $ U.update (U.Rename nId name) c
298 getTable :: Connection -> NodeId -> Maybe TabType
299 -> Maybe Offset -> Maybe Limit
300 -> Maybe OrderBy -> Handler [FacetDoc]
301 getTable c cId ft o l order = liftIO $ case ft of
302 (Just Docs) -> runViewDocuments' c cId False o l order
303 (Just Trash) -> runViewDocuments' c cId True o l order
304 _ -> panic "not implemented"
306 getPairing :: Connection -> ContactId -> Maybe TabType
307 -> Maybe Offset -> Maybe Limit
308 -> Maybe OrderBy -> Handler [FacetDoc]
309 getPairing c cId ft o l order = liftIO $ case ft of
310 (Just Docs) -> runViewAuthorsDoc c cId False o l order
311 (Just Trash) -> runViewAuthorsDoc c cId True o l order
312 _ -> panic "not implemented"
315 getChart :: Connection -> NodeId -> Maybe UTCTime -> Maybe UTCTime
316 -> Handler [FacetChart]
317 getChart _ _ _ _ = undefined -- TODO
319 postNode :: Connection -> NodeId -> PostNode -> Handler [Int]
320 postNode c pId (PostNode name nt) = liftIO $ mk c nt (Just pId) name
322 putNode :: Connection -> NodeId -> Handler Int
323 putNode = undefined -- TODO
325 deleteNodes' :: Connection -> [NodeId] -> Handler Int
326 deleteNodes' conn ids = liftIO (runCmd conn $ deleteNodes ids)
328 deleteNode' :: Connection -> NodeId -> Handler Int
329 deleteNode' conn id = liftIO (runCmd conn $ deleteNode id)
331 getChildren' :: JSONB a => Connection -> NodeId -> proxy a -> Maybe NodeType
332 -> Maybe Int -> Maybe Int -> Handler [Node a]
333 getChildren' conn pId p nodeType offset limit = liftIO (getChildren conn pId p nodeType offset limit)
335 tableNgramsPatch' :: Connection -> CorpusId -> Maybe ListId -> NgramsIdPatchsFeed -> Handler NgramsIdPatchsBack
336 tableNgramsPatch' c cId mL ns = liftIO $ tableNgramsPatch c cId mL ns
338 query :: Text -> Handler Text
343 -- TODO Is it possible to adapt the function according to iValue input ?
344 --upload :: MultipartData -> Handler Text
345 --upload multipartData = do
347 -- putStrLn "Inputs:"
348 -- forM_ (inputs multipartData) $ \input ->
349 -- putStrLn $ " " <> show (iName input)
350 -- <> " -> " <> show (iValue input)
352 -- forM_ (files multipartData) $ \file -> do
353 -- content <- readFile (fdFilePath file)
354 -- putStrLn $ "Content of " <> show (fdFileName file)
355 -- <> " at " <> fdFilePath file
357 -- pure (pack "Data loaded")