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)
62 --import Gargantext.Text.Flow
63 import Gargantext.Viz.Graph hiding (Node)-- (Graph(_graph_metadata),LegendField(..), GraphMetadata(..),readGraphFromJson,defaultGraph)
64 -- import Gargantext.Core (Lang(..))
65 import Gargantext.Core.Types (Offset, Limit)
66 import Gargantext.Core.Types.Main (Tree, NodeTree, ListId, CorpusId, ContactId)
67 -- import Gargantext.Text.Terms (TermType(..))
69 import Test.QuickCheck (elements)
70 import Test.QuickCheck.Arbitrary (Arbitrary, arbitrary)
72 -------------------------------------------------------------------
73 -- | TODO : access by admin only
74 type NodesAPI = Delete '[JSON] Int
77 -- Be careful: really delete nodes
78 -- Access by admin only
79 nodesAPI :: Connection -> [NodeId] -> Server NodesAPI
80 nodesAPI conn ids = deleteNodes' conn ids
82 ------------------------------------------------------------------------
83 -- | TODO: access by admin only
84 -- To manager the Users roots
85 type Roots = Get '[JSON] [NodeAny]
86 :<|> Post '[JSON] Int -- TODO
87 :<|> Put '[JSON] Int -- TODO
88 :<|> Delete '[JSON] Int -- TODO
90 -- | TODO: access by admin only
91 roots :: Connection -> Server Roots
92 roots conn = liftIO (putStrLn ( "/user" :: Text) >> getNodesWithParentId 0 Nothing conn)
93 :<|> pure (panic "not implemented yet") -- TODO
94 :<|> pure (panic "not implemented yet") -- TODO
95 :<|> pure (panic "not implemented yet") -- TODO
97 -------------------------------------------------------------------
98 -- | Node API Types management
99 -- TODO : access by users
100 type NodeAPI a = Get '[JSON] (Node a)
101 :<|> "rename" :> RenameApi
104 :<|> Delete '[JSON] Int
105 :<|> "children" :> ChildrenApi a
108 :<|> "table" :> TableApi
109 :<|> "list" :> TableNgramsApi
110 :<|> "listGet" :> TableNgramsApiGet
111 :<|> "pairing" :> PairingApi
113 :<|> "chart" :> ChartApi
114 :<|> "favorites" :> FavApi
115 :<|> "documents" :> DocsApi
117 type RenameApi = Summary " RenameNode Node"
118 :> ReqBody '[JSON] RenameNode
121 type PostNodeApi = Summary " PostNode Node with ParentId as {id}"
122 :> ReqBody '[JSON] PostNode
123 :> Post '[JSON] [Int]
125 type ChildrenApi a = Summary " Summary children"
126 :> QueryParam "type" NodeType
127 :> QueryParam "offset" Int
128 :> QueryParam "limit" Int
129 :> Get '[JSON] [Node a]
130 ------------------------------------------------------------------------
131 -- TODO: make the NodeId type indexed by `a`, then we no longer need the proxy.
132 nodeAPI :: JSONB a => Connection -> proxy a -> NodeId -> Server (NodeAPI a)
134 = liftIO (getNode conn id p)
136 :<|> postNode conn id
138 :<|> deleteNode' conn id
139 :<|> getChildren' conn id p
142 :<|> getTable conn id
143 :<|> tableNgramsPatch' conn id
144 :<|> getTableNgrams conn id
145 :<|> getPairing conn id
147 :<|> getChart conn id
153 ------------------------------------------------------------------------
154 data RenameNode = RenameNode { r_name :: Text }
157 instance FromJSON RenameNode
158 instance ToJSON RenameNode
159 instance ToSchema RenameNode
160 instance Arbitrary RenameNode where
161 arbitrary = elements [RenameNode "test"]
162 ------------------------------------------------------------------------
163 data PostNode = PostNode { pn_name :: Text
164 , pn_typename :: NodeType}
167 instance FromJSON PostNode
168 instance ToJSON PostNode
169 instance ToSchema PostNode
170 instance Arbitrary PostNode where
171 arbitrary = elements [PostNode "Node test" NodeCorpus]
173 ------------------------------------------------------------------------
174 type DocsApi = Summary "Docs : Move to trash"
175 :> ReqBody '[JSON] Documents
176 :> Delete '[JSON] [Int]
178 data Documents = Documents { documents :: [NodeId]}
181 instance FromJSON Documents
182 instance ToJSON Documents
183 instance ToSchema Documents
185 delDocs :: Connection -> CorpusId -> Documents -> Handler [Int]
186 delDocs c cId ds = liftIO $ nodesToTrash c
187 $ map (\n -> (cId, n, True)) $ documents ds
189 ------------------------------------------------------------------------
190 type FavApi = Summary " Favorites label"
191 :> ReqBody '[JSON] Favorites
193 :<|> Summary " Favorites unlabel"
194 :> ReqBody '[JSON] Favorites
195 :> Delete '[JSON] [Int]
197 data Favorites = Favorites { favorites :: [NodeId]}
200 instance FromJSON Favorites
201 instance ToJSON Favorites
202 instance ToSchema Favorites
204 putFav :: Connection -> CorpusId -> Favorites -> Handler [Int]
205 putFav c cId fs = liftIO $ nodesToFavorite c
206 $ map (\n -> (cId, n, True)) $ favorites fs
208 delFav :: Connection -> CorpusId -> Favorites -> Handler [Int]
209 delFav c cId fs = liftIO $ nodesToFavorite c
210 $ map (\n -> (cId, n, False)) $ favorites fs
212 favApi :: Connection -> CorpusId -> (Favorites -> Handler [Int])
213 :<|> (Favorites -> Handler [Int])
214 favApi c cId = putFav c cId :<|> delFav c cId
216 ------------------------------------------------------------------------
217 type TableApi = Summary " Table API"
218 :> QueryParam "view" TabType
219 :> QueryParam "offset" Int
220 :> QueryParam "limit" Int
221 :> QueryParam "order" OrderBy
222 :> Get '[JSON] [FacetDoc]
224 -- TODO adapt FacetDoc -> ListDoc (and add type of document as column)
225 type PairingApi = Summary " Pairing API"
226 :> QueryParam "view" TabType -- TODO change TabType -> DocType (CorpusId for pairing)
227 :> QueryParam "offset" Int
228 :> QueryParam "limit" Int
229 :> QueryParam "order" OrderBy
230 :> Get '[JSON] [FacetDoc]
232 ------------------------------------------------------------------------
233 type ChartApi = Summary " Chart API"
234 :> QueryParam "from" UTCTime
235 :> QueryParam "to" UTCTime
236 :> Get '[JSON] [FacetChart]
238 -- Depending on the Type of the Node, we could post
239 -- New documents for a corpus
240 -- New map list terms
241 -- :<|> "process" :> MultipartForm MultipartData :> Post '[JSON] Text
243 -- To launch a query and update the corpus
244 -- :<|> "query" :> Capture "string" Text :> Get '[JSON] Text
246 ------------------------------------------------------------------------
247 type GraphAPI = Get '[JSON] Graph
248 graphAPI :: Connection -> NodeId -> Server GraphAPI
249 graphAPI c nId = liftIO $ graphAPI' c nId
251 graphAPI' :: Connection -> NodeId -> IO Graph
254 nodeGraph <- getNode c nId HyperdataGraph
256 let metadata = GraphMetadata "Title" [maybe 0 identity $ _node_parentId nodeGraph]
257 [ LegendField 1 "#FFFFFF" "Label 1"
258 , LegendField 2 "#0048BA" "Label 2"
261 graph <- set graph_metadata (Just metadata) <$> maybe defaultGraph identity <$> readGraphFromJson "purescript-gargantext/dist/examples/imtNew.json"
266 -- t <- textFlow (Mono EN) (Contexts contextText)
267 -- liftIO $ liftIO $ pure $ maybe t identity maybeGraph
268 -- TODO what do we get about the node? to replace contextText
270 -- TODO(orphan): There should be a proper APIError data type with a case TreeError.
271 instance HasTreeError ServantErr where
272 _TreeError = prism' mk (const Nothing) -- Note a prism
274 mk NoRoot = err404 { errBody = "Root node not found" }
275 mk EmptyRoot = err500 { errBody = "Root node should not be empty" }
276 mk TooManyRoots = err500 { errBody = "Too many root nodes" }
278 type TreeAPI = Get '[JSON] (Tree NodeTree)
279 treeAPI :: Connection -> NodeId -> Server TreeAPI
282 ------------------------------------------------------------------------
283 -- | Check if the name is less than 255 char
284 rename :: Connection -> NodeId -> RenameNode -> Handler [Int]
285 rename c nId (RenameNode name) = liftIO $ U.update (U.Rename nId name) c
287 getTable :: Connection -> NodeId -> Maybe TabType
288 -> Maybe Offset -> Maybe Limit
289 -> Maybe OrderBy -> Handler [FacetDoc]
290 getTable c cId ft o l order = liftIO $ case ft of
291 (Just Docs) -> runViewDocuments' c cId False o l order
292 (Just Trash) -> runViewDocuments' c cId True o l order
293 _ -> panic "not implemented"
295 getPairing :: Connection -> ContactId -> Maybe TabType
296 -> Maybe Offset -> Maybe Limit
297 -> Maybe OrderBy -> Handler [FacetDoc]
298 getPairing c cId ft o l order = liftIO $ case ft of
299 (Just Docs) -> runViewAuthorsDoc c cId False o l order
300 (Just Trash) -> runViewAuthorsDoc c cId True o l order
301 _ -> panic "not implemented"
304 getChart :: Connection -> NodeId -> Maybe UTCTime -> Maybe UTCTime
305 -> Handler [FacetChart]
306 getChart _ _ _ _ = undefined -- TODO
308 postNode :: Connection -> NodeId -> PostNode -> Handler [Int]
309 postNode c pId (PostNode name nt) = liftIO $ mk c nt (Just pId) name
311 putNode :: Connection -> NodeId -> Handler Int
312 putNode = undefined -- TODO
314 deleteNodes' :: Connection -> [NodeId] -> Handler Int
315 deleteNodes' conn ids = liftIO (runCmd conn $ deleteNodes ids)
317 deleteNode' :: Connection -> NodeId -> Handler Int
318 deleteNode' conn id = liftIO (runCmd conn $ deleteNode id)
320 getChildren' :: JSONB a => Connection -> NodeId -> proxy a -> Maybe NodeType
321 -> Maybe Int -> Maybe Int -> Handler [Node a]
322 getChildren' conn pId p nodeType offset limit = liftIO (getChildren conn pId p nodeType offset limit)
324 tableNgramsPatch' :: Connection -> CorpusId -> Maybe ListId -> NgramsIdPatchsFeed -> Handler NgramsIdPatchsBack
325 tableNgramsPatch' c cId mL ns = liftIO $ tableNgramsPatch c cId mL ns
327 query :: Text -> Handler Text
332 -- TODO Is it possible to adapt the function according to iValue input ?
333 --upload :: MultipartData -> Handler Text
334 --upload multipartData = do
336 -- putStrLn "Inputs:"
337 -- forM_ (inputs multipartData) $ \input ->
338 -- putStrLn $ " " <> show (iName input)
339 -- <> " -> " <> show (iValue input)
341 -- forM_ (files multipartData) $ \file -> do
342 -- content <- readFile (fdFilePath file)
343 -- putStrLn $ "Content of " <> show (fdFileName file)
344 -- <> " at " <> fdFilePath file
346 -- pure (pack "Data loaded")