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 RankNTypes #-}
21 {-# LANGUAGE TemplateHaskell #-}
22 {-# LANGUAGE TypeOperators #-}
24 -------------------------------------------------------------------
25 module Gargantext.API.Node
26 ( module Gargantext.API.Node
28 , HyperdataAnnuaire(..)
30 , HyperdataResource(..)
32 , HyperdataDocument(..)
33 , HyperdataDocumentV3(..)
35 -------------------------------------------------------------------
36 import Control.Lens (prism', set)
37 import Control.Monad.IO.Class (liftIO)
38 import Control.Monad ((>>))
39 --import System.IO (putStrLn, readFile)
41 import Data.Aeson (FromJSON, ToJSON)
42 import Data.Text (Text())
44 import Data.Time (UTCTime)
46 import GHC.Generics (Generic)
49 import Gargantext.API.Ngrams (TabType(..), TableNgramsApi, TableNgramsApiGet, tableNgramsPatch, getTableNgrams)
50 import Gargantext.Prelude
51 import Gargantext.Database.Types.Node
52 import Gargantext.Database.Utils (Cmd, CmdM)
53 import Gargantext.Database.Schema.Node ( getNodesWithParentId, getNode, deleteNode, deleteNodes, mk, JSONB, NodeError(..), HasNodeError(..))
54 import Gargantext.Database.Node.Children (getChildren)
55 import qualified Gargantext.Database.Node.Update as U (update, Update(..))
56 import Gargantext.Database.Facet (FacetDoc , runViewDocuments, OrderBy(..),FacetChart,runViewAuthorsDoc)
57 import Gargantext.Database.Tree (treeDB, HasTreeError(..), TreeError(..))
58 import Gargantext.Database.Metrics.Count (getCoocByDocDev)
59 import Gargantext.Database.Schema.Node (defaultList)
60 import Gargantext.Database.Schema.NodeNode (nodesToFavorite, nodesToTrash)
61 import Gargantext.API.Search ( SearchAPI, searchIn, SearchInQuery)
63 import Gargantext.Text.Flow (cooc2graph)
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)
68 import Gargantext.Database.Types.Node (CorpusId, ContactId)
69 -- import Gargantext.Text.Terms (TermType(..))
71 import Test.QuickCheck (elements)
72 import Test.QuickCheck.Arbitrary (Arbitrary, arbitrary)
74 type GargServer api = forall env m. CmdM env ServantErr m => ServerT api m
76 -------------------------------------------------------------------
77 -- | TODO : access by admin only
78 type NodesAPI = Delete '[JSON] Int
81 -- Be careful: really delete nodes
82 -- Access by admin only
83 nodesAPI :: [NodeId] -> GargServer NodesAPI
84 nodesAPI ids = deleteNodes ids
86 ------------------------------------------------------------------------
87 -- | TODO: access by admin only
88 -- To manager the Users roots
89 type Roots = Get '[JSON] [NodeAny]
90 :<|> Put '[JSON] Int -- TODO
92 -- | TODO: access by admin only
93 roots :: GargServer Roots
94 roots = (liftIO (putStrLn ( "/user" :: Text)) >> getNodesWithParentId 0 Nothing)
95 :<|> pure (panic "not implemented yet") -- TODO use patch map to update what we need
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
116 :<|> "search":> Summary "Node Search"
117 :> ReqBody '[JSON] SearchInQuery
118 :> QueryParam "offset" Int
119 :> QueryParam "limit" Int
120 :> QueryParam "order" OrderBy
123 type RenameApi = Summary " Rename Node"
124 :> ReqBody '[JSON] RenameNode
127 type PostNodeApi = Summary " PostNode Node with ParentId as {id}"
128 :> ReqBody '[JSON] PostNode
129 :> Post '[JSON] [NodeId]
131 type ChildrenApi a = Summary " Summary children"
132 :> QueryParam "type" NodeType
133 :> QueryParam "offset" Int
134 :> QueryParam "limit" Int
135 :> Get '[JSON] [Node a]
136 ------------------------------------------------------------------------
137 -- TODO: make the NodeId type indexed by `a`, then we no longer need the proxy.
138 nodeAPI :: JSONB a => proxy a -> NodeId -> GargServer (NodeAPI a)
139 nodeAPI p id = getNode id p
144 :<|> getChildren id p
148 :<|> tableNgramsPatch id
149 :<|> getTableNgrams id
159 ------------------------------------------------------------------------
160 data RenameNode = RenameNode { r_name :: Text }
163 instance FromJSON RenameNode
164 instance ToJSON RenameNode
165 instance ToSchema RenameNode
166 instance Arbitrary RenameNode where
167 arbitrary = elements [RenameNode "test"]
168 ------------------------------------------------------------------------
169 data PostNode = PostNode { pn_name :: Text
170 , pn_typename :: NodeType}
173 instance FromJSON PostNode
174 instance ToJSON PostNode
175 instance ToSchema PostNode
176 instance Arbitrary PostNode where
177 arbitrary = elements [PostNode "Node test" NodeCorpus]
179 ------------------------------------------------------------------------
180 type DocsApi = Summary "Docs : Move to trash"
181 :> ReqBody '[JSON] Documents
182 :> Delete '[JSON] [Int]
184 data Documents = Documents { documents :: [NodeId]}
187 instance FromJSON Documents
188 instance ToJSON Documents
189 instance ToSchema Documents
191 delDocs :: CorpusId -> Documents -> Cmd err [Int]
192 delDocs cId ds = nodesToTrash $ map (\n -> (cId, n, True)) $ documents ds
194 ------------------------------------------------------------------------
195 type FavApi = Summary " Favorites label"
196 :> ReqBody '[JSON] Favorites
198 :<|> Summary " Favorites unlabel"
199 :> ReqBody '[JSON] Favorites
200 :> Delete '[JSON] [Int]
202 data Favorites = Favorites { favorites :: [NodeId]}
205 instance FromJSON Favorites
206 instance ToJSON Favorites
207 instance ToSchema Favorites
209 putFav :: CorpusId -> Favorites -> Cmd err [Int]
210 putFav cId fs = nodesToFavorite $ map (\n -> (cId, n, True)) $ favorites fs
212 delFav :: CorpusId -> Favorites -> Cmd err [Int]
213 delFav cId fs = nodesToFavorite $ map (\n -> (cId, n, False)) $ favorites fs
215 favApi :: CorpusId -> GargServer FavApi
216 favApi cId = putFav cId :<|> delFav cId
218 ------------------------------------------------------------------------
219 type TableApi = Summary " Table API"
220 :> QueryParam "view" TabType
221 :> QueryParam "offset" Int
222 :> QueryParam "limit" Int
223 :> QueryParam "order" OrderBy
224 :> Get '[JSON] [FacetDoc]
226 -- TODO adapt FacetDoc -> ListDoc (and add type of document as column)
227 type PairingApi = Summary " Pairing API"
228 :> QueryParam "view" TabType -- TODO change TabType -> DocType (CorpusId for pairing)
229 :> QueryParam "offset" Int
230 :> QueryParam "limit" Int
231 :> QueryParam "order" OrderBy
232 :> Get '[JSON] [FacetDoc]
234 ------------------------------------------------------------------------
235 type ChartApi = Summary " Chart API"
236 :> QueryParam "from" UTCTime
237 :> QueryParam "to" UTCTime
238 :> Get '[JSON] [FacetChart]
240 -- Depending on the Type of the Node, we could post
241 -- New documents for a corpus
242 -- New map list terms
243 -- :<|> "process" :> MultipartForm MultipartData :> Post '[JSON] Text
245 -- To launch a query and update the corpus
246 -- :<|> "query" :> Capture "string" Text :> Get '[JSON] Text
248 ------------------------------------------------------------------------
249 type GraphAPI = Get '[JSON] Graph
251 graphAPI :: NodeId -> GargServer GraphAPI
252 graphAPI cId = undefined
254 --graphAPI' :: NodeId -> GargServer GraphAPI
255 --graphAPI' :: NodeId -> Cmd err Graph -- GargServer GraphAPI
258 nodeGraph <- getNode cId HyperdataGraph
260 let title = "IMT - Scientific publications - 1982-2017 - English"
261 let metadata = GraphMetadata title [maybe 0 identity $ _node_parentId nodeGraph] [ LegendField 6 "#FFF" "Data processing"
262 , LegendField 7 "#FFF" "Networks"
263 , LegendField 1 "#FFF" "Material science"
264 , LegendField 5 "#FFF" "Energy / Environment"
266 -- (map (\n -> LegendField n "#FFFFFF" (pack $ show n)) [1..10])
267 lId <- defaultList cId
269 cooc <- getCoocByDocDev cId lId
271 graph <- set graph_metadata (Just metadata)
272 -- <$> maybe defaultGraph identity
275 <$> readGraphFromJson "purescript-gargantext/dist/examples/imtNew.json"
280 -- t <- textFlow (Mono EN) (Contexts contextText)
281 -- liftIO $ liftIO $ pure $ maybe t identity maybeGraph
282 -- TODO what do we get about the node? to replace contextText
284 instance HasNodeError ServantErr where
285 _NodeError = prism' mk (const Nothing) -- $ panic "HasNodeError ServantErr: not a prism")
288 mk NoListFound = err404 { errBody = e <> "No list found" }
289 mk NoRootFound = err404 { errBody = e <> "No Root found" }
290 mk NoCorpusFound = err404 { errBody = e <> "No Corpus found" }
291 mk NoUserFound = err404 { errBody = e <> "No User found" }
293 mk MkNode = err500 { errBody = e <> "Cannot mk node" }
294 mk NegativeId = err500 { errBody = e <> "Node with negative Id" }
295 mk UserNoParent = err500 { errBody = e <> "Should not have parent"}
296 mk HasParent = err500 { errBody = e <> "NodeType has parent" }
297 mk NotImplYet = err500 { errBody = e <> "Not implemented yet" }
298 mk ManyParents = err500 { errBody = e <> "Too many parents" }
299 mk ManyNodeUsers = err500 { errBody = e <> "Many userNode/user" }
301 -- TODO(orphan): There should be a proper APIError data type with a case TreeError.
302 instance HasTreeError ServantErr where
303 _TreeError = prism' mk (const Nothing) -- $ panic "HasTreeError ServantErr: not a prism")
306 mk NoRoot = err404 { errBody = e <> "Root node not found" }
307 mk EmptyRoot = err500 { errBody = e <> "Root node should not be empty" }
308 mk TooManyRoots = err500 { errBody = e <> "Too many root nodes" }
310 type TreeAPI = Get '[JSON] (Tree NodeTree)
311 treeAPI :: NodeId -> GargServer TreeAPI
314 ------------------------------------------------------------------------
315 -- | Check if the name is less than 255 char
316 rename :: NodeId -> RenameNode -> Cmd err [Int]
317 rename nId (RenameNode name) = U.update (U.Rename nId name)
319 getTable :: NodeId -> Maybe TabType
320 -> Maybe Offset -> Maybe Limit
321 -> Maybe OrderBy -> Cmd err [FacetDoc]
322 getTable cId ft o l order = case ft of
323 (Just Docs) -> runViewDocuments cId False o l order
324 (Just Trash) -> runViewDocuments cId True o l order
325 _ -> panic "not implemented"
327 getPairing :: ContactId -> Maybe TabType
328 -> Maybe Offset -> Maybe Limit
329 -> Maybe OrderBy -> Cmd err [FacetDoc]
330 getPairing cId ft o l order = case ft of
331 (Just Docs) -> runViewAuthorsDoc cId False o l order
332 (Just Trash) -> runViewAuthorsDoc cId True o l order
333 _ -> panic "not implemented"
336 getChart :: NodeId -> Maybe UTCTime -> Maybe UTCTime
337 -> Cmd err [FacetChart]
338 getChart _ _ _ = undefined -- TODO
340 postNode :: NodeId -> PostNode -> Cmd err [NodeId]
341 postNode pId (PostNode name nt) = mk nt (Just pId) name
343 putNode :: NodeId -> Cmd err Int
344 putNode = undefined -- TODO
346 query :: Monad m => Text -> m Text
351 -- TODO Is it possible to adapt the function according to iValue input ?
352 --upload :: MultipartData -> Handler Text
353 --upload multipartData = do
355 -- putStrLn "Inputs:"
356 -- forM_ (inputs multipartData) $ \input ->
357 -- putStrLn $ " " <> show (iName input)
358 -- <> " -> " <> show (iValue input)
360 -- forM_ (files multipartData) $ \file -> do
361 -- content <- readFile (fdFilePath file)
362 -- putStrLn $ "Content of " <> show (fdFileName file)
363 -- <> " at " <> fdFilePath file
365 -- pure (pack "Data loaded")