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 RankNTypes #-}
24 {-# LANGUAGE TemplateHaskell #-}
26 module Gargantext.Database.Schema.Node where
28 import Control.Arrow (returnA)
29 import Control.Lens (Prism', set, view, (#), (^?))
30 import Control.Lens.TH (makeLensesWith, abbreviatedFields)
31 import Control.Monad.Error.Class (MonadError(..))
33 import Data.Maybe (Maybe(..), fromMaybe)
34 import Data.Profunctor.Product.TH (makeAdaptorAndInstance)
35 import Data.Text (Text, pack)
36 import Database.PostgreSQL.Simple.FromField (FromField, fromField)
37 import GHC.Int (Int64)
38 import Gargantext.Core (Lang(..))
39 import Gargantext.Core.Types
40 import Gargantext.Core.Types.Individu (Username)
41 import Gargantext.Database.Config (nodeTypeId)
42 import Gargantext.Database.Queries.Filter (limit', offset')
43 import Gargantext.Database.Types.Node (NodeType, defaultCorpus, Hyperdata)
44 import Gargantext.Database.Utils
45 import Gargantext.Prelude hiding (sum, head)
46 import Opaleye hiding (FromField)
47 import Opaleye.Internal.QueryArr (Query)
48 import Prelude hiding (null, id, map, sum)
50 ------------------------------------------------------------------------
52 data NodeError = NoListFound
65 class HasNodeError e where
66 _NodeError :: Prism' e NodeError
68 nodeError :: (MonadError e m, HasNodeError e) => NodeError -> m a
69 nodeError ne = throwError $ _NodeError # ne
71 catchNodeError :: (MonadError e m, HasNodeError e) => m a -> (NodeError -> m a) -> m a
72 catchNodeError f g = catchError f (\e -> maybe (throwError e) g (e ^? _NodeError))
74 ------------------------------------------------------------------------
75 instance FromField HyperdataAny where
76 fromField = fromField'
78 instance FromField HyperdataCorpus
80 fromField = fromField'
82 instance FromField HyperdataDocument
84 fromField = fromField'
86 instance FromField HyperdataDocumentV3
88 fromField = fromField'
90 instance FromField HyperdataUser
92 fromField = fromField'
94 instance FromField HyperdataList
96 fromField = fromField'
98 instance FromField HyperdataGraph
100 fromField = fromField'
102 instance FromField HyperdataAnnuaire
104 fromField = fromField'
105 ------------------------------------------------------------------------
106 instance QueryRunnerColumnDefault PGJsonb HyperdataAny
108 queryRunnerColumnDefault = fieldQueryRunnerColumn
110 instance QueryRunnerColumnDefault PGJsonb HyperdataDocument
112 queryRunnerColumnDefault = fieldQueryRunnerColumn
114 instance QueryRunnerColumnDefault PGJsonb HyperdataDocumentV3
116 queryRunnerColumnDefault = fieldQueryRunnerColumn
118 instance QueryRunnerColumnDefault PGJsonb HyperdataCorpus
120 queryRunnerColumnDefault = fieldQueryRunnerColumn
122 instance QueryRunnerColumnDefault PGJsonb HyperdataUser
124 queryRunnerColumnDefault = fieldQueryRunnerColumn
126 instance QueryRunnerColumnDefault PGJsonb HyperdataList
128 queryRunnerColumnDefault = fieldQueryRunnerColumn
130 instance QueryRunnerColumnDefault PGJsonb HyperdataGraph
132 queryRunnerColumnDefault = fieldQueryRunnerColumn
134 instance QueryRunnerColumnDefault PGJsonb HyperdataAnnuaire
136 queryRunnerColumnDefault = fieldQueryRunnerColumn
138 instance QueryRunnerColumnDefault PGTSVector (Maybe TSVector)
140 queryRunnerColumnDefault = fieldQueryRunnerColumn
142 instance QueryRunnerColumnDefault PGInt4 (Maybe NodeId)
144 queryRunnerColumnDefault = fieldQueryRunnerColumn
146 instance QueryRunnerColumnDefault PGInt4 NodeId
148 queryRunnerColumnDefault = fieldQueryRunnerColumn
151 ------------------------------------------------------------------------
153 -- TODO Classe HasDefault where
154 -- default NodeType = Hyperdata
155 ------------------------------------------------------------------------
156 $(makeAdaptorAndInstance "pNode" ''NodePoly)
157 $(makeLensesWith abbreviatedFields ''NodePoly)
159 $(makeAdaptorAndInstance "pNodeSearch" ''NodePolySearch)
160 $(makeLensesWith abbreviatedFields ''NodePolySearch)
162 type NodeWrite = NodePoly (Maybe (Column PGInt4) )
165 (Maybe (Column PGInt4) )
167 (Maybe (Column PGTimestamptz))
170 type NodeRead = NodePoly (Column PGInt4 )
175 (Column PGTimestamptz )
178 type NodeReadNull = NodePoly (Column (Nullable PGInt4))
179 (Column (Nullable PGInt4))
180 (Column (Nullable PGInt4))
181 (Column (Nullable PGInt4))
182 (Column (Nullable PGText))
183 (Column (Nullable PGTimestamptz))
184 (Column (Nullable PGJsonb))
186 nodeTable :: Table NodeWrite NodeRead
187 nodeTable = Table "nodes" (pNode Node { _node_id = optional "id"
188 , _node_typename = required "typename"
189 , _node_userId = required "user_id"
191 , _node_parentId = optional "parent_id"
192 , _node_name = required "name"
193 , _node_date = optional "date"
195 , _node_hyperdata = required "hyperdata"
199 queryNodeTable :: Query NodeRead
200 queryNodeTable = queryTable nodeTable
202 ------------------------------------------------------------------------
203 -- | Node(Read|Write)Search is slower than Node(Write|Read) use it
204 -- for full text search only
205 type NodeSearchWrite =
207 (Maybe (Column PGInt4) )
210 (Column (Nullable PGInt4) )
212 (Maybe (Column PGTimestamptz))
214 (Maybe (Column PGTSVector) )
216 type NodeSearchRead =
221 (Column (Nullable PGInt4 ))
223 (Column PGTimestamptz )
227 type NodeSearchReadNull =
229 (Column (Nullable PGInt4) )
230 (Column (Nullable PGInt4) )
231 (Column (Nullable PGInt4) )
232 (Column (Nullable PGInt4) )
233 (Column (Nullable PGText) )
234 (Column (Nullable PGTimestamptz))
235 (Column (Nullable PGJsonb) )
236 (Column (Nullable PGTSVector) )
239 nodeTableSearch :: Table NodeSearchWrite NodeSearchRead
240 nodeTableSearch = Table "nodes" (pNodeSearch NodeSearch { _ns_id = optional "id"
241 , _ns_typename = required "typename"
242 , _ns_userId = required "user_id"
244 , _ns_parentId = required "parent_id"
245 , _ns_name = required "name"
246 , _ns_date = optional "date"
248 , _ns_hyperdata = required "hyperdata"
249 , _ns_search = optional "search"
254 queryNodeSearchTable :: Query NodeSearchRead
255 queryNodeSearchTable = queryTable nodeTableSearch
257 selectNode :: Column PGInt4 -> Query NodeRead
258 selectNode id = proc () -> do
259 row <- queryNodeTable -< ()
260 restrict -< _node_id row .== id
263 runGetNodes :: Query NodeRead -> Cmd err [NodeAny]
264 runGetNodes = runOpaQuery
266 ------------------------------------------------------------------------
267 ------------------------------------------------------------------------
269 -- | order by publication date
270 -- Favorites (Bool), node_ngrams
271 selectNodesWith :: ParentId -> Maybe NodeType
272 -> Maybe Offset -> Maybe Limit -> Query NodeRead
273 selectNodesWith parentId maybeNodeType maybeOffset maybeLimit =
274 --offset' maybeOffset $ limit' maybeLimit $ orderBy (asc (hyperdataDocument_Publication_date . node_hyperdata)) $ selectNodesWith' parentId typeId
275 limit' maybeLimit $ offset' maybeOffset
276 $ orderBy (asc _node_id)
277 $ selectNodesWith' parentId maybeNodeType
279 selectNodesWith' :: ParentId -> Maybe NodeType -> Query NodeRead
280 selectNodesWith' parentId maybeNodeType = proc () -> do
281 node <- (proc () -> do
282 row@(Node _ typeId _ parentId' _ _ _) <- queryNodeTable -< ()
283 restrict -< parentId' .== (pgNodeId parentId)
285 let typeId' = maybe 0 nodeTypeId maybeNodeType
287 restrict -< if typeId' > 0
288 then typeId .== (pgInt4 (typeId' :: Int))
290 returnA -< row ) -< ()
294 deleteNode :: NodeId -> Cmd err Int
295 deleteNode n = mkCmd $ \conn ->
296 fromIntegral <$> runDelete conn nodeTable
297 (\(Node n_id _ _ _ _ _ _) -> n_id .== pgNodeId n)
299 deleteNodes :: [NodeId] -> Cmd err Int
300 deleteNodes ns = mkCmd $ \conn ->
301 fromIntegral <$> runDelete conn nodeTable
302 (\(Node n_id _ _ _ _ _ _) -> in_ ((map pgNodeId ns)) n_id)
304 -- TODO: NodeType should match with `a'
305 getNodesWith :: JSONB a => NodeId -> proxy a -> Maybe NodeType
306 -> Maybe Offset -> Maybe Limit -> Cmd err [Node a]
307 getNodesWith parentId _ nodeType maybeOffset maybeLimit =
308 runOpaQuery $ selectNodesWith parentId nodeType maybeOffset maybeLimit
310 -- TODO: Why is the second parameter ignored?
311 -- TODO: Why not use getNodesWith?
312 getNodesWithParentId :: NodeId -> Maybe Text -> Cmd err [NodeAny]
313 getNodesWithParentId n _ = runOpaQuery $ selectNodesWithParentID n
315 ------------------------------------------------------------------------
316 getDocumentsV3WithParentId :: NodeId -> Cmd err [Node HyperdataDocumentV3]
317 getDocumentsV3WithParentId n = runOpaQuery $ selectNodesWith' n (Just NodeDocument)
319 -- TODO: merge with getDocumentsWithParentId by having a class IsHyperdataDocument
320 getDocumentsWithParentId :: NodeId -> Cmd err [Node HyperdataDocument]
321 getDocumentsWithParentId n = runOpaQuery $ selectNodesWith' n (Just NodeDocument)
323 getListsWithParentId :: NodeId -> Cmd err [Node HyperdataList]
324 getListsWithParentId n = runOpaQuery $ selectNodesWith' n (Just NodeList)
326 getCorporaWithParentId :: NodeId -> Cmd err [Node HyperdataCorpus]
327 getCorporaWithParentId n = runOpaQuery $ selectNodesWith' n (Just NodeCorpus)
329 ------------------------------------------------------------------------
330 selectNodesWithParentID :: NodeId -> Query NodeRead
331 selectNodesWithParentID n = proc () -> do
332 row@(Node _ _ _ parent_id _ _ _) <- queryNodeTable -< ()
333 restrict -< parent_id .== (pgNodeId n)
336 selectNodesWithType :: Column PGInt4 -> Query NodeRead
337 selectNodesWithType type_id = proc () -> do
338 row@(Node _ tn _ _ _ _ _) <- queryNodeTable -< ()
339 restrict -< tn .== type_id
342 type JSONB = QueryRunnerColumnDefault PGJsonb
344 getNode :: JSONB a => NodeId -> proxy a -> Cmd err (Node a)
346 fromMaybe (error $ "Node does node exist: " <> show nId) . headMay
347 <$> runOpaQuery (limit 1 $ selectNode (pgNodeId nId))
349 getNodesWithType :: Column PGInt4 -> Cmd err [Node HyperdataDocument]
350 getNodesWithType = runOpaQuery . selectNodesWithType
352 ------------------------------------------------------------------------
353 ------------------------------------------------------------------------
354 defaultUser :: HyperdataUser
355 defaultUser = HyperdataUser (Just $ (pack . show) EN)
357 nodeUserW :: Maybe Name -> Maybe HyperdataUser -> UserId -> NodeWrite
358 nodeUserW maybeName maybeHyperdata = node NodeUser name user Nothing
360 name = maybe "User" identity maybeName
361 user = maybe defaultUser identity maybeHyperdata
362 ------------------------------------------------------------------------
363 defaultFolder :: HyperdataFolder
364 defaultFolder = HyperdataFolder (Just "Markdown Description")
366 nodeFolderW :: Maybe Name -> Maybe HyperdataFolder -> ParentId -> UserId -> NodeWrite
367 nodeFolderW maybeName maybeFolder pid = node NodeFolder name folder (Just pid)
369 name = maybe "Folder" identity maybeName
370 folder = maybe defaultFolder identity maybeFolder
371 ------------------------------------------------------------------------
372 nodeCorpusW :: Maybe Name -> Maybe HyperdataCorpus -> ParentId -> UserId -> NodeWrite
373 nodeCorpusW maybeName maybeCorpus pId = node NodeCorpus name corpus (Just pId)
375 name = maybe "Corpus" identity maybeName
376 corpus = maybe defaultCorpus identity maybeCorpus
377 --------------------------
378 defaultDocument :: HyperdataDocument
379 defaultDocument = hyperdataDocument
381 nodeDocumentW :: Maybe Name -> Maybe HyperdataDocument -> CorpusId -> UserId -> NodeWrite
382 nodeDocumentW maybeName maybeDocument cId = node NodeDocument name doc (Just cId)
384 name = maybe "Document" identity maybeName
385 doc = maybe defaultDocument identity maybeDocument
386 ------------------------------------------------------------------------
387 defaultAnnuaire :: HyperdataAnnuaire
388 defaultAnnuaire = HyperdataAnnuaire (Just "Title") (Just "Description")
390 nodeAnnuaireW :: Maybe Name -> Maybe HyperdataAnnuaire -> ParentId -> UserId -> NodeWrite
391 nodeAnnuaireW maybeName maybeAnnuaire pId = node NodeAnnuaire name annuaire (Just pId)
393 name = maybe "Annuaire" identity maybeName
394 annuaire = maybe defaultAnnuaire identity maybeAnnuaire
395 --------------------------
397 ------------------------------------------------------------------------
398 arbitraryList :: HyperdataList
399 arbitraryList = HyperdataList (Just "Preferences")
401 nodeListW :: Maybe Name -> Maybe HyperdataList -> ParentId -> UserId -> NodeWrite
402 nodeListW maybeName maybeList pId = node NodeList name list (Just pId)
404 name = maybe "Listes" identity maybeName
405 list = maybe arbitraryList identity maybeList
407 ------------------------------------------------------------------------
408 arbitraryGraph :: HyperdataGraph
409 arbitraryGraph = HyperdataGraph (Just "Preferences")
411 nodeGraphW :: Maybe Name -> Maybe HyperdataGraph -> ParentId -> UserId -> NodeWrite
412 nodeGraphW maybeName maybeGraph pId = node NodeGraph name graph (Just pId)
414 name = maybe "Graph" identity maybeName
415 graph = maybe arbitraryGraph identity maybeGraph
417 ------------------------------------------------------------------------
419 arbitraryDashboard :: HyperdataDashboard
420 arbitraryDashboard = HyperdataDashboard (Just "Preferences")
422 nodeDashboardW :: Maybe Name -> Maybe HyperdataDashboard -> ParentId -> UserId -> NodeWrite
423 nodeDashboardW maybeName maybeDashboard pId = node NodeDashboard name dashboard (Just pId)
425 name = maybe "Dashboard" identity maybeName
426 dashboard = maybe arbitraryDashboard identity maybeDashboard
428 ------------------------------------------------------------------------
429 node :: (ToJSON a, Hyperdata a) => NodeType -> Name -> a -> Maybe ParentId -> UserId -> NodeWrite
430 node nodeType name hyperData parentId userId = Node Nothing (pgInt4 typeId) (pgInt4 userId) (pgNodeId <$> parentId) (pgStrictText name) Nothing (pgJSONB $ cs $ encode hyperData)
432 typeId = nodeTypeId nodeType
434 -------------------------------
435 insertNodes :: [NodeWrite] -> Cmd err Int64
436 insertNodes ns = mkCmd $ \conn -> runInsertMany conn nodeTable ns
438 insertNodesR :: [NodeWrite] -> Cmd err [NodeId]
439 insertNodesR ns = mkCmd $ \conn ->
440 runInsert_ conn (Insert nodeTable ns (rReturning (\(Node i _ _ _ _ _ _) -> i)) Nothing)
442 insertNodesWithParent :: Maybe ParentId -> [NodeWrite] -> Cmd err Int64
443 insertNodesWithParent pid ns = insertNodes (set node_parentId (pgNodeId <$> pid) <$> ns)
445 insertNodesWithParentR :: Maybe ParentId -> [NodeWrite] -> Cmd err [NodeId]
446 insertNodesWithParentR pid ns = insertNodesR (set node_parentId (pgNodeId <$> pid) <$> ns)
447 ------------------------------------------------------------------------
448 -- TODO Hierachy of Nodes
449 -- post and get same types Node' and update if changes
451 {- TODO semantic to achieve
452 post c uid pid [ Node' NodeCorpus "name" "{}" []
453 , Node' NodeFolder "name" "{}" [Node' NodeCorpus "test 2" "" [ Node' NodeDocument "title" "metaData" []
454 , Node' NodeDocument "title" "jsonData" []
459 ------------------------------------------------------------------------
462 -- currently this function removes the child relation
463 -- needs a Temporary type between Node' and NodeWriteT
464 node2table :: UserId -> Maybe ParentId -> Node' -> NodeWrite
465 node2table uid pid (Node' nt txt v []) = Node Nothing (pgInt4 $ nodeTypeId nt) (pgInt4 uid) (fmap pgNodeId pid) (pgStrictText txt) Nothing (pgStrictJSONB $ cs $ encode v)
466 node2table _ _ (Node' _ _ _ _) = panic "node2table: should not happen, Tree insert not implemented yet"
469 data Node' = Node' { _n_type :: NodeType
472 , _n_children :: [Node']
475 mkNode :: [NodeWrite] -> Cmd err Int64
476 mkNode ns = mkCmd $ \conn -> runInsertMany conn nodeTable ns
478 mkNodeR :: [NodeWrite] -> Cmd err [NodeId]
479 mkNodeR ns = mkCmd $ \conn -> runInsertManyReturning conn nodeTable ns (_node_id)
481 ------------------------------------------------------------------------
483 data NewNode = NewNode { _newNodeId :: NodeId
484 , _newNodeChildren :: [NodeId] }
486 postNode :: HasNodeError err => UserId -> Maybe ParentId -> Node' -> Cmd err NewNode
487 postNode uid pid (Node' nt txt v []) = do
488 pids <- mkNodeR [node2table uid pid (Node' nt txt v [])]
490 [pid'] -> pure $ NewNode pid' []
491 _ -> nodeError ManyParents
493 postNode uid pid (Node' NodeCorpus txt v ns) = do
494 NewNode pid' _ <- postNode uid pid (Node' NodeCorpus txt v [])
495 pids <- mkNodeR (concat $ map (\n -> [childWith uid pid' n]) ns)
496 pure $ NewNode pid' pids
498 postNode uid pid (Node' NodeAnnuaire txt v ns) = do
499 NewNode pid' _ <- postNode uid pid (Node' NodeAnnuaire txt v [])
500 pids <- mkNodeR (concat $ map (\n -> [childWith uid pid' n]) ns)
501 pure $ NewNode pid' pids
502 postNode _ _ (Node' _ _ _ _) = nodeError NotImplYet
505 childWith :: UserId -> ParentId -> Node' -> NodeWrite
506 childWith uId pId (Node' NodeDocument txt v []) = node2table uId (Just pId) (Node' NodeDocument txt v [])
507 childWith uId pId (Node' NodeContact txt v []) = node2table uId (Just pId) (Node' NodeContact txt v [])
508 childWith _ _ (Node' _ _ _ _) = panic "This NodeType can not be a child"
513 mkNodeWithParent :: HasNodeError err => NodeType -> Maybe ParentId -> UserId -> Name -> Cmd err [NodeId]
514 mkNodeWithParent NodeUser (Just _) _ _ = nodeError UserNoParent
515 --mkNodeWithParent _ Nothing _ _ = nodeError HasParent
516 mkNodeWithParent nt pId uId name =
517 insertNodesWithParentR pId [node nt name hd pId uId]
519 hd = HyperdataUser . Just . pack $ show EN
521 mkRoot :: HasNodeError err => Username -> UserId -> Cmd err [RootId]
522 mkRoot uname uId = case uId > 0 of
523 False -> nodeError NegativeId
524 True -> mkNodeWithParent NodeUser Nothing uId uname
526 mkCorpus :: Maybe Name -> Maybe HyperdataCorpus -> ParentId -> UserId -> Cmd err [CorpusId]
527 mkCorpus n h p u = insertNodesR [nodeCorpusW n h p u]
529 getOrMkList :: HasNodeError err => ParentId -> UserId -> Cmd err ListId
530 getOrMkList pId uId =
531 maybe (mkList' pId uId) (pure . view node_id) . headMay =<< getListsWithParentId pId
533 mkList' pId uId = maybe (nodeError MkNode) pure . headMay =<< mkList pId uId
535 -- | TODO remove defaultList
536 defaultList :: HasNodeError err => CorpusId -> Cmd err ListId
538 maybe (nodeError NoListFound) (pure . view node_id) . headMay =<< getListsWithParentId cId
540 mkList :: HasNodeError err => ParentId -> UserId -> Cmd err [NodeId]
541 mkList p u = insertNodesR [nodeListW Nothing Nothing p u]
543 mkGraph :: ParentId -> UserId -> Cmd err [GraphId]
544 mkGraph p u = insertNodesR [nodeGraphW Nothing Nothing p u]
546 mkDashboard :: ParentId -> UserId -> Cmd err [NodeId]
547 mkDashboard p u = insertNodesR [nodeDashboardW Nothing Nothing p u]
549 mkAnnuaire :: ParentId -> UserId -> Cmd err [NodeId]
550 mkAnnuaire p u = insertNodesR [nodeAnnuaireW Nothing Nothing p u]
552 -- | Default CorpusId Master and ListId Master
554 pgNodeId :: NodeId -> Column PGInt4
555 pgNodeId = pgInt4 . id2int