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, unpack, 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.Utils
44 import Gargantext.Database.Config (nodeTypeId)
45 import Gargantext.Database.Queries.Filter (limit', offset')
46 import Gargantext.Database.Types.Node (NodeType, defaultCorpus, Hyperdata)
47 import Gargantext.Database.Utils (fromField')
48 import Gargantext.Prelude hiding (sum, head)
49 import Opaleye hiding (FromField)
50 import Opaleye.Internal.QueryArr (Query)
51 import Prelude hiding (null, id, map, sum)
52 import qualified Data.ByteString as DB
53 import qualified Data.ByteString.Lazy as DBL
54 import qualified Data.Profunctor.Product as PP
56 ------------------------------------------------------------------------
57 instance FromField HyperdataAny
59 fromField = fromField'
61 instance FromField HyperdataCorpus
63 fromField = fromField'
65 instance FromField HyperdataDocument
67 fromField = fromField'
69 instance FromField HyperdataDocumentV3
71 fromField = fromField'
73 instance FromField HyperdataUser
75 fromField = fromField'
77 instance FromField HyperdataList
79 fromField = fromField'
81 instance FromField HyperdataAnnuaire
83 fromField = fromField'
84 ------------------------------------------------------------------------
85 instance QueryRunnerColumnDefault PGJsonb HyperdataAny
87 queryRunnerColumnDefault = fieldQueryRunnerColumn
89 instance QueryRunnerColumnDefault PGJsonb HyperdataDocument
91 queryRunnerColumnDefault = fieldQueryRunnerColumn
93 instance QueryRunnerColumnDefault PGJsonb HyperdataDocumentV3
95 queryRunnerColumnDefault = fieldQueryRunnerColumn
97 instance QueryRunnerColumnDefault PGJsonb HyperdataCorpus
99 queryRunnerColumnDefault = fieldQueryRunnerColumn
101 instance QueryRunnerColumnDefault PGJsonb HyperdataUser
103 queryRunnerColumnDefault = fieldQueryRunnerColumn
105 instance QueryRunnerColumnDefault PGJsonb HyperdataList
107 queryRunnerColumnDefault = fieldQueryRunnerColumn
109 instance QueryRunnerColumnDefault PGJsonb HyperdataAnnuaire
111 queryRunnerColumnDefault = fieldQueryRunnerColumn
113 instance QueryRunnerColumnDefault PGTSVector (Maybe TSVector)
115 queryRunnerColumnDefault = fieldQueryRunnerColumn
118 ------------------------------------------------------------------------
121 -- TODO Classe HasDefault where
122 -- default NodeType = Hyperdata
123 ------------------------------------------------------------------------
124 $(makeAdaptorAndInstance "pNode" ''NodePoly)
125 $(makeLensesWith abbreviatedFields ''NodePoly)
126 type NodeWrite' = NodePoly (Maybe Int) Int Int (Maybe ParentId) Text (Maybe UTCTime) ByteString (Maybe TSVector)
129 type NodeWrite = NodePoly (Maybe (Column PGInt4 ))
132 (Column (Nullable PGInt4 ))
134 (Maybe (Column PGTimestamptz))
136 (Maybe (Column PGTSVector))
138 type NodeRead = NodePoly (Column PGInt4 )
141 (Column (Nullable PGInt4 ))
143 (Column PGTimestamptz )
148 type NodeReadNull = NodePoly (Column (Nullable PGInt4 ))
149 (Column (Nullable PGInt4 ))
150 (Column (Nullable PGInt4 ))
151 (Column (Nullable PGInt4 ))
152 (Column (Nullable PGText ))
153 (Column (Nullable PGTimestamptz ))
154 (Column (Nullable PGJsonb))
155 (Column (Nullable PGTSVector))
158 nodeTable :: Table NodeWrite NodeRead
159 nodeTable = Table "nodes" (pNode Node { _node_id = optional "id"
160 , _node_typename = required "typename"
161 , _node_userId = required "user_id"
163 , _node_parentId = required "parent_id"
164 , _node_name = required "name"
165 , _node_date = optional "date"
167 , _node_hyperdata = required "hyperdata"
168 , _node_search = optional "search"
172 nodeTableSearch :: Table NodeWriteSearch NodeReadSearch
173 nodeTableSearch = Table "nodes" (pNode Node { _node_id = optional "id"
174 , _node_typename = required "typename"
175 , _node_userId = required "user_id"
177 , _node_parentId = required "parent_id"
178 , _node_name = required "name"
179 , _node_date = optional "date"
181 , _node_hyperdata = required "hyperdata"
182 , _node_search = optional "search"
187 nodeTable' :: Table (Maybe (Column PGInt4)
190 ,Maybe (Column PGInt4)
192 ,Maybe (Column PGTimestamptz)
194 ,Maybe (Column PGTSVector)
201 ,(Column PGTimestamptz)
206 nodeTable' = Table "nodes" (PP.p8 ( optional "id"
207 , required "typename"
210 , optional "parent_id"
214 , required "hyperdata"
220 queryNodeTable :: Query NodeRead
221 queryNodeTable = queryTable nodeTable
224 queryNodeTableSearch :: Query NodeReadSearch
225 queryNodeTableSearch = queryTable nodeTableSearch
229 selectNode :: Column PGInt4 -> Query NodeRead
230 selectNode id = proc () -> do
231 row <- queryNodeTable -< ()
232 restrict -< _node_id row .== id
236 runGetNodes :: Query NodeRead -> Cmd [NodeAny]
237 runGetNodes q = mkCmd $ \conn -> runQuery conn q
239 ------------------------------------------------------------------------
240 ------------------------------------------------------------------------
242 -- | order by publication date
243 -- Favorites (Bool), node_ngrams
244 selectNodesWith :: ParentId -> Maybe NodeType
245 -> Maybe Offset -> Maybe Limit -> Query NodeRead
246 selectNodesWith parentId maybeNodeType maybeOffset maybeLimit =
247 --offset' maybeOffset $ limit' maybeLimit $ orderBy (asc (hyperdataDocument_Publication_date . node_hyperdata)) $ selectNodesWith' parentId typeId
248 limit' maybeLimit $ offset' maybeOffset
249 $ orderBy (asc _node_id)
250 $ selectNodesWith' parentId maybeNodeType
252 selectNodesWith' :: ParentId -> Maybe NodeType -> Query NodeRead
253 selectNodesWith' parentId maybeNodeType = proc () -> do
254 node <- (proc () -> do
255 row@(Node _ typeId _ parentId' _ _ _ _) <- queryNodeTable -< ()
256 restrict -< parentId' .== (toNullable $ pgInt4 parentId)
258 let typeId' = maybe 0 nodeTypeId maybeNodeType
260 restrict -< if typeId' > 0
261 then typeId .== (pgInt4 (typeId' :: Int))
263 returnA -< row ) -< ()
267 --type Cmd' a = forall m. (MonadReader env m, HasConnection env, MonadIO m) => m a
270 -- deleteNode :: (MonadReader Connection m, MonadIO m) => Int -> m Int
271 -- deleteNode :: Int -> Cmd' Int
273 deleteNode :: Int -> Cmd Int
274 deleteNode n = mkCmd $ \conn ->
275 fromIntegral <$> runDelete conn nodeTable
276 (\(Node n_id _ _ _ _ _ _ _) -> n_id .== pgInt4 n)
278 deleteNodes :: [Int] -> Cmd Int
279 deleteNodes ns = mkCmd $ \conn ->
280 fromIntegral <$> runDelete conn nodeTable
281 (\(Node n_id _ _ _ _ _ _ _) -> in_ ((map pgInt4 ns)) n_id)
284 getNodesWith :: JSONB a => Connection -> Int -> proxy a -> Maybe NodeType
285 -> Maybe Offset -> Maybe Limit -> IO [Node a]
286 getNodesWith conn parentId _ nodeType maybeOffset maybeLimit =
287 runQuery conn $ selectNodesWith parentId nodeType maybeOffset maybeLimit
291 getNodesWithParentId :: Int
292 -> Maybe Text -> Connection -> IO [NodeAny]
293 getNodesWithParentId n _ conn = runQuery conn $ selectNodesWithParentID n
295 getNodesWithParentId' :: Int
296 -> Maybe Text -> Connection -> IO [NodeAny]
297 getNodesWithParentId' n _ conn = runQuery conn $ selectNodesWithParentID n
300 ------------------------------------------------------------------------
301 getDocumentsV3WithParentId :: Connection -> Int -> IO [Node HyperdataDocumentV3]
302 getDocumentsV3WithParentId conn n = runQuery conn $ selectNodesWith' n (Just NodeDocument)
304 getDocumentsWithParentId :: Connection -> Int -> IO [Node HyperdataDocument]
305 getDocumentsWithParentId conn n = runQuery conn $ selectNodesWith' n (Just NodeDocument)
307 getListsWithParentId :: Connection -> Int -> IO [Node HyperdataList]
308 getListsWithParentId conn n = runQuery conn $ selectNodesWith' n (Just NodeList)
310 getCorporaWithParentId :: Connection -> Int -> IO [Node HyperdataCorpus]
311 getCorporaWithParentId conn n = runQuery conn $ selectNodesWith' n (Just NodeCorpus)
313 getCorporaWithParentId' :: Int -> Cmd [Node HyperdataCorpus]
314 getCorporaWithParentId' n = mkCmd $ \conn -> runQuery conn $ selectNodesWith' n (Just NodeCorpus)
317 ------------------------------------------------------------------------
318 selectNodesWithParentID :: Int -> Query NodeRead
319 selectNodesWithParentID n = proc () -> do
320 row@(Node _ _ _ parent_id _ _ _ _) <- queryNodeTable -< ()
322 then parent_id .== (toNullable $ pgInt4 n)
323 else isNull parent_id
326 selectNodesWithType :: Column PGInt4 -> Query NodeRead
327 selectNodesWithType type_id = proc () -> do
328 row@(Node _ tn _ _ _ _ _ _) <- queryNodeTable -< ()
329 restrict -< tn .== type_id
332 type JSONB = QueryRunnerColumnDefault PGJsonb
334 getNode :: JSONB a => Connection -> Int -> proxy a -> IO (Node a)
335 getNode conn id _ = do
336 fromMaybe (error $ "Node does node exist: " <> show id) . headMay <$> runQuery conn (limit 1 $ selectNode (pgInt4 id))
338 getNodesWithType :: Connection -> Column PGInt4 -> IO [Node HyperdataDocument]
339 getNodesWithType conn type_id = do
340 runQuery conn $ selectNodesWithType type_id
342 ------------------------------------------------------------------------
344 ------------------------------------------------------------------------
345 defaultUser :: HyperdataUser
346 defaultUser = HyperdataUser (Just $ (pack . show) EN)
348 nodeUserW :: Maybe Name -> Maybe HyperdataUser -> UserId -> NodeWrite'
349 nodeUserW maybeName maybeHyperdata = node NodeUser name user Nothing
351 name = maybe "User" identity maybeName
352 user = maybe defaultUser identity maybeHyperdata
353 ------------------------------------------------------------------------
354 defaultFolder :: HyperdataFolder
355 defaultFolder = HyperdataFolder (Just "Markdown Description")
357 nodeFolderW :: Maybe Name -> Maybe HyperdataFolder -> ParentId -> UserId -> NodeWrite'
358 nodeFolderW maybeName maybeFolder pid = node NodeFolder name folder (Just pid)
360 name = maybe "Folder" identity maybeName
361 folder = maybe defaultFolder identity maybeFolder
362 ------------------------------------------------------------------------
363 nodeCorpusW :: Maybe Name -> Maybe HyperdataCorpus -> ParentId -> UserId -> NodeWrite'
364 nodeCorpusW maybeName maybeCorpus pId = node NodeCorpus name corpus (Just pId)
366 name = maybe "Corpus" identity maybeName
367 corpus = maybe defaultCorpus identity maybeCorpus
368 --------------------------
369 defaultDocument :: HyperdataDocument
370 defaultDocument = hyperdataDocument
372 nodeDocumentW :: Maybe Name -> Maybe HyperdataDocument -> CorpusId -> UserId -> NodeWrite'
373 nodeDocumentW maybeName maybeDocument cId = node NodeDocument name doc (Just cId)
375 name = maybe "Document" identity maybeName
376 doc = maybe defaultDocument identity maybeDocument
377 ------------------------------------------------------------------------
378 defaultAnnuaire :: HyperdataAnnuaire
379 defaultAnnuaire = HyperdataAnnuaire (Just "Title") (Just "Description")
381 nodeAnnuaireW :: Maybe Name -> Maybe HyperdataAnnuaire -> ParentId -> UserId -> NodeWrite'
382 nodeAnnuaireW maybeName maybeAnnuaire pId = node NodeAnnuaire name annuaire (Just pId)
384 name = maybe "Annuaire" identity maybeName
385 annuaire = maybe defaultAnnuaire identity maybeAnnuaire
386 --------------------------
388 ------------------------------------------------------------------------
389 arbitraryList :: HyperdataList
390 arbitraryList = HyperdataList (Just "Preferences")
392 nodeListW :: Maybe Name -> Maybe HyperdataList -> ParentId -> UserId -> NodeWrite'
393 nodeListW maybeName maybeList pId = node NodeList name list (Just pId)
395 name = maybe "Listes" identity maybeName
396 list = maybe arbitraryList identity maybeList
398 ------------------------------------------------------------------------
399 arbitraryGraph :: HyperdataGraph
400 arbitraryGraph = HyperdataGraph (Just "Preferences")
402 nodeGraphW :: Maybe Name -> Maybe HyperdataGraph -> ParentId -> UserId -> NodeWrite'
403 nodeGraphW maybeName maybeGraph pId = node NodeGraph name graph (Just pId)
405 name = maybe "Graph" identity maybeName
406 graph = maybe arbitraryGraph identity maybeGraph
408 ------------------------------------------------------------------------
410 arbitraryDashboard :: HyperdataDashboard
411 arbitraryDashboard = HyperdataDashboard (Just "Preferences")
413 nodeDashboardW :: Maybe Name -> Maybe HyperdataDashboard -> ParentId -> UserId -> NodeWrite'
414 nodeDashboardW maybeName maybeDashboard pId = node NodeDashboard name dashboard (Just pId)
416 name = maybe "Dashboard" identity maybeName
417 dashboard = maybe arbitraryDashboard identity maybeDashboard
421 ------------------------------------------------------------------------
422 node :: (ToJSON a, Hyperdata a) => NodeType -> Name -> a -> Maybe ParentId -> UserId -> NodeWrite'
423 node nodeType name hyperData parentId userId = Node Nothing typeId userId parentId name Nothing byteData Nothing
425 typeId = nodeTypeId nodeType
426 byteData = DB.pack . DBL.unpack $ encode hyperData
428 -------------------------------
429 node2row :: (Functor maybe1, Functor maybe2, Functor maybe3, Functor maybe4) =>
430 NodePoly (maybe1 Int) Int Int
431 (maybe2 Int) Text (maybe3 UTCTime)
432 ByteString (maybe4 TSVector)
433 -> ( maybe1 (Column PGInt4), Column PGInt4, Column PGInt4
434 , maybe2 (Column PGInt4), Column PGText, maybe3 (Column PGTimestamptz)
435 , Column PGJsonb, maybe4 (Column PGTSVector))
436 node2row (Node id tn ud pid nm dt hp tv) = ((pgInt4 <$> id)
445 ,(pgTSVector . unpack <$> tv)
447 ------------------------------------------------------------------------
448 insertNodesR' :: [NodeWrite'] -> Cmd [Int]
449 insertNodesR' ns = mkCmd $ \c -> insertNodesR ns c
451 insertNodes :: [NodeWrite'] -> Connection -> IO Int64
452 insertNodes ns conn = runInsertMany conn nodeTable' (map node2row ns)
454 insertNodesR :: [NodeWrite'] -> Connection -> IO [Int]
455 insertNodesR ns conn = runInsertManyReturning conn nodeTable' (map node2row ns) (\(i,_,_,_,_,_,_,_) -> i)
456 -------------------------
457 insertNodesWithParent :: Maybe ParentId -> [NodeWrite'] -> Connection -> IO Int64
458 insertNodesWithParent pid ns conn = insertNodes (map (set node_parentId pid) ns) conn
460 insertNodesWithParentR :: Maybe ParentId -> [NodeWrite'] -> Connection -> IO [Int]
461 insertNodesWithParentR pid ns conn = insertNodesR (map (set node_parentId pid) ns) conn
462 ------------------------------------------------------------------------
463 -- TODO Hierachy of Nodes
464 -- post and get same types Node' and update if changes
466 {- TODO semantic to achieve
467 post c uid pid [ Node' NodeCorpus "name" "{}" []
468 , Node' NodeFolder "name" "{}" [Node' NodeCorpus "test 2" "" [ Node' NodeDocument "title" "metaData" []
469 , Node' NodeDocument "title" "jsonData" []
474 ------------------------------------------------------------------------
477 -- currently this function remove the child relation
478 -- needs a Temporary type between Node' and NodeWriteT
479 node2table :: UserId -> Maybe ParentId -> Node' -> NodeWriteT
480 node2table uid pid (Node' nt txt v []) = ( Nothing, (pgInt4$ nodeTypeId nt), (pgInt4 uid), (fmap pgInt4 pid)
481 , pgStrictText txt, Nothing, pgStrictJSONB $ DB.pack $ DBL.unpack $ encode v, Nothing)
482 node2table _ _ (Node' _ _ _ _) = panic "node2table: should not happen, Tree insert not implemented yet"
485 data Node' = Node' { _n_type :: NodeType
488 , _n_children :: [Node']
492 type NodeWriteT = ( Maybe (Column PGInt4)
495 , Maybe (Column PGInt4)
497 , Maybe (Column PGTimestamptz)
499 , Maybe (Column PGTSVector)
503 mkNode' :: [NodeWriteT] -> Cmd Int64
504 mkNode' ns = mkCmd $ \conn -> runInsertMany conn nodeTable' ns
506 mkNodeR' :: [NodeWriteT] -> Cmd [Int]
507 mkNodeR' ns = mkCmd $ \conn -> runInsertManyReturning conn nodeTable' ns (\(i,_,_,_,_,_,_,_) -> i)
509 ------------------------------------------------------------------------
511 data NewNode = NewNode { _newNodeId :: Int
512 , _newNodeChildren :: [Int] }
515 postNode :: UserId -> Maybe ParentId -> Node' -> Cmd NewNode
516 postNode uid pid (Node' nt txt v []) = do
517 pids <- mkNodeR' [node2table uid pid (Node' nt txt v [])]
519 [pid] -> pure $ NewNode pid []
520 _ -> panic "postNode: only one pid expected"
522 postNode uid pid (Node' NodeCorpus txt v ns) = do
523 NewNode pid' _ <- postNode uid pid (Node' NodeCorpus txt v [])
524 pids <- mkNodeR' (concat $ map (\n -> [childWith uid pid' n]) ns)
525 pure $ NewNode pid' pids
527 postNode uid pid (Node' NodeAnnuaire txt v ns) = do
528 NewNode pid' _ <- postNode uid pid (Node' NodeAnnuaire txt v [])
529 pids <- mkNodeR' (concat $ map (\n -> [childWith uid pid' n]) ns)
530 pure $ NewNode pid' pids
531 postNode _ _ (Node' _ _ _ _) = panic "TODO: postNode for this type not implemented yet"
534 childWith :: UserId -> ParentId -> Node' -> NodeWriteT
535 childWith uId pId (Node' NodeDocument txt v []) = node2table uId (Just pId) (Node' NodeDocument txt v [])
536 childWith uId pId (Node' NodeContact txt v []) = node2table uId (Just pId) (Node' NodeContact txt v [])
537 childWith _ _ (Node' _ _ _ _) = panic "This NodeType can not be a child"
540 -- TODO: remove hardcoded userId (with Reader)
541 -- TODO: user Reader in the API and adapt this function
545 mk :: Connection -> NodeType -> Maybe ParentId -> Text -> IO [Int]
546 mk c nt pId name = mk' c nt userId pId name
548 mk' :: Connection -> NodeType -> UserId -> Maybe ParentId -> Text -> IO [Int]
549 mk' c nt uId pId name = map fromIntegral <$> insertNodesWithParentR pId [node nt name hd pId uId] c
551 hd = HyperdataUser . Just . pack $ show EN
555 mk'' :: NodeType -> Maybe ParentId -> UserId -> Name -> Cmd [Int]
556 mk'' NodeUser Nothing uId name = mkCmd $ \c -> mk' c NodeUser uId Nothing name
557 mk'' NodeUser _ _ _ = panic "NodeUser do not have any parent"
558 mk'' _ Nothing _ _ = panic "NodeType does have a parent"
559 mk'' nt pId uId name = mkCmd $ \c -> mk' c nt uId pId name
562 mkRoot :: Username -> UserId -> Cmd [Int]
563 mkRoot uname uId = case uId > 0 of
564 False -> panic "UserId <= 0"
565 True -> mk'' NodeUser Nothing uId uname
567 mkCorpus :: Maybe Name -> Maybe HyperdataCorpus -> ParentId -> UserId -> Cmd [Int]
568 mkCorpus n h p u = insertNodesR' [nodeCorpusW n h p u]
570 mkList :: ParentId -> UserId -> Cmd [Int]
571 mkList p u = insertNodesR' [nodeListW Nothing Nothing p u]
573 mkGraph :: ParentId -> UserId -> Cmd [Int]
574 mkGraph p u = insertNodesR' [nodeGraphW Nothing Nothing p u]
576 mkDashboard :: ParentId -> UserId -> Cmd [Int]
577 mkDashboard p u = insertNodesR' [nodeDashboardW Nothing Nothing p u]
579 mkAnnuaire :: ParentId -> UserId -> Cmd [Int]
580 mkAnnuaire p u = insertNodesR' [nodeAnnuaireW Nothing Nothing p u]
582 -- | Default CorpusId Master and ListId Master