2 Module : Gargantext.Database.Schema.Node
3 Description : Main requests of Node to the database
4 Copyright : (c) CNRS, 2017-Present
5 License : AGPL + CECILL v3
6 Maintainer : team@gargantext.org
7 Stability : experimental
11 {-# OPTIONS_GHC -fno-warn-name-shadowing #-}
12 {-# OPTIONS_GHC -fno-warn-orphans #-}
14 {-# LANGUAGE Arrows #-}
15 {-# LANGUAGE DeriveGeneric #-}
16 {-# LANGUAGE ConstraintKinds #-}
17 {-# LANGUAGE FlexibleContexts #-}
18 {-# LANGUAGE FlexibleInstances #-}
19 {-# LANGUAGE FunctionalDependencies #-}
20 {-# LANGUAGE OverloadedStrings #-}
21 {-# LANGUAGE MultiParamTypeClasses #-}
22 {-# LANGUAGE NoImplicitPrelude #-}
23 {-# LANGUAGE TemplateHaskell #-}
25 module Gargantext.Database.Schema.Node where
27 import Control.Arrow (returnA)
28 import Control.Lens (set)
29 import Control.Lens.TH (makeLensesWith, abbreviatedFields)
31 import Data.ByteString (ByteString)
32 import Data.Maybe (Maybe(..), fromMaybe)
33 import Data.Profunctor.Product.TH (makeAdaptorAndInstance)
34 import Data.Text (Text, pack)
35 import Data.Time (UTCTime)
36 import Database.PostgreSQL.Simple (Connection)
37 import Database.PostgreSQL.Simple.FromField (FromField, fromField)
38 import GHC.Int (Int64)
39 import Gargantext.Core (Lang(..))
40 import Gargantext.Core.Types
41 import Gargantext.Core.Types.Individu (Username)
42 import Gargantext.Core.Types.Main (UserId)
43 import Gargantext.Database.Config (nodeTypeId)
44 import Gargantext.Database.Queries.Filter (limit', offset')
45 import Gargantext.Database.Types.Node (NodeType, defaultCorpus, Hyperdata)
46 import Gargantext.Database.Utils
47 import Gargantext.Prelude hiding (sum, head)
48 import Opaleye hiding (FromField)
49 import Opaleye.Internal.QueryArr (Query)
50 import Prelude hiding (null, id, map, sum)
51 import qualified Data.ByteString as DB
52 import qualified Data.ByteString.Lazy as DBL
53 import qualified Data.Profunctor.Product as PP
55 ------------------------------------------------------------------------
56 instance FromField HyperdataAny
58 fromField = fromField'
60 instance FromField HyperdataCorpus
62 fromField = fromField'
64 instance FromField HyperdataDocument
66 fromField = fromField'
68 instance FromField HyperdataDocumentV3
70 fromField = fromField'
72 instance FromField HyperdataUser
74 fromField = fromField'
76 instance FromField HyperdataList
78 fromField = fromField'
80 instance FromField HyperdataAnnuaire
82 fromField = fromField'
83 ------------------------------------------------------------------------
84 instance QueryRunnerColumnDefault PGJsonb HyperdataAny
86 queryRunnerColumnDefault = fieldQueryRunnerColumn
88 instance QueryRunnerColumnDefault PGJsonb HyperdataDocument
90 queryRunnerColumnDefault = fieldQueryRunnerColumn
92 instance QueryRunnerColumnDefault PGJsonb HyperdataDocumentV3
94 queryRunnerColumnDefault = fieldQueryRunnerColumn
96 instance QueryRunnerColumnDefault PGJsonb HyperdataCorpus
98 queryRunnerColumnDefault = fieldQueryRunnerColumn
100 instance QueryRunnerColumnDefault PGJsonb HyperdataUser
102 queryRunnerColumnDefault = fieldQueryRunnerColumn
104 instance QueryRunnerColumnDefault PGJsonb HyperdataList
106 queryRunnerColumnDefault = fieldQueryRunnerColumn
108 instance QueryRunnerColumnDefault PGJsonb HyperdataAnnuaire
110 queryRunnerColumnDefault = fieldQueryRunnerColumn
112 instance QueryRunnerColumnDefault PGTSVector (Maybe TSVector)
114 queryRunnerColumnDefault = fieldQueryRunnerColumn
117 ------------------------------------------------------------------------
120 -- TODO Classe HasDefault where
121 -- default NodeType = Hyperdata
122 ------------------------------------------------------------------------
123 $(makeAdaptorAndInstance "pNode" ''NodePoly)
124 $(makeLensesWith abbreviatedFields ''NodePoly)
125 $(makeAdaptorAndInstance "pNodeSearch" ''NodePolySearch)
126 $(makeLensesWith abbreviatedFields ''NodePolySearch)
128 type NodeWrite = NodePoly (Maybe (Column PGInt4 ))
131 (Column (Nullable PGInt4 ))
133 (Maybe (Column PGTimestamptz))
136 type NodeRead = NodePoly (Column PGInt4 )
139 (Column (Nullable PGInt4 ))
141 (Column PGTimestamptz )
145 type NodeReadNull = NodePoly (Column (Nullable PGInt4 ))
146 (Column (Nullable PGInt4 ))
147 (Column (Nullable PGInt4 ))
148 (Column (Nullable PGInt4 ))
149 (Column (Nullable PGText ))
150 (Column (Nullable PGTimestamptz ))
151 (Column (Nullable PGJsonb))
153 nodeTable :: Table NodeWrite NodeRead
154 nodeTable = Table "nodes" (pNode Node { _node_id = optional "id"
155 , _node_typename = required "typename"
156 , _node_userId = required "user_id"
158 , _node_parentId = required "parent_id"
159 , _node_name = required "name"
160 , _node_date = optional "date"
162 , _node_hyperdata = required "hyperdata"
166 queryNodeTable :: Query NodeRead
167 queryNodeTable = queryTable nodeTable
169 -- | TODO remove below
170 type NodeWrite' = NodePoly (Maybe Int) Int Int (Maybe ParentId) Text (Maybe UTCTime) ByteString
173 nodeTable' :: Table (Maybe (Column PGInt4)
176 ,Maybe (Column PGInt4)
178 ,Maybe (Column PGTimestamptz)
186 ,(Column PGTimestamptz)
190 nodeTable' = Table "nodes" (PP.p7 ( optional "id"
191 , required "typename"
194 , optional "parent_id"
198 , required "hyperdata"
202 ------------------------------------------------------------------------
203 -- | Node(Read|Write)Search is slower than Node(Write|Read) use it
204 -- for full text search only
206 type NodeSearchWrite = NodePolySearch (Maybe (Column PGInt4 ))
209 (Column (Nullable PGInt4 ))
211 (Maybe (Column PGTimestamptz))
213 (Maybe (Column PGTSVector))
215 type NodeSearchRead = NodePolySearch (Column PGInt4 )
218 (Column (Nullable PGInt4 ))
220 (Column PGTimestamptz )
225 type NodeSearchReadNull = NodePolySearch (Column (Nullable PGInt4 ))
226 (Column (Nullable PGInt4 ))
227 (Column (Nullable PGInt4 ))
228 (Column (Nullable PGInt4 ))
229 (Column (Nullable PGText ))
230 (Column (Nullable PGTimestamptz ))
231 (Column (Nullable PGJsonb))
232 (Column (Nullable PGTSVector))
236 nodeTableSearch :: Table NodeSearchWrite NodeSearchRead
237 nodeTableSearch = Table "nodes" (pNodeSearch NodeSearch { _ns_id = optional "id"
238 , _ns_typename = required "typename"
239 , _ns_userId = required "user_id"
241 , _ns_parentId = required "parent_id"
242 , _ns_name = required "name"
243 , _ns_date = optional "date"
245 , _ns_hyperdata = required "hyperdata"
246 , _ns_search = optional "search"
251 queryNodeSearchTable :: Query NodeSearchRead
252 queryNodeSearchTable = queryTable nodeTableSearch
255 selectNode :: Column PGInt4 -> Query NodeRead
256 selectNode id = proc () -> do
257 row <- queryNodeTable -< ()
258 restrict -< _node_id row .== id
262 runGetNodes :: Query NodeRead -> Cmd [NodeAny]
263 runGetNodes q = mkCmd $ \conn -> runQuery conn q
265 ------------------------------------------------------------------------
266 ------------------------------------------------------------------------
268 -- | order by publication date
269 -- Favorites (Bool), node_ngrams
270 selectNodesWith :: ParentId -> Maybe NodeType
271 -> Maybe Offset -> Maybe Limit -> Query NodeRead
272 selectNodesWith parentId maybeNodeType maybeOffset maybeLimit =
273 --offset' maybeOffset $ limit' maybeLimit $ orderBy (asc (hyperdataDocument_Publication_date . node_hyperdata)) $ selectNodesWith' parentId typeId
274 limit' maybeLimit $ offset' maybeOffset
275 $ orderBy (asc _node_id)
276 $ selectNodesWith' parentId maybeNodeType
278 selectNodesWith' :: ParentId -> Maybe NodeType -> Query NodeRead
279 selectNodesWith' parentId maybeNodeType = proc () -> do
280 node <- (proc () -> do
281 row@(Node _ typeId _ parentId' _ _ _) <- queryNodeTable -< ()
282 restrict -< parentId' .== (toNullable $ pgInt4 parentId)
284 let typeId' = maybe 0 nodeTypeId maybeNodeType
286 restrict -< if typeId' > 0
287 then typeId .== (pgInt4 (typeId' :: Int))
289 returnA -< row ) -< ()
293 --type Cmd' a = forall m. (MonadReader env m, HasConnection env, MonadIO m) => m a
296 -- deleteNode :: (MonadReader Connection m, MonadIO m) => Int -> m Int
297 -- deleteNode :: Int -> Cmd' Int
299 deleteNode :: Int -> Cmd Int
300 deleteNode n = mkCmd $ \conn ->
301 fromIntegral <$> runDelete conn nodeTable
302 (\(Node n_id _ _ _ _ _ _) -> n_id .== pgInt4 n)
304 deleteNodes :: [Int] -> Cmd Int
305 deleteNodes ns = mkCmd $ \conn ->
306 fromIntegral <$> runDelete conn nodeTable
307 (\(Node n_id _ _ _ _ _ _) -> in_ ((map pgInt4 ns)) n_id)
310 getNodesWith :: JSONB a => Connection -> Int -> proxy a -> Maybe NodeType
311 -> Maybe Offset -> Maybe Limit -> IO [Node a]
312 getNodesWith conn parentId _ nodeType maybeOffset maybeLimit =
313 runQuery conn $ selectNodesWith parentId nodeType maybeOffset maybeLimit
317 getNodesWithParentId :: Int
318 -> Maybe Text -> Connection -> IO [NodeAny]
319 getNodesWithParentId n _ conn = runQuery conn $ selectNodesWithParentID n
321 getNodesWithParentId' :: Int
322 -> Maybe Text -> Connection -> IO [NodeAny]
323 getNodesWithParentId' n _ conn = runQuery conn $ selectNodesWithParentID n
326 ------------------------------------------------------------------------
327 getDocumentsV3WithParentId :: Connection -> Int -> IO [Node HyperdataDocumentV3]
328 getDocumentsV3WithParentId conn n = runQuery conn $ selectNodesWith' n (Just NodeDocument)
330 getDocumentsWithParentId :: Connection -> Int -> IO [Node HyperdataDocument]
331 getDocumentsWithParentId conn n = runQuery conn $ selectNodesWith' n (Just NodeDocument)
333 getListsWithParentId :: Connection -> Int -> IO [Node HyperdataList]
334 getListsWithParentId conn n = runQuery conn $ selectNodesWith' n (Just NodeList)
336 getCorporaWithParentId :: Connection -> Int -> IO [Node HyperdataCorpus]
337 getCorporaWithParentId conn n = runQuery conn $ selectNodesWith' n (Just NodeCorpus)
339 getCorporaWithParentId' :: Int -> Cmd [Node HyperdataCorpus]
340 getCorporaWithParentId' n = mkCmd $ \conn -> runQuery conn $ selectNodesWith' n (Just NodeCorpus)
343 ------------------------------------------------------------------------
344 selectNodesWithParentID :: Int -> Query NodeRead
345 selectNodesWithParentID n = proc () -> do
346 row@(Node _ _ _ parent_id _ _ _) <- queryNodeTable -< ()
348 then parent_id .== (toNullable $ pgInt4 n)
349 else isNull parent_id
352 selectNodesWithType :: Column PGInt4 -> Query NodeRead
353 selectNodesWithType type_id = proc () -> do
354 row@(Node _ tn _ _ _ _ _) <- queryNodeTable -< ()
355 restrict -< tn .== type_id
358 type JSONB = QueryRunnerColumnDefault PGJsonb
360 getNode :: JSONB a => Connection -> Int -> proxy a -> IO (Node a)
361 getNode conn id _ = do
362 fromMaybe (error $ "Node does node exist: " <> show id) . headMay <$> runQuery conn (limit 1 $ selectNode (pgInt4 id))
364 getNodesWithType :: Connection -> Column PGInt4 -> IO [Node HyperdataDocument]
365 getNodesWithType conn type_id = do
366 runQuery conn $ selectNodesWithType type_id
368 ------------------------------------------------------------------------
370 ------------------------------------------------------------------------
371 defaultUser :: HyperdataUser
372 defaultUser = HyperdataUser (Just $ (pack . show) EN)
374 nodeUserW :: Maybe Name -> Maybe HyperdataUser -> UserId -> NodeWrite'
375 nodeUserW maybeName maybeHyperdata = node NodeUser name user Nothing
377 name = maybe "User" identity maybeName
378 user = maybe defaultUser identity maybeHyperdata
379 ------------------------------------------------------------------------
380 defaultFolder :: HyperdataFolder
381 defaultFolder = HyperdataFolder (Just "Markdown Description")
383 nodeFolderW :: Maybe Name -> Maybe HyperdataFolder -> ParentId -> UserId -> NodeWrite'
384 nodeFolderW maybeName maybeFolder pid = node NodeFolder name folder (Just pid)
386 name = maybe "Folder" identity maybeName
387 folder = maybe defaultFolder identity maybeFolder
388 ------------------------------------------------------------------------
389 nodeCorpusW :: Maybe Name -> Maybe HyperdataCorpus -> ParentId -> UserId -> NodeWrite'
390 nodeCorpusW maybeName maybeCorpus pId = node NodeCorpus name corpus (Just pId)
392 name = maybe "Corpus" identity maybeName
393 corpus = maybe defaultCorpus identity maybeCorpus
394 --------------------------
395 defaultDocument :: HyperdataDocument
396 defaultDocument = hyperdataDocument
398 nodeDocumentW :: Maybe Name -> Maybe HyperdataDocument -> CorpusId -> UserId -> NodeWrite'
399 nodeDocumentW maybeName maybeDocument cId = node NodeDocument name doc (Just cId)
401 name = maybe "Document" identity maybeName
402 doc = maybe defaultDocument identity maybeDocument
403 ------------------------------------------------------------------------
404 defaultAnnuaire :: HyperdataAnnuaire
405 defaultAnnuaire = HyperdataAnnuaire (Just "Title") (Just "Description")
407 nodeAnnuaireW :: Maybe Name -> Maybe HyperdataAnnuaire -> ParentId -> UserId -> NodeWrite'
408 nodeAnnuaireW maybeName maybeAnnuaire pId = node NodeAnnuaire name annuaire (Just pId)
410 name = maybe "Annuaire" identity maybeName
411 annuaire = maybe defaultAnnuaire identity maybeAnnuaire
412 --------------------------
414 ------------------------------------------------------------------------
415 arbitraryList :: HyperdataList
416 arbitraryList = HyperdataList (Just "Preferences")
418 nodeListW :: Maybe Name -> Maybe HyperdataList -> ParentId -> UserId -> NodeWrite'
419 nodeListW maybeName maybeList pId = node NodeList name list (Just pId)
421 name = maybe "Listes" identity maybeName
422 list = maybe arbitraryList identity maybeList
424 ------------------------------------------------------------------------
425 arbitraryGraph :: HyperdataGraph
426 arbitraryGraph = HyperdataGraph (Just "Preferences")
428 nodeGraphW :: Maybe Name -> Maybe HyperdataGraph -> ParentId -> UserId -> NodeWrite'
429 nodeGraphW maybeName maybeGraph pId = node NodeGraph name graph (Just pId)
431 name = maybe "Graph" identity maybeName
432 graph = maybe arbitraryGraph identity maybeGraph
434 ------------------------------------------------------------------------
436 arbitraryDashboard :: HyperdataDashboard
437 arbitraryDashboard = HyperdataDashboard (Just "Preferences")
439 nodeDashboardW :: Maybe Name -> Maybe HyperdataDashboard -> ParentId -> UserId -> NodeWrite'
440 nodeDashboardW maybeName maybeDashboard pId = node NodeDashboard name dashboard (Just pId)
442 name = maybe "Dashboard" identity maybeName
443 dashboard = maybe arbitraryDashboard identity maybeDashboard
447 ------------------------------------------------------------------------
448 node :: (ToJSON a, Hyperdata a) => NodeType -> Name -> a -> Maybe ParentId -> UserId -> NodeWrite'
449 node nodeType name hyperData parentId userId = Node Nothing typeId userId parentId name Nothing byteData
451 typeId = nodeTypeId nodeType
452 byteData = DB.pack . DBL.unpack $ encode hyperData
454 -------------------------------
455 node2row :: (Functor maybe1, Functor maybe2, Functor maybe3) =>
456 NodePoly (maybe1 Int) Int Int
457 (maybe2 Int) Text (maybe3 UTCTime)
459 -> ( maybe1 (Column PGInt4), Column PGInt4, Column PGInt4
460 , maybe2 (Column PGInt4), Column PGText, maybe3 (Column PGTimestamptz)
462 node2row (Node id tn ud pid nm dt hp) = ((pgInt4 <$> id)
472 ------------------------------------------------------------------------
473 insertNodesR' :: [NodeWrite'] -> Cmd [Int]
474 insertNodesR' ns = mkCmd $ \c -> insertNodesR ns c
476 insertNodes :: [NodeWrite'] -> Connection -> IO Int64
477 insertNodes ns conn = runInsertMany conn nodeTable' (map node2row ns)
479 insertNodesR :: [NodeWrite'] -> Connection -> IO [Int]
480 insertNodesR ns conn = runInsertManyReturning conn nodeTable' (map node2row ns) (\(i,_,_,_,_,_,_) -> i)
481 -------------------------
482 insertNodesWithParent :: Maybe ParentId -> [NodeWrite'] -> Connection -> IO Int64
483 insertNodesWithParent pid ns conn = insertNodes (map (set node_parentId pid) ns) conn
485 insertNodesWithParentR :: Maybe ParentId -> [NodeWrite'] -> Connection -> IO [Int]
486 insertNodesWithParentR pid ns conn = insertNodesR (map (set node_parentId pid) ns) conn
487 ------------------------------------------------------------------------
488 -- TODO Hierachy of Nodes
489 -- post and get same types Node' and update if changes
491 {- TODO semantic to achieve
492 post c uid pid [ Node' NodeCorpus "name" "{}" []
493 , Node' NodeFolder "name" "{}" [Node' NodeCorpus "test 2" "" [ Node' NodeDocument "title" "metaData" []
494 , Node' NodeDocument "title" "jsonData" []
499 ------------------------------------------------------------------------
502 -- currently this function remove the child relation
503 -- needs a Temporary type between Node' and NodeWriteT
504 node2table :: UserId -> Maybe ParentId -> Node' -> NodeWriteT
505 node2table uid pid (Node' nt txt v []) = ( Nothing, (pgInt4$ nodeTypeId nt), (pgInt4 uid), (fmap pgInt4 pid)
506 , pgStrictText txt, Nothing, pgStrictJSONB $ DB.pack $ DBL.unpack $ encode v)
507 node2table _ _ (Node' _ _ _ _) = panic "node2table: should not happen, Tree insert not implemented yet"
510 data Node' = Node' { _n_type :: NodeType
513 , _n_children :: [Node']
516 -- | TODO NodeWriteT -> NodeWrite
517 type NodeWriteT = ( Maybe (Column PGInt4)
520 , Maybe (Column PGInt4)
522 , Maybe (Column PGTimestamptz)
526 mkNode' :: [NodeWrite] -> Cmd Int64
527 mkNode' ns = mkCmd $ \conn -> runInsertMany conn nodeTable ns
529 -- TODO: replace mkNodeR'
530 mkNodeR'' :: [NodeWrite] -> Cmd [Int]
531 mkNodeR'' ns = mkCmd $ \conn -> runInsertManyReturning conn nodeTable ns (_node_id)
533 mkNodeR' :: [NodeWriteT] -> Cmd [Int]
534 mkNodeR' ns = mkCmd $ \conn -> runInsertManyReturning conn nodeTable' ns (\(i,_,_,_,_,_,_) -> i)
536 ------------------------------------------------------------------------
538 data NewNode = NewNode { _newNodeId :: Int
539 , _newNodeChildren :: [Int] }
542 postNode :: UserId -> Maybe ParentId -> Node' -> Cmd NewNode
543 postNode uid pid (Node' nt txt v []) = do
544 pids <- mkNodeR' [node2table uid pid (Node' nt txt v [])]
546 [pid] -> pure $ NewNode pid []
547 _ -> panic "postNode: only one pid expected"
549 postNode uid pid (Node' NodeCorpus txt v ns) = do
550 NewNode pid' _ <- postNode uid pid (Node' NodeCorpus txt v [])
551 pids <- mkNodeR' (concat $ map (\n -> [childWith uid pid' n]) ns)
552 pure $ NewNode pid' pids
554 postNode uid pid (Node' NodeAnnuaire txt v ns) = do
555 NewNode pid' _ <- postNode uid pid (Node' NodeAnnuaire txt v [])
556 pids <- mkNodeR' (concat $ map (\n -> [childWith uid pid' n]) ns)
557 pure $ NewNode pid' pids
558 postNode _ _ (Node' _ _ _ _) = panic "TODO: postNode for this type not implemented yet"
561 childWith :: UserId -> ParentId -> Node' -> NodeWriteT
562 childWith uId pId (Node' NodeDocument txt v []) = node2table uId (Just pId) (Node' NodeDocument txt v [])
563 childWith uId pId (Node' NodeContact txt v []) = node2table uId (Just pId) (Node' NodeContact txt v [])
564 childWith _ _ (Node' _ _ _ _) = panic "This NodeType can not be a child"
568 mk :: Connection -> NodeType -> Maybe ParentId -> Text -> IO [Int]
569 mk c nt pId name = mk' c nt userId pId name
573 mk' :: Connection -> NodeType -> UserId -> Maybe ParentId -> Text -> IO [Int]
574 mk' c nt uId pId name = map fromIntegral <$> insertNodesWithParentR pId [node nt name hd pId uId] c
576 hd = HyperdataUser . Just . pack $ show EN
580 mk'' :: NodeType -> Maybe ParentId -> UserId -> Name -> Cmd [Int]
581 mk'' NodeUser Nothing uId name = mkCmd $ \c -> mk' c NodeUser uId Nothing name
582 mk'' NodeUser _ _ _ = panic "NodeUser do not have any parent"
583 mk'' _ Nothing _ _ = panic "NodeType does have a parent"
584 mk'' nt pId uId name = mkCmd $ \c -> mk' c nt uId pId name
587 mkRoot :: Username -> UserId -> Cmd [Int]
588 mkRoot uname uId = case uId > 0 of
589 False -> panic "UserId <= 0"
590 True -> mk'' NodeUser Nothing uId uname
592 mkCorpus :: Maybe Name -> Maybe HyperdataCorpus -> ParentId -> UserId -> Cmd [Int]
593 mkCorpus n h p u = insertNodesR' [nodeCorpusW n h p u]
596 getOrMkList :: ParentId -> UserId -> Cmd Int
597 getOrMkList pId uId = do
598 maybeList <- defaultListSafe' pId
600 Nothing -> maybe (panic "no list") identity <$> headMay <$> mkList pId uId
603 defaultListSafe' :: CorpusId -> Cmd (Maybe ListId)
604 defaultListSafe' cId = mkCmd $ \c -> do
605 maybeNode <- headMay <$> getListsWithParentId c cId
607 Nothing -> pure Nothing
608 (Just node) -> pure $ Just $ _node_id node
611 defaultListSafe :: Connection -> CorpusId -> IO (Maybe ListId)
612 defaultListSafe c cId = do
613 maybeNode <- headMay <$> getListsWithParentId c cId
615 Nothing -> pure Nothing
616 (Just node) -> pure $ Just $ _node_id node
618 defaultList :: Connection -> CorpusId -> IO ListId
619 defaultList c cId = maybe (panic errMessage) identity <$> defaultListSafe c cId
621 errMessage = "Gargantext.API.Ngrams.defaultList: no list found"
623 mkList :: ParentId -> UserId -> Cmd [Int]
624 mkList p u = insertNodesR' [nodeListW Nothing Nothing p u]
626 mkGraph :: ParentId -> UserId -> Cmd [Int]
627 mkGraph p u = insertNodesR' [nodeGraphW Nothing Nothing p u]
629 mkDashboard :: ParentId -> UserId -> Cmd [Int]
630 mkDashboard p u = insertNodesR' [nodeDashboardW Nothing Nothing p u]
632 mkAnnuaire :: ParentId -> UserId -> Cmd [Int]
633 mkAnnuaire p u = insertNodesR' [nodeAnnuaireW Nothing Nothing p u]
635 -- | Default CorpusId Master and ListId Master