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 Data.Time (UTCTime)
38 import GHC.Generics (Generic)
39 import Gargantext.API.Admin.Auth (withAccess, PathId(..))
40 import Gargantext.API.Prelude
41 import Gargantext.API.Metrics
42 import Gargantext.API.Ngrams (TabType(..), TableNgramsApi, apiNgramsTableCorpus, QueryParamR)
43 import Gargantext.API.Ngrams.NTree (MyTree)
44 import Gargantext.API.Node.New
45 import Gargantext.API.Search (SearchDocsAPI, searchDocs, SearchPairsAPI, searchPairs)
46 import Gargantext.API.Table
47 import Gargantext.Core.Types (NodeTableResult)
48 import Gargantext.Core.Types.Main (Tree, NodeTree, ListType)
49 import Gargantext.Database.Action.Flow.Pairing (pairing)
50 import Gargantext.Database.Query.Facet (FacetDoc, OrderBy(..))
51 import Gargantext.Database.Query.Table.Node
52 import Gargantext.Database.Query.Table.Node.Children (getChildren)
53 import Gargantext.Database.Query.Table.Node.UpdateOpaleye (updateHyperdata)
54 import Gargantext.Database.Query.Table.Node.User
55 import Gargantext.Database.Query.Tree (treeDB)
56 import Gargantext.Database.Admin.Config (nodeTypeId)
57 import Gargantext.Database.Query.Table.Node.Error (HasNodeError(..))
58 import Gargantext.Database.Admin.Types.Node
59 import Gargantext.Database.Prelude -- (Cmd, CmdM)
60 import Gargantext.Database.Schema.Node (_node_typename)
61 import Gargantext.Database.Query.Table.NodeNode
62 import Gargantext.Prelude
63 import Gargantext.Viz.Chart
64 import Gargantext.Viz.Phylo.API (PhyloAPI, phyloAPI)
66 import Test.QuickCheck (elements)
67 import Test.QuickCheck.Arbitrary (Arbitrary, arbitrary)
68 import qualified Gargantext.Database.Query.Table.Node.Update as U (update, Update(..))
71 import qualified Gargantext.Text.List.Learn as Learn
72 import qualified Data.Vector as Vec
75 type NodesAPI = Delete '[JSON] Int
78 -- Be careful: really delete nodes
79 -- Access by admin only
80 nodesAPI :: [NodeId] -> GargServer NodesAPI
81 nodesAPI ids = deleteNodes ids
83 ------------------------------------------------------------------------
84 -- | TODO-ACCESS: access by admin only.
85 -- At first let's just have an isAdmin check.
86 -- Later: CanAccessAnyNode or (CanGetAnyNode, CanPutAnyNode)
87 -- To manage the Users roots
90 -- TODO needs design discussion.
91 type Roots = Get '[JSON] [Node HyperdataUser]
92 :<|> Put '[JSON] Int -- TODO
94 -- | TODO: access by admin only
95 roots :: GargServer Roots
96 roots = getNodesWithParentId Nothing
97 :<|> pure (panic "not implemented yet") -- TODO use patch map to update what we need
99 -------------------------------------------------------------------
100 -- | Node API Types management
101 -- TODO-ACCESS : access by users
102 -- No ownership check is needed if we strictly follow the capability model.
104 -- CanGetNode (Node, Children, TableApi, TableNgramsApiGet, PairingApi, ChartApi,
106 -- CanRenameNode (or part of CanEditNode?)
107 -- CanCreateChildren (PostNodeApi)
108 -- CanEditNode / CanPutNode TODO not implemented yet
110 -- CanPatch (TableNgramsApi)
114 type NodeAPI a = Get '[JSON] (Node a)
115 :<|> "rename" :> RenameApi
116 :<|> PostNodeApi -- TODO move to children POST
118 :<|> ReqBody '[JSON] a :> Put '[JSON] Int
119 :<|> Delete '[JSON] Int
120 :<|> "children" :> ChildrenApi a
123 :<|> "table" :> TableApi
124 :<|> "ngrams" :> TableNgramsApi
126 :<|> "category" :> CatApi
127 :<|> "search" :> SearchDocsAPI
130 :<|> "pairwith" :> PairWith
131 :<|> "pairs" :> Pairs
132 :<|> "pairing" :> PairingApi
133 :<|> "searchPair" :> SearchPairsAPI
136 :<|> "metrics" :> ScatterAPI
137 :<|> "chart" :> ChartApi
139 :<|> "tree" :> TreeApi
140 :<|> "phylo" :> PhyloAPI
141 -- :<|> "add" :> NodeAddAPI
143 -- TODO-ACCESS: check userId CanRenameNode nodeId
144 -- TODO-EVENTS: NodeRenamed RenameNode or re-use some more general NodeEdited...
145 type RenameApi = Summary " Rename Node"
146 :> ReqBody '[JSON] RenameNode
149 type PostNodeApi = Summary " PostNode Node with ParentId as {id}"
150 :> ReqBody '[JSON] PostNode
151 :> Post '[JSON] [NodeId]
153 type ChildrenApi a = Summary " Summary children"
154 :> QueryParam "type" NodeType
155 :> QueryParam "offset" Int
156 :> QueryParam "limit" Int
157 -- :> Get '[JSON] [Node a]
158 :> Get '[JSON] (NodeTableResult a)
160 ------------------------------------------------------------------------
161 type NodeNodeAPI a = Get '[JSON] (Node a)
163 nodeNodeAPI :: forall proxy a. (JSONB a, ToJSON a)
168 -> GargServer (NodeNodeAPI a)
169 nodeNodeAPI p uId cId nId = withAccess (Proxy :: Proxy (NodeNodeAPI a)) Proxy uId (PathNodeNode cId nId) nodeNodeAPI'
171 nodeNodeAPI' :: GargServer (NodeNodeAPI a)
172 nodeNodeAPI' = getNodeWith nId p
174 ------------------------------------------------------------------------
175 -- TODO: make the NodeId type indexed by `a`, then we no longer need the proxy.
176 nodeAPI :: forall proxy a.
183 -> GargServer (NodeAPI a)
184 nodeAPI p uId id' = withAccess (Proxy :: Proxy (NodeAPI a)) Proxy uId (PathNode id') nodeAPI'
186 nodeAPI' :: GargServer (NodeAPI a)
187 nodeAPI' = getNodeWith id' p
189 :<|> postNode uId id'
190 :<|> postNodeAsyncAPI uId id'
192 :<|> deleteNodeApi id'
193 :<|> getChildren id' p
197 :<|> apiNgramsTableCorpus id'
212 :<|> phyloAPI id' uId
213 -- :<|> nodeAddAPI id'
214 -- :<|> postUpload id'
216 deleteNodeApi id'' = do
217 node' <- getNode id''
218 if _node_typename node' == nodeTypeId NodeUser
219 then panic "not allowed" -- TODO add proper Right Management Type
222 ------------------------------------------------------------------------
223 data RenameNode = RenameNode { r_name :: Text }
226 -- TODO unPrefix "r_" FromJSON, ToJSON, ToSchema, adapt frontend.
227 instance FromJSON RenameNode
228 instance ToJSON RenameNode
229 instance ToSchema RenameNode
230 instance Arbitrary RenameNode where
231 arbitrary = elements [RenameNode "test"]
232 ------------------------------------------------------------------------
233 ------------------------------------------------------------------------
234 type CatApi = Summary " To Categorize NodeNodes: 0 for delete, 1/null neutral, 2 favorite"
235 :> ReqBody '[JSON] NodesToCategory
238 data NodesToCategory = NodesToCategory { ntc_nodesId :: [NodeId]
239 , ntc_category :: Int
243 -- TODO unPrefix "ntc_" FromJSON, ToJSON, ToSchema, adapt frontend.
244 instance FromJSON NodesToCategory
245 instance ToJSON NodesToCategory
246 instance ToSchema NodesToCategory
248 catApi :: CorpusId -> GargServer CatApi
251 putCat :: CorpusId -> NodesToCategory -> Cmd err [Int]
252 putCat cId cs' = nodeNodesCategory $ map (\n -> (cId, n, ntc_category cs')) (ntc_nodesId cs')
254 ------------------------------------------------------------------------
255 -- TODO adapt FacetDoc -> ListDoc (and add type of document as column)
256 -- Pairing utilities to move elsewhere
257 type PairingApi = Summary " Pairing API"
258 :> QueryParam "view" TabType
259 -- TODO change TabType -> DocType (CorpusId for pairing)
260 :> QueryParam "offset" Int
261 :> QueryParam "limit" Int
262 :> QueryParam "order" OrderBy
263 :> Get '[JSON] [FacetDoc]
266 type Pairs = Summary "List of Pairs"
267 :> Get '[JSON] [AnnuaireId]
268 pairs :: CorpusId -> GargServer Pairs
270 ns <- getNodeNode cId
271 pure $ map _nn_node2_id ns
273 type PairWith = Summary "Pair a Corpus with an Annuaire"
274 :> "annuaire" :> Capture "annuaire_id" AnnuaireId
275 :> "list" :> Capture "list_id" ListId
278 pairWith :: CorpusId -> GargServer PairWith
279 pairWith cId aId lId = do
280 r <- pairing cId aId lId
281 _ <- insertNodeNode [ NodeNode cId aId Nothing Nothing]
284 ------------------------------------------------------------------------
285 type ChartApi = Summary " Chart API"
286 :> QueryParam "from" UTCTime
287 :> QueryParam "to" UTCTime
288 :> Get '[JSON] (ChartMetrics Histo)
290 type PieApi = Summary " Chart API"
291 :> QueryParam "from" UTCTime
292 :> QueryParam "to" UTCTime
293 :> QueryParamR "ngramsType" TabType
294 :> Get '[JSON] (ChartMetrics Histo)
296 type TreeApi = Summary " Tree API"
297 :> QueryParam "from" UTCTime
298 :> QueryParam "to" UTCTime
299 :> QueryParamR "ngramsType" TabType
300 :> QueryParamR "listType" ListType
301 :> Get '[JSON] (ChartMetrics [MyTree])
303 -- Depending on the Type of the Node, we could post
304 -- New documents for a corpus
305 -- New map list terms
306 -- :<|> "process" :> MultipartForm MultipartData :> Post '[JSON] Text
308 ------------------------------------------------------------------------
310 type TreeAPI = QueryParams "type" NodeType :> Get '[JSON] (Tree NodeTree)
312 treeAPI :: NodeId -> GargServer TreeAPI
315 ------------------------------------------------------------------------
316 -- | Check if the name is less than 255 char
317 rename :: NodeId -> RenameNode -> Cmd err [Int]
318 rename nId (RenameNode name') = U.update (U.Rename nId name')
320 putNode :: forall err a. (HasNodeError err, JSONB a, ToJSON a)
324 putNode n h = fromIntegral <$> updateHyperdata n h
325 -------------------------------------------------------------