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)
34 import Data.Aeson.TH (deriveJSON)
37 import Data.Text (Text())
38 import GHC.Generics (Generic)
40 import Test.QuickCheck (elements)
41 import Test.QuickCheck.Arbitrary (Arbitrary, arbitrary)
43 import Gargantext.API.Admin.Auth.Types (PathId(..))
44 import Gargantext.API.Admin.Auth (withAccess)
45 import Gargantext.API.Metrics
46 import Gargantext.API.Ngrams (TableNgramsApi, apiNgramsTableCorpus)
47 import Gargantext.API.Ngrams.Types (TabType(..))
48 import Gargantext.API.Node.File
49 import Gargantext.API.Node.New
50 import Gargantext.API.Prelude
51 import Gargantext.API.Table
52 import Gargantext.Core.Types (NodeTableResult)
53 import Gargantext.Core.Types.Individu (User(..))
54 import Gargantext.Core.Types.Main (Tree, NodeTree)
55 import Gargantext.Core.Utils.Prefix (unPrefix)
56 import Gargantext.Database.Action.Flow.Pairing (pairing)
57 import Gargantext.Database.Admin.Types.Hyperdata
58 import Gargantext.Database.Admin.Types.Node
59 import Gargantext.Database.Prelude -- (Cmd, CmdM)
60 import Gargantext.Database.Query.Facet (FacetDoc, OrderBy(..))
61 import Gargantext.Database.Query.Table.Node
62 import Gargantext.Database.Query.Table.Node.Children (getChildren)
63 import Gargantext.Database.Query.Table.Node.Error (HasNodeError(..))
64 import Gargantext.Database.Query.Table.Node.Update (Update(..), update)
65 import Gargantext.Database.Query.Table.Node.UpdateOpaleye (updateHyperdata)
66 import Gargantext.Database.Query.Table.NodeNode
67 import Gargantext.Database.Query.Tree (tree, TreeMode(..))
68 import Gargantext.Prelude
69 import Gargantext.Core.Viz.Phylo.Legacy.LegacyAPI (PhyloAPI, phyloAPI)
70 import qualified Gargantext.API.Node.DocumentsFromWriteNodes as DocumentsFromWriteNodes
71 import qualified Gargantext.API.Node.DocumentUpload as DocumentUpload
72 import qualified Gargantext.API.Node.FrameCalcUpload as FrameCalcUpload
73 import qualified Gargantext.API.Node.Share as Share
74 import qualified Gargantext.API.Node.Update as Update
75 import qualified Gargantext.API.Search as Search
76 import qualified Gargantext.Database.Action.Delete as Action (deleteNode)
77 import qualified Gargantext.Database.Query.Table.Node.Update as U (update, Update(..))
80 import qualified Gargantext.Core.Text.List.Learn as Learn
81 import qualified Data.Vector as Vec
86 type NodesAPI = Delete '[JSON] Int
89 -- Be careful: really delete nodes
90 -- Access by admin only
91 nodesAPI :: [NodeId] -> GargServer NodesAPI
92 nodesAPI = deleteNodes
94 ------------------------------------------------------------------------
95 -- | TODO-ACCESS: access by admin only.
96 -- At first let's just have an isAdmin check.
97 -- Later: CanAccessAnyNode or (CanGetAnyNode, CanPutAnyNode)
98 -- To manage the Users roots
101 -- TODO needs design discussion.
102 type Roots = Get '[JSON] [Node HyperdataUser]
103 :<|> Put '[JSON] Int -- TODO
105 -- | TODO: access by admin only
106 roots :: GargServer Roots
107 roots = getNodesWithParentId Nothing
108 :<|> pure (panic "not implemented yet") -- TODO use patch map to update what we need
110 -------------------------------------------------------------------
111 -- | Node API Types management
112 -- TODO-ACCESS : access by users
113 -- No ownership check is needed if we strictly follow the capability model.
115 -- CanGetNode (Node, Children, TableApi, TableNgramsApiGet, PairingApi, ChartApi,
117 -- CanRenameNode (or part of CanEditNode?)
118 -- CanCreateChildren (PostNodeApi)
119 -- CanEditNode / CanPutNode TODO not implemented yet
121 -- CanPatch (TableNgramsApi)
125 type NodeAPI a = Get '[JSON] (Node a)
126 :<|> "rename" :> RenameApi
127 :<|> PostNodeApi -- TODO move to children POST
129 :<|> FrameCalcUpload.API
130 :<|> ReqBody '[JSON] a :> Put '[JSON] Int
131 :<|> "update" :> Update.API
132 :<|> Delete '[JSON] Int
133 :<|> "children" :> ChildrenApi a
136 :<|> "table" :> TableApi
137 :<|> "ngrams" :> TableNgramsApi
139 :<|> "category" :> CatApi
140 :<|> "score" :> ScoreApi
141 :<|> "search" :> (Search.API Search.SearchResult)
142 :<|> "share" :> Share.API
145 :<|> "pairwith" :> PairWith
146 :<|> "pairs" :> Pairs
147 :<|> "pairing" :> PairingApi
150 :<|> "metrics" :> ScatterAPI
151 :<|> "chart" :> ChartApi
153 :<|> "tree" :> TreeApi
154 :<|> "phylo" :> PhyloAPI
155 -- :<|> "add" :> NodeAddAPI
156 :<|> "move" :> MoveAPI
157 :<|> "unpublish" :> Share.Unpublish
159 :<|> "file" :> FileApi
160 :<|> "async" :> FileAsyncApi
162 :<|> "documents-from-write-nodes" :> DocumentsFromWriteNodes.API
163 :<|> DocumentUpload.API
165 -- TODO-ACCESS: check userId CanRenameNode nodeId
166 -- TODO-EVENTS: NodeRenamed RenameNode or re-use some more general NodeEdited...
167 type RenameApi = Summary " Rename Node"
168 :> ReqBody '[JSON] RenameNode
171 type PostNodeApi = Summary " PostNode Node with ParentId as {id}"
172 :> ReqBody '[JSON] PostNode
173 :> Post '[JSON] [NodeId]
175 type ChildrenApi a = Summary " Summary children"
176 :> QueryParam "type" NodeType
177 :> QueryParam "offset" Int
178 :> QueryParam "limit" Int
179 -- :> Get '[JSON] [Node a]
180 :> Get '[JSON] (NodeTableResult a)
182 ------------------------------------------------------------------------
183 type NodeNodeAPI a = Get '[JSON] (Node a)
185 nodeNodeAPI :: forall proxy a. (JSONB a, ToJSON a)
190 -> GargServer (NodeNodeAPI a)
191 nodeNodeAPI p uId cId nId = withAccess (Proxy :: Proxy (NodeNodeAPI a)) Proxy uId (PathNodeNode cId nId) nodeNodeAPI'
193 nodeNodeAPI' :: GargServer (NodeNodeAPI a)
194 nodeNodeAPI' = getNodeWith nId p
196 ------------------------------------------------------------------------
197 -- TODO: make the NodeId type indexed by `a`, then we no longer need the proxy.
198 nodeAPI :: forall proxy a.
203 , MimeUnrender JSON a
207 -> GargServer (NodeAPI a)
208 nodeAPI p uId id' = withAccess (Proxy :: Proxy (NodeAPI a)) Proxy uId (PathNode id') nodeAPI'
210 nodeAPI' :: GargServer (NodeAPI a)
211 nodeAPI' = getNodeWith id' p
213 :<|> postNode uId id'
214 :<|> postNodeAsyncAPI uId id'
215 :<|> FrameCalcUpload.api uId id'
217 :<|> Update.api uId id'
218 :<|> Action.deleteNode (RootId $ NodeId uId) id'
219 :<|> getChildren id' p
223 :<|> apiNgramsTableCorpus id'
228 :<|> Share.api (RootId $ NodeId uId) id'
239 :<|> phyloAPI id' uId
240 :<|> moveNode (RootId $ NodeId uId) id'
241 -- :<|> nodeAddAPI id'
242 -- :<|> postUpload id'
243 :<|> Share.unPublish id'
246 :<|> fileAsyncApi uId id'
248 :<|> DocumentsFromWriteNodes.api uId id'
249 :<|> DocumentUpload.api uId id'
252 ------------------------------------------------------------------------
253 data RenameNode = RenameNode { r_name :: Text }
256 ------------------------------------------------------------------------
257 ------------------------------------------------------------------------
258 type CatApi = Summary " To Categorize NodeNodes: 0 for delete, 1/null neutral, 2 favorite"
259 :> ReqBody '[JSON] NodesToCategory
262 data NodesToCategory = NodesToCategory { ntc_nodesId :: [NodeId]
263 , ntc_category :: Int
267 -- TODO unPrefix "ntc_" FromJSON, ToJSON, ToSchema, adapt frontend.
268 instance FromJSON NodesToCategory
269 instance ToJSON NodesToCategory
270 instance ToSchema NodesToCategory
272 catApi :: CorpusId -> GargServer CatApi
275 putCat :: CorpusId -> NodesToCategory -> Cmd err [Int]
276 putCat cId cs' = nodeNodesCategory $ map (\n -> (cId, n, ntc_category cs')) (ntc_nodesId cs')
278 ------------------------------------------------------------------------
279 type ScoreApi = Summary " To Score NodeNodes"
280 :> ReqBody '[JSON] NodesToScore
283 data NodesToScore = NodesToScore { nts_nodesId :: [NodeId]
288 -- TODO unPrefix "ntc_" FromJSON, ToJSON, ToSchema, adapt frontend.
289 instance FromJSON NodesToScore
290 instance ToJSON NodesToScore
291 instance ToSchema NodesToScore
293 scoreApi :: CorpusId -> GargServer ScoreApi
296 putScore :: CorpusId -> NodesToScore -> Cmd err [Int]
297 putScore cId cs' = nodeNodesScore $ map (\n -> (cId, n, nts_score cs')) (nts_nodesId cs')
299 ------------------------------------------------------------------------
300 -- TODO adapt FacetDoc -> ListDoc (and add type of document as column)
301 -- Pairing utilities to move elsewhere
302 type PairingApi = Summary " Pairing API"
303 :> QueryParam "view" TabType
304 -- TODO change TabType -> DocType (CorpusId for pairing)
305 :> QueryParam "offset" Int
306 :> QueryParam "limit" Int
307 :> QueryParam "order" OrderBy
308 :> Get '[JSON] [FacetDoc]
311 type Pairs = Summary "List of Pairs"
312 :> Get '[JSON] [AnnuaireId]
313 pairs :: CorpusId -> GargServer Pairs
315 ns <- getNodeNode cId
316 pure $ map _nn_node2_id ns
318 type PairWith = Summary "Pair a Corpus with an Annuaire"
319 :> "annuaire" :> Capture "annuaire_id" AnnuaireId
320 :> QueryParam "list_id" ListId
323 pairWith :: CorpusId -> GargServer PairWith
324 pairWith cId aId lId = do
325 r <- pairing cId aId lId
326 _ <- insertNodeNode [ NodeNode { _nn_node1_id = cId
328 , _nn_score = Nothing
329 , _nn_category = Nothing }]
333 ------------------------------------------------------------------------
334 type TreeAPI = QueryParams "type" NodeType
335 :> Get '[JSON] (Tree NodeTree)
337 :> QueryParams "type" NodeType
338 :> Get '[JSON] (Tree NodeTree)
340 treeAPI :: NodeId -> GargServer TreeAPI
341 treeAPI id = tree TreeAdvanced id
342 :<|> tree TreeFirstLevel id
344 ------------------------------------------------------------------------
345 -- | TODO Check if the name is less than 255 char
346 rename :: NodeId -> RenameNode -> Cmd err [Int]
347 rename nId (RenameNode name') = U.update (U.Rename nId name')
349 putNode :: forall err a. (HasNodeError err, JSONB a, ToJSON a)
353 putNode n h = fromIntegral <$> updateHyperdata n h
355 -------------------------------------------------------------
356 type MoveAPI = Summary "Move Node endpoint"
357 :> Capture "parent_id" ParentId
364 moveNode _u n p = update (Move n p)
365 -------------------------------------------------------------
368 $(deriveJSON (unPrefix "r_" ) ''RenameNode )
369 instance ToSchema RenameNode
370 instance Arbitrary RenameNode where
371 arbitrary = elements [RenameNode "test"]
374 -------------------------------------------------------------