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
10 -- TODO-SECURITY: Critical
12 -- TODO-ACCESS: CanGetNode
13 -- TODO-EVENTS: No events as this is a read only query.
15 -------------------------------------------------------------------
16 -- TODO-ACCESS: access by admin only.
17 -- At first let's just have an isAdmin check.
18 -- Later: check userId CanDeleteNodes Nothing
19 -- TODO-EVENTS: DeletedNodes [NodeId]
20 -- {"tag": "DeletedNodes", "nodes": [Int*]}
24 {-# OPTIONS_GHC -fno-warn-orphans #-}
26 {-# LANGUAGE ScopedTypeVariables #-}
27 {-# LANGUAGE TemplateHaskell #-}
28 {-# LANGUAGE TypeOperators #-}
30 module Gargantext.API.Node
33 import Data.Aeson (FromJSON, ToJSON)
36 import Data.Text (Text())
37 import GHC.Generics (Generic)
39 import Test.QuickCheck (elements)
40 import Test.QuickCheck.Arbitrary (Arbitrary, arbitrary)
42 import Gargantext.API.Admin.Auth (withAccess, PathId(..))
43 import Gargantext.API.Prelude
44 import Gargantext.API.Metrics
45 import Gargantext.API.Ngrams (TabType(..), TableNgramsApi, apiNgramsTableCorpus)
46 import Gargantext.API.Node.New
47 import qualified Gargantext.API.Node.Share as Share
48 import qualified Gargantext.API.Node.Update as Update
50 import Gargantext.API.Search (SearchDocsAPI, searchDocs, SearchPairsAPI, searchPairs)
51 import Gargantext.API.Table
52 import Gargantext.Core.Types (NodeTableResult)
53 import Gargantext.Core.Types.Main (Tree, NodeTree)
54 import Gargantext.Database.Action.Flow.Pairing (pairing)
55 import Gargantext.Database.Query.Facet (FacetDoc, OrderBy(..))
56 import Gargantext.Core.Types.Individu (User(..))
57 import Gargantext.Database.Query.Table.Node
58 import Gargantext.Database.Query.Table.Node.Children (getChildren)
59 import Gargantext.Database.Query.Table.Node.UpdateOpaleye (updateHyperdata)
60 import Gargantext.Database.Query.Table.Node.User
61 import Gargantext.Database.Query.Tree (tree, TreeMode(..))
62 import Gargantext.Database.Query.Table.Node.Error (HasNodeError(..))
63 import Gargantext.Database.Admin.Types.Node
64 import Gargantext.Database.Prelude -- (Cmd, CmdM)
65 import Gargantext.Database.Query.Table.NodeNode
66 import Gargantext.Prelude
67 import Gargantext.Viz.Phylo.API (PhyloAPI, phyloAPI)
68 import qualified Gargantext.Database.Query.Table.Node.Update as U (update, Update(..))
69 import qualified Gargantext.Database.Action.Delete as Action (deleteNode)
72 import qualified Gargantext.Text.List.Learn as Learn
73 import qualified Data.Vector as Vec
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: access by admin only.
88 -- At first let's just have an isAdmin check.
89 -- Later: CanAccessAnyNode or (CanGetAnyNode, CanPutAnyNode)
90 -- To manage the Users roots
93 -- TODO needs design discussion.
94 type Roots = Get '[JSON] [Node HyperdataUser]
95 :<|> Put '[JSON] Int -- TODO
97 -- | TODO: access by admin only
98 roots :: GargServer Roots
99 roots = getNodesWithParentId Nothing
100 :<|> pure (panic "not implemented yet") -- TODO use patch map to update what we need
102 -------------------------------------------------------------------
103 -- | Node API Types management
104 -- TODO-ACCESS : access by users
105 -- No ownership check is needed if we strictly follow the capability model.
107 -- CanGetNode (Node, Children, TableApi, TableNgramsApiGet, PairingApi, ChartApi,
109 -- CanRenameNode (or part of CanEditNode?)
110 -- CanCreateChildren (PostNodeApi)
111 -- CanEditNode / CanPutNode TODO not implemented yet
113 -- CanPatch (TableNgramsApi)
117 type NodeAPI a = Get '[JSON] (Node a)
118 :<|> "rename" :> RenameApi
119 :<|> PostNodeApi -- TODO move to children POST
121 :<|> ReqBody '[JSON] a :> Put '[JSON] Int
122 :<|> Delete '[JSON] Int
123 :<|> "children" :> ChildrenApi a
126 :<|> "table" :> TableApi
127 :<|> "ngrams" :> TableNgramsApi
129 :<|> "category" :> CatApi
130 :<|> "search" :> SearchDocsAPI
131 :<|> "share" :> Share.API
134 :<|> "pairwith" :> PairWith
135 :<|> "pairs" :> Pairs
136 :<|> "pairing" :> PairingApi
137 :<|> "searchPair" :> SearchPairsAPI
140 :<|> "metrics" :> ScatterAPI
141 :<|> "chart" :> ChartApi
143 :<|> "tree" :> TreeApi
144 :<|> "phylo" :> PhyloAPI
145 -- :<|> "add" :> NodeAddAPI
146 :<|> "update" :> Update.API
148 -- TODO-ACCESS: check userId CanRenameNode nodeId
149 -- TODO-EVENTS: NodeRenamed RenameNode or re-use some more general NodeEdited...
150 type RenameApi = Summary " Rename Node"
151 :> ReqBody '[JSON] RenameNode
154 type PostNodeApi = Summary " PostNode Node with ParentId as {id}"
155 :> ReqBody '[JSON] PostNode
156 :> Post '[JSON] [NodeId]
158 type ChildrenApi a = Summary " Summary children"
159 :> QueryParam "type" NodeType
160 :> QueryParam "offset" Int
161 :> QueryParam "limit" Int
162 -- :> Get '[JSON] [Node a]
163 :> Get '[JSON] (NodeTableResult a)
165 ------------------------------------------------------------------------
166 type NodeNodeAPI a = Get '[JSON] (Node a)
168 nodeNodeAPI :: forall proxy a. (JSONB a, ToJSON a)
173 -> GargServer (NodeNodeAPI a)
174 nodeNodeAPI p uId cId nId = withAccess (Proxy :: Proxy (NodeNodeAPI a)) Proxy uId (PathNodeNode cId nId) nodeNodeAPI'
176 nodeNodeAPI' :: GargServer (NodeNodeAPI a)
177 nodeNodeAPI' = getNodeWith nId p
179 ------------------------------------------------------------------------
180 -- TODO: make the NodeId type indexed by `a`, then we no longer need the proxy.
181 nodeAPI :: forall proxy a.
188 -> GargServer (NodeAPI a)
189 nodeAPI p uId id' = withAccess (Proxy :: Proxy (NodeAPI a)) Proxy uId (PathNode id') nodeAPI'
191 nodeAPI' :: GargServer (NodeAPI a)
192 nodeAPI' = getNodeWith id' p
194 :<|> postNode uId id'
195 :<|> postNodeAsyncAPI uId id'
197 :<|> Action.deleteNode (RootId $ NodeId uId) id'
198 :<|> getChildren id' p
202 :<|> apiNgramsTableCorpus id'
217 :<|> phyloAPI id' uId
218 -- :<|> nodeAddAPI id'
219 -- :<|> postUpload id'
220 :<|> Update.api uId id'
222 scatterApi :: NodeId -> GargServer ScatterAPI
223 scatterApi id' = getScatter id'
224 :<|> updateScatter id'
226 chartApi :: NodeId -> GargServer ChartApi
227 chartApi id' = getChart id'
230 pieApi :: NodeId -> GargServer PieApi
231 pieApi id' = getPie id'
234 treeApi :: NodeId -> GargServer TreeApi
235 treeApi id' = getTree id'
238 ------------------------------------------------------------------------
239 data RenameNode = RenameNode { r_name :: Text }
242 -- TODO unPrefix "r_" FromJSON, ToJSON, ToSchema, adapt frontend.
243 instance FromJSON RenameNode
244 instance ToJSON RenameNode
245 instance ToSchema RenameNode
246 instance Arbitrary RenameNode where
247 arbitrary = elements [RenameNode "test"]
248 ------------------------------------------------------------------------
249 ------------------------------------------------------------------------
250 type CatApi = Summary " To Categorize NodeNodes: 0 for delete, 1/null neutral, 2 favorite"
251 :> ReqBody '[JSON] NodesToCategory
254 data NodesToCategory = NodesToCategory { ntc_nodesId :: [NodeId]
255 , ntc_category :: Int
259 -- TODO unPrefix "ntc_" FromJSON, ToJSON, ToSchema, adapt frontend.
260 instance FromJSON NodesToCategory
261 instance ToJSON NodesToCategory
262 instance ToSchema NodesToCategory
264 catApi :: CorpusId -> GargServer CatApi
267 putCat :: CorpusId -> NodesToCategory -> Cmd err [Int]
268 putCat cId cs' = nodeNodesCategory $ map (\n -> (cId, n, ntc_category cs')) (ntc_nodesId cs')
270 ------------------------------------------------------------------------
271 -- TODO adapt FacetDoc -> ListDoc (and add type of document as column)
272 -- Pairing utilities to move elsewhere
273 type PairingApi = Summary " Pairing API"
274 :> QueryParam "view" TabType
275 -- TODO change TabType -> DocType (CorpusId for pairing)
276 :> QueryParam "offset" Int
277 :> QueryParam "limit" Int
278 :> QueryParam "order" OrderBy
279 :> Get '[JSON] [FacetDoc]
282 type Pairs = Summary "List of Pairs"
283 :> Get '[JSON] [AnnuaireId]
284 pairs :: CorpusId -> GargServer Pairs
286 ns <- getNodeNode cId
287 pure $ map _nn_node2_id ns
289 type PairWith = Summary "Pair a Corpus with an Annuaire"
290 :> "annuaire" :> Capture "annuaire_id" AnnuaireId
291 :> "list" :> Capture "list_id" ListId
294 pairWith :: CorpusId -> GargServer PairWith
295 pairWith cId aId lId = do
296 r <- pairing cId aId lId
297 _ <- insertNodeNode [ NodeNode cId aId Nothing Nothing]
300 ------------------------------------------------------------------------
302 ------------------------------------------------------------------------
303 type TreeAPI = QueryParams "type" NodeType :> Get '[JSON] (Tree NodeTree)
305 treeAPI :: NodeId -> GargServer TreeAPI
306 treeAPI = tree Advanced
308 ------------------------------------------------------------------------
309 -- | Check if the name is less than 255 char
310 rename :: NodeId -> RenameNode -> Cmd err [Int]
311 rename nId (RenameNode name') = U.update (U.Rename nId name')
313 putNode :: forall err a. (HasNodeError err, JSONB a, ToJSON a)
317 putNode n h = fromIntegral <$> updateHyperdata n h
318 -------------------------------------------------------------