2 Module : Gargantext.Database.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 GeneralizedNewtypeDeriving #-}
21 {-# LANGUAGE OverloadedStrings #-}
22 {-# LANGUAGE MultiParamTypeClasses #-}
23 {-# LANGUAGE NoImplicitPrelude #-}
24 {-# LANGUAGE TemplateHaskell #-}
26 module Gargantext.Database.Node where
28 import Control.Applicative (Applicative)
29 import Control.Arrow (returnA)
30 import Control.Lens (set)
31 import Control.Lens.TH (makeLensesWith, abbreviatedFields)
32 import Control.Monad.IO.Class
33 import Control.Monad.Reader
35 import Data.ByteString (ByteString)
36 import Data.Maybe (Maybe(..), fromMaybe)
37 import Data.Profunctor.Product.TH (makeAdaptorAndInstance)
38 import Data.Text (Text, unpack, pack)
39 import Data.Time (UTCTime)
40 import Database.PostgreSQL.Simple (Connection)
41 import Database.PostgreSQL.Simple.FromField (FromField, fromField)
42 import GHC.Int (Int64)
43 import Gargantext.Core (Lang(..))
44 import Gargantext.Core.Types
45 import Gargantext.Core.Types.Individu (Username)
46 import Gargantext.Core.Types.Main (UserId)
47 import Gargantext.Database.Config (nodeTypeId)
48 import Gargantext.Database.Queries
49 import Gargantext.Database.Types.Node (NodeType, defaultCorpus, Hyperdata)
50 import Gargantext.Database.Utils (fromField')
51 import Gargantext.Prelude hiding (sum, head)
52 import Opaleye hiding (FromField)
53 import Opaleye.Internal.QueryArr (Query)
54 import Prelude hiding (null, id, map, sum)
55 import qualified Data.ByteString as DB
56 import qualified Data.ByteString.Lazy as DBL
57 import qualified Data.Profunctor.Product as PP
59 ------------------------------------------------------------------------
60 ------------------------------------------------------------------------
61 {- | Reader Monad reinvented here:
63 newtype Cmd a = Cmd { unCmd :: Connection -> IO a }
65 instance Monad Cmd where
66 return a = Cmd $ \_ -> return a
68 m >>= f = Cmd $ \c -> do
72 newtype Cmd a = Cmd (ReaderT Connection IO a)
73 deriving (Functor, Applicative, Monad, MonadReader Connection, MonadIO)
75 runCmd :: Connection -> Cmd a -> IO a
76 runCmd c (Cmd f) = runReaderT f c
78 mkCmd :: (Connection -> IO a) -> Cmd a
81 ------------------------------------------------------------------------
82 ------------------------------------------------------------------------
83 instance FromField HyperdataAny
85 fromField = fromField'
87 instance FromField HyperdataCorpus
89 fromField = fromField'
91 instance FromField HyperdataDocument
93 fromField = fromField'
95 instance FromField HyperdataDocumentV3
97 fromField = fromField'
99 instance FromField HyperdataUser
101 fromField = fromField'
103 instance FromField HyperdataList
105 fromField = fromField'
107 instance FromField HyperdataAnnuaire
109 fromField = fromField'
110 ------------------------------------------------------------------------
111 instance QueryRunnerColumnDefault PGJsonb HyperdataAny
113 queryRunnerColumnDefault = fieldQueryRunnerColumn
115 instance QueryRunnerColumnDefault PGJsonb HyperdataDocument
117 queryRunnerColumnDefault = fieldQueryRunnerColumn
119 instance QueryRunnerColumnDefault PGJsonb HyperdataDocumentV3
121 queryRunnerColumnDefault = fieldQueryRunnerColumn
123 instance QueryRunnerColumnDefault PGJsonb HyperdataCorpus
125 queryRunnerColumnDefault = fieldQueryRunnerColumn
127 instance QueryRunnerColumnDefault PGJsonb HyperdataUser
129 queryRunnerColumnDefault = fieldQueryRunnerColumn
131 instance QueryRunnerColumnDefault PGJsonb HyperdataList
133 queryRunnerColumnDefault = fieldQueryRunnerColumn
135 instance QueryRunnerColumnDefault PGJsonb HyperdataAnnuaire
137 queryRunnerColumnDefault = fieldQueryRunnerColumn
139 instance QueryRunnerColumnDefault PGTSVector (Maybe TSVector)
141 queryRunnerColumnDefault = fieldQueryRunnerColumn
144 ------------------------------------------------------------------------
146 $(makeAdaptorAndInstance "pNode" ''NodePoly)
147 $(makeLensesWith abbreviatedFields ''NodePoly)
150 nodeTable :: Table NodeWrite NodeRead
151 nodeTable = Table "nodes" (pNode Node { _node_id = optional "id"
152 , _node_typename = required "typename"
153 , _node_userId = required "user_id"
155 , _node_parentId = required "parent_id"
156 , _node_name = required "name"
157 , _node_date = optional "date"
159 , _node_hyperdata = required "hyperdata"
160 , _node_search = optional "search"
165 nodeTable' :: Table (Maybe (Column PGInt4)
168 ,Maybe (Column PGInt4)
170 ,Maybe (Column PGTimestamptz)
172 ,Maybe (Column PGTSVector)
179 ,(Column PGTimestamptz)
184 nodeTable' = Table "nodes" (PP.p8 ( optional "id"
185 , required "typename"
188 , optional "parent_id"
192 , required "hyperdata"
198 queryNodeTable :: Query NodeRead
199 queryNodeTable = queryTable nodeTable
201 selectNode :: Column PGInt4 -> Query NodeRead
202 selectNode id = proc () -> do
203 row <- queryNodeTable -< ()
204 restrict -< _node_id row .== id
208 runGetNodes :: Query NodeRead -> Cmd [NodeAny]
209 runGetNodes q = mkCmd $ \conn -> runQuery conn q
211 ------------------------------------------------------------------------
212 ------------------------------------------------------------------------
214 -- | order by publication date
215 -- Favorites (Bool), node_ngrams
216 selectNodesWith :: ParentId -> Maybe NodeType
217 -> Maybe Offset -> Maybe Limit -> Query NodeRead
218 selectNodesWith parentId maybeNodeType maybeOffset maybeLimit =
219 --offset' maybeOffset $ limit' maybeLimit $ orderBy (asc (hyperdataDocument_Publication_date . node_hyperdata)) $ selectNodesWith' parentId typeId
220 limit' maybeLimit $ offset' maybeOffset
221 $ orderBy (asc _node_id)
222 $ selectNodesWith' parentId maybeNodeType
224 selectNodesWith' :: ParentId -> Maybe NodeType -> Query NodeRead
225 selectNodesWith' parentId maybeNodeType = proc () -> do
226 node <- (proc () -> do
227 row@(Node _ typeId _ parentId' _ _ _ _) <- queryNodeTable -< ()
228 restrict -< parentId' .== (toNullable $ pgInt4 parentId)
230 let typeId' = maybe 0 nodeTypeId maybeNodeType
232 restrict -< if typeId' > 0
233 then typeId .== (pgInt4 (typeId' :: Int))
235 returnA -< row ) -< ()
239 --type Cmd' a = forall m. (MonadReader env m, HasConnection env, MonadIO m) => m a
242 -- deleteNode :: (MonadReader Connection m, MonadIO m) => Int -> m Int
243 -- deleteNode :: Int -> Cmd' Int
245 deleteNode :: Int -> Cmd Int
246 deleteNode n = mkCmd $ \conn ->
247 fromIntegral <$> runDelete conn nodeTable
248 (\(Node n_id _ _ _ _ _ _ _) -> n_id .== pgInt4 n)
250 deleteNodes :: [Int] -> Cmd Int
251 deleteNodes ns = mkCmd $ \conn ->
252 fromIntegral <$> runDelete conn nodeTable
253 (\(Node n_id _ _ _ _ _ _ _) -> in_ ((map pgInt4 ns)) n_id)
256 getNodesWith :: JSONB a => Connection -> Int -> proxy a -> Maybe NodeType
257 -> Maybe Offset -> Maybe Limit -> IO [Node a]
258 getNodesWith conn parentId _ nodeType maybeOffset maybeLimit =
259 runQuery conn $ selectNodesWith parentId nodeType maybeOffset maybeLimit
263 getNodesWithParentId :: Int
264 -> Maybe Text -> Connection -> IO [NodeAny]
265 getNodesWithParentId n _ conn = runQuery conn $ selectNodesWithParentID n
267 getNodesWithParentId' :: Int
268 -> Maybe Text -> Connection -> IO [NodeAny]
269 getNodesWithParentId' n _ conn = runQuery conn $ selectNodesWithParentID n
272 ------------------------------------------------------------------------
273 getDocumentsV3WithParentId :: Connection -> Int -> IO [Node HyperdataDocumentV3]
274 getDocumentsV3WithParentId conn n = runQuery conn $ selectNodesWith' n (Just NodeDocument)
276 getDocumentsWithParentId :: Connection -> Int -> IO [Node HyperdataDocument]
277 getDocumentsWithParentId conn n = runQuery conn $ selectNodesWith' n (Just NodeDocument)
279 getListsWithParentId :: Connection -> Int -> IO [Node HyperdataList]
280 getListsWithParentId conn n = runQuery conn $ selectNodesWith' n (Just NodeList)
282 getCorporaWithParentId :: Connection -> Int -> IO [Node HyperdataCorpus]
283 getCorporaWithParentId conn n = runQuery conn $ selectNodesWith' n (Just NodeCorpus)
285 getCorporaWithParentId' :: Int -> Cmd [Node HyperdataCorpus]
286 getCorporaWithParentId' n = mkCmd $ \conn -> runQuery conn $ selectNodesWith' n (Just NodeCorpus)
289 ------------------------------------------------------------------------
290 selectNodesWithParentID :: Int -> Query NodeRead
291 selectNodesWithParentID n = proc () -> do
292 row@(Node _ _ _ parent_id _ _ _ _) <- queryNodeTable -< ()
294 then parent_id .== (toNullable $ pgInt4 n)
295 else isNull parent_id
298 selectNodesWithType :: Column PGInt4 -> Query NodeRead
299 selectNodesWithType type_id = proc () -> do
300 row@(Node _ tn _ _ _ _ _ _) <- queryNodeTable -< ()
301 restrict -< tn .== type_id
304 type JSONB = QueryRunnerColumnDefault PGJsonb
306 getNode :: JSONB a => Connection -> Int -> proxy a -> IO (Node a)
307 getNode conn id _ = do
308 fromMaybe (error $ "Node does node exist: " <> show id) . headMay <$> runQuery conn (limit 1 $ selectNode (pgInt4 id))
310 getNodesWithType :: Connection -> Column PGInt4 -> IO [Node HyperdataDocument]
311 getNodesWithType conn type_id = do
312 runQuery conn $ selectNodesWithType type_id
314 ------------------------------------------------------------------------
316 -- TODO Classe HasDefault where
317 -- default NodeType = Hyperdata
318 ------------------------------------------------------------------------
319 type NodeWrite' = NodePoly (Maybe Int) Int Int (Maybe ParentId) Text (Maybe UTCTime) ByteString (Maybe TSVector)
320 ------------------------------------------------------------------------
321 defaultUser :: HyperdataUser
322 defaultUser = HyperdataUser (Just $ (pack . show) EN)
324 nodeUserW :: Maybe Name -> Maybe HyperdataUser -> UserId -> NodeWrite'
325 nodeUserW maybeName maybeHyperdata = node NodeUser name user Nothing
327 name = maybe "User" identity maybeName
328 user = maybe defaultUser identity maybeHyperdata
329 ------------------------------------------------------------------------
330 defaultFolder :: HyperdataFolder
331 defaultFolder = HyperdataFolder (Just "Markdown Description")
333 nodeFolderW :: Maybe Name -> Maybe HyperdataFolder -> ParentId -> UserId -> NodeWrite'
334 nodeFolderW maybeName maybeFolder pid = node NodeFolder name folder (Just pid)
336 name = maybe "Folder" identity maybeName
337 folder = maybe defaultFolder identity maybeFolder
338 ------------------------------------------------------------------------
339 nodeCorpusW :: Maybe Name -> Maybe HyperdataCorpus -> ParentId -> UserId -> NodeWrite'
340 nodeCorpusW maybeName maybeCorpus pId = node NodeCorpus name corpus (Just pId)
342 name = maybe "Corpus" identity maybeName
343 corpus = maybe defaultCorpus identity maybeCorpus
344 --------------------------
345 defaultDocument :: HyperdataDocument
346 defaultDocument = hyperdataDocument
348 nodeDocumentW :: Maybe Name -> Maybe HyperdataDocument -> CorpusId -> UserId -> NodeWrite'
349 nodeDocumentW maybeName maybeDocument cId = node NodeDocument name doc (Just cId)
351 name = maybe "Document" identity maybeName
352 doc = maybe defaultDocument identity maybeDocument
353 ------------------------------------------------------------------------
354 defaultAnnuaire :: HyperdataAnnuaire
355 defaultAnnuaire = HyperdataAnnuaire (Just "Title") (Just "Description")
357 nodeAnnuaireW :: Maybe Name -> Maybe HyperdataAnnuaire -> ParentId -> UserId -> NodeWrite'
358 nodeAnnuaireW maybeName maybeAnnuaire pId = node NodeAnnuaire name annuaire (Just pId)
360 name = maybe "Annuaire" identity maybeName
361 annuaire = maybe defaultAnnuaire identity maybeAnnuaire
362 --------------------------
364 ------------------------------------------------------------------------
365 arbitraryList :: HyperdataList
366 arbitraryList = HyperdataList (Just "Preferences")
368 nodeListW :: Maybe Name -> Maybe HyperdataList -> ParentId -> UserId -> NodeWrite'
369 nodeListW maybeName maybeList pId = node NodeList name list (Just pId)
371 name = maybe "Listes" identity maybeName
372 list = maybe arbitraryList identity maybeList
374 ------------------------------------------------------------------------
375 arbitraryGraph :: HyperdataGraph
376 arbitraryGraph = HyperdataGraph (Just "Preferences")
378 nodeGraphW :: Maybe Name -> Maybe HyperdataGraph -> ParentId -> UserId -> NodeWrite'
379 nodeGraphW maybeName maybeGraph pId = node NodeGraph name graph (Just pId)
381 name = maybe "Graph" identity maybeName
382 graph = maybe arbitraryGraph identity maybeGraph
384 ------------------------------------------------------------------------
386 arbitraryDashboard :: HyperdataDashboard
387 arbitraryDashboard = HyperdataDashboard (Just "Preferences")
389 nodeDashboardW :: Maybe Name -> Maybe HyperdataDashboard -> ParentId -> UserId -> NodeWrite'
390 nodeDashboardW maybeName maybeDashboard pId = node NodeDashboard name dashboard (Just pId)
392 name = maybe "Dashboard" identity maybeName
393 dashboard = maybe arbitraryDashboard identity maybeDashboard
397 ------------------------------------------------------------------------
398 node :: (ToJSON a, Hyperdata a) => NodeType -> Name -> a -> Maybe ParentId -> UserId -> NodeWrite'
399 node nodeType name hyperData parentId userId = Node Nothing typeId userId parentId name Nothing byteData Nothing
401 typeId = nodeTypeId nodeType
402 byteData = DB.pack . DBL.unpack $ encode hyperData
404 -------------------------------
405 node2row :: (Functor maybe1, Functor maybe2, Functor maybe3, Functor maybe4) =>
406 NodePoly (maybe1 Int) Int Int
407 (maybe2 Int) Text (maybe3 UTCTime)
408 ByteString (maybe4 TSVector)
409 -> ( maybe1 (Column PGInt4), Column PGInt4, Column PGInt4
410 , maybe2 (Column PGInt4), Column PGText, maybe3 (Column PGTimestamptz)
411 , Column PGJsonb, maybe4 (Column PGTSVector))
412 node2row (Node id tn ud pid nm dt hp tv) = ((pgInt4 <$> id)
421 ,(pgTSVector . unpack <$> tv)
423 ------------------------------------------------------------------------
424 insertNodesR' :: [NodeWrite'] -> Cmd [Int]
425 insertNodesR' ns = mkCmd $ \c -> insertNodesR ns c
427 insertNodes :: [NodeWrite'] -> Connection -> IO Int64
428 insertNodes ns conn = runInsertMany conn nodeTable' (map node2row ns)
430 insertNodesR :: [NodeWrite'] -> Connection -> IO [Int]
431 insertNodesR ns conn = runInsertManyReturning conn nodeTable' (map node2row ns) (\(i,_,_,_,_,_,_,_) -> i)
432 -------------------------
433 insertNodesWithParent :: Maybe ParentId -> [NodeWrite'] -> Connection -> IO Int64
434 insertNodesWithParent pid ns conn = insertNodes (map (set node_parentId pid) ns) conn
436 insertNodesWithParentR :: Maybe ParentId -> [NodeWrite'] -> Connection -> IO [Int]
437 insertNodesWithParentR pid ns conn = insertNodesR (map (set node_parentId pid) ns) conn
438 ------------------------------------------------------------------------
439 -- TODO Hierachy of Nodes
440 -- post and get same types Node' and update if changes
442 {- TODO semantic to achieve
443 post c uid pid [ Node' NodeCorpus "name" "{}" []
444 , Node' NodeFolder "name" "{}" [Node' NodeCorpus "test 2" "" [ Node' NodeDocument "title" "metaData" []
445 , Node' NodeDocument "title" "jsonData" []
450 ------------------------------------------------------------------------
453 -- currently this function remove the child relation
454 -- needs a Temporary type between Node' and NodeWriteT
455 node2table :: UserId -> Maybe ParentId -> Node' -> NodeWriteT
456 node2table uid pid (Node' nt txt v []) = ( Nothing, (pgInt4$ nodeTypeId nt), (pgInt4 uid), (fmap pgInt4 pid)
457 , pgStrictText txt, Nothing, pgStrictJSONB $ DB.pack $ DBL.unpack $ encode v, Nothing)
458 node2table _ _ (Node' _ _ _ _) = panic "node2table: should not happen, Tree insert not implemented yet"
461 data Node' = Node' { _n_type :: NodeType
464 , _n_children :: [Node']
468 type NodeWriteT = ( Maybe (Column PGInt4)
471 , Maybe (Column PGInt4)
473 , Maybe (Column PGTimestamptz)
475 , Maybe (Column PGTSVector)
479 mkNode' :: [NodeWriteT] -> Cmd Int64
480 mkNode' ns = mkCmd $ \conn -> runInsertMany conn nodeTable' ns
482 mkNodeR' :: [NodeWriteT] -> Cmd [Int]
483 mkNodeR' ns = mkCmd $ \conn -> runInsertManyReturning conn nodeTable' ns (\(i,_,_,_,_,_,_,_) -> i)
485 ------------------------------------------------------------------------
487 data NewNode = NewNode { _newNodeId :: Int
488 , _newNodeChildren :: [Int] }
491 postNode :: UserId -> Maybe ParentId -> Node' -> Cmd NewNode
492 postNode uid pid (Node' nt txt v []) = do
493 pids <- mkNodeR' [node2table uid pid (Node' nt txt v [])]
495 [pid] -> pure $ NewNode pid []
496 _ -> panic "postNode: only one pid expected"
498 postNode uid pid (Node' NodeCorpus txt v ns) = do
499 NewNode pid' _ <- postNode uid pid (Node' NodeCorpus txt v [])
500 pids <- mkNodeR' (concat $ map (\n -> [childWith uid pid' n]) ns)
501 pure $ NewNode pid' pids
503 postNode uid pid (Node' NodeAnnuaire txt v ns) = do
504 NewNode pid' _ <- postNode uid pid (Node' NodeAnnuaire txt v [])
505 pids <- mkNodeR' (concat $ map (\n -> [childWith uid pid' n]) ns)
506 pure $ NewNode pid' pids
507 postNode _ _ (Node' _ _ _ _) = panic "TODO: postNode for this type not implemented yet"
510 childWith :: UserId -> ParentId -> Node' -> NodeWriteT
511 childWith uId pId (Node' NodeDocument txt v []) = node2table uId (Just pId) (Node' NodeDocument txt v [])
512 childWith uId pId (Node' NodeContact txt v []) = node2table uId (Just pId) (Node' NodeContact txt v [])
513 childWith _ _ (Node' _ _ _ _) = panic "This NodeType can not be a child"
516 -- TODO: remove hardcoded userId (with Reader)
517 -- TODO: user Reader in the API and adapt this function
521 mk :: Connection -> NodeType -> Maybe ParentId -> Text -> IO [Int]
522 mk c nt pId name = mk' c nt userId pId name
524 mk' :: Connection -> NodeType -> UserId -> Maybe ParentId -> Text -> IO [Int]
525 mk' c nt uId pId name = map fromIntegral <$> insertNodesWithParentR pId [node nt name hd pId uId] c
527 hd = HyperdataUser . Just . pack $ show EN
531 mk'' :: NodeType -> Maybe ParentId -> UserId -> Name -> Cmd [Int]
532 mk'' NodeUser Nothing uId name = mkCmd $ \c -> mk' c NodeUser uId Nothing name
533 mk'' NodeUser _ _ _ = panic "NodeUser do not have any parent"
534 mk'' _ Nothing _ _ = panic "NodeType does have a parent"
535 mk'' nt pId uId name = mkCmd $ \c -> mk' c nt uId pId name
538 mkRoot :: Username -> UserId -> Cmd [Int]
539 mkRoot uname uId = case uId > 0 of
540 False -> panic "UserId <= 0"
541 True -> mk'' NodeUser Nothing uId uname
543 mkCorpus :: Maybe Name -> Maybe HyperdataCorpus -> ParentId -> UserId -> Cmd [Int]
544 mkCorpus n h p u = insertNodesR' [nodeCorpusW n h p u]
546 mkList :: ParentId -> UserId -> Cmd [Int]
547 mkList p u = insertNodesR' [nodeListW Nothing Nothing p u]
549 mkGraph :: ParentId -> UserId -> Cmd [Int]
550 mkGraph p u = insertNodesR' [nodeGraphW Nothing Nothing p u]
552 mkDashboard :: ParentId -> UserId -> Cmd [Int]
553 mkDashboard p u = insertNodesR' [nodeDashboardW Nothing Nothing p u]
555 mkAnnuaire :: ParentId -> UserId -> Cmd [Int]
556 mkAnnuaire p u = insertNodesR' [nodeAnnuaireW Nothing Nothing p u]
558 -- | Default CorpusId Master and ListId Master