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 Data.Text (pack)
29 import GHC.Int (Int64)
30 import Control.Lens (set)
32 import Data.Time (UTCTime)
33 import Database.PostgreSQL.Simple.FromField (FromField, fromField)
34 import Prelude hiding (null, id, map, sum)
36 import Gargantext.Core (Lang(..))
37 import Gargantext.Core.Types
38 import Gargantext.Database.Utils (fromField')
39 import Gargantext.Database.Types.Node (NodeType, defaultCorpus, Hyperdata)
40 import Gargantext.Database.Queries
41 import Gargantext.Database.Config (nodeTypeId)
42 import Gargantext.Prelude hiding (sum)
44 import Control.Applicative (Applicative)
45 import Control.Arrow (returnA)
46 import Control.Lens.TH (makeLensesWith, abbreviatedFields)
47 import Control.Monad.IO.Class
48 import Control.Monad.Reader
50 import Data.Maybe (Maybe, fromMaybe)
51 import Data.Text (Text)
52 import Data.Profunctor.Product.TH (makeAdaptorAndInstance)
54 import qualified Data.ByteString as DB
55 import qualified Data.ByteString.Lazy as DBL
56 import Data.ByteString (ByteString)
58 import Database.PostgreSQL.Simple (Connection)
59 import Opaleye hiding (FromField)
60 import Opaleye.Internal.QueryArr (Query)
61 import qualified Data.Profunctor.Product as PP
63 ------------------------------------------------------------------------
64 ------------------------------------------------------------------------
65 {- | Reader Monad reinvented here:
67 newtype Cmd a = Cmd { unCmd :: Connection -> IO a }
69 instance Monad Cmd where
70 return a = Cmd $ \_ -> return a
72 m >>= f = Cmd $ \c -> do
76 newtype Cmd a = Cmd (ReaderT Connection IO a)
77 deriving (Functor, Applicative, Monad, MonadReader Connection, MonadIO)
79 runCmd :: Connection -> Cmd a -> IO a
80 runCmd c (Cmd f) = runReaderT f c
82 mkCmd :: (Connection -> IO a) -> Cmd a
85 ------------------------------------------------------------------------
92 ------------------------------------------------------------------------
93 instance FromField HyperdataAny
95 fromField = fromField'
97 instance FromField HyperdataCorpus
99 fromField = fromField'
101 instance FromField HyperdataDocument
103 fromField = fromField'
105 instance FromField HyperdataDocumentV3
107 fromField = fromField'
109 instance FromField HyperdataUser
111 fromField = fromField'
113 instance FromField HyperdataList
115 fromField = fromField'
117 instance FromField HyperdataAnnuaire
119 fromField = fromField'
120 ------------------------------------------------------------------------
121 instance QueryRunnerColumnDefault PGJsonb HyperdataAny
123 queryRunnerColumnDefault = fieldQueryRunnerColumn
125 instance QueryRunnerColumnDefault PGJsonb HyperdataDocument
127 queryRunnerColumnDefault = fieldQueryRunnerColumn
129 instance QueryRunnerColumnDefault PGJsonb HyperdataDocumentV3
131 queryRunnerColumnDefault = fieldQueryRunnerColumn
133 instance QueryRunnerColumnDefault PGJsonb HyperdataCorpus
135 queryRunnerColumnDefault = fieldQueryRunnerColumn
137 instance QueryRunnerColumnDefault PGJsonb HyperdataUser
139 queryRunnerColumnDefault = fieldQueryRunnerColumn
141 instance QueryRunnerColumnDefault PGJsonb HyperdataList
143 queryRunnerColumnDefault = fieldQueryRunnerColumn
145 instance QueryRunnerColumnDefault PGJsonb HyperdataAnnuaire
147 queryRunnerColumnDefault = fieldQueryRunnerColumn
148 ------------------------------------------------------------------------
150 $(makeAdaptorAndInstance "pNode" ''NodePoly)
151 $(makeLensesWith abbreviatedFields ''NodePoly)
154 nodeTable :: Table NodeWrite NodeRead
155 nodeTable = Table "nodes" (pNode Node { _node_id = optional "id"
156 , _node_typename = required "typename"
157 , _node_userId = required "user_id"
158 , _node_parentId = required "parent_id"
159 , _node_name = required "name"
160 , _node_date = optional "date"
161 , _node_hyperdata = required "hyperdata"
162 -- , node_titleAbstract = optional "title_abstract"
167 nodeTable' :: Table (Maybe (Column PGInt4)
170 ,Maybe (Column PGInt4)
172 ,Maybe (Column PGTimestamptz)
180 ,(Column PGTimestamptz)
184 nodeTable' = Table "nodes" (PP.p7 ( optional "id"
185 , required "typename"
187 , optional "parent_id"
190 , required "hyperdata"
195 queryNodeTable :: Query NodeRead
196 queryNodeTable = queryTable nodeTable
198 selectNode :: Column PGInt4 -> Query NodeRead
199 selectNode id = proc () -> do
200 row <- queryNodeTable -< ()
201 restrict -< _node_id row .== id
204 runGetNodes :: Query NodeRead -> Cmd [NodeAny]
205 runGetNodes q = mkCmd $ \conn -> runQuery conn q
207 ------------------------------------------------------------------------
208 selectRootUsername :: Username -> Query NodeRead
209 selectRootUsername username = proc () -> do
210 row <- queryNodeTable -< ()
211 restrict -< _node_typename row .== (pgInt4 $ nodeTypeId NodeUser)
212 restrict -< _node_name row .== (pgStrictText username)
215 getRootUsername :: Username -> Connection -> IO [Node HyperdataUser]
216 getRootUsername uname conn = runQuery conn (selectRootUsername uname)
218 ------------------------------------------------------------------------
219 selectRootUser :: UserId -> Query NodeRead
220 selectRootUser userId = proc () -> do
221 row <- queryNodeTable -< ()
222 restrict -< _node_userId row .== (pgInt4 userId)
223 restrict -< _node_typename row .== (pgInt4 $ nodeTypeId NodeUser)
226 getRoot :: UserId -> Cmd [Node HyperdataUser]
227 getRoot userId = mkCmd $ \conn -> runQuery conn (selectRootUser userId)
228 ------------------------------------------------------------------------
230 -- | order by publication date
231 -- Favorites (Bool), node_ngrams
232 selectNodesWith :: ParentId -> Maybe NodeType
233 -> Maybe Offset -> Maybe Limit -> Query NodeRead
234 selectNodesWith parentId maybeNodeType maybeOffset maybeLimit =
235 --offset' maybeOffset $ limit' maybeLimit $ orderBy (asc (hyperdataDocument_Publication_date . node_hyperdata)) $ selectNodesWith' parentId typeId
236 limit' maybeLimit $ offset' maybeOffset
237 $ orderBy (asc _node_id)
238 $ selectNodesWith' parentId maybeNodeType
240 selectNodesWith' :: ParentId -> Maybe NodeType -> Query NodeRead
241 selectNodesWith' parentId maybeNodeType = proc () -> do
242 node <- (proc () -> do
243 row@(Node _ typeId _ parentId' _ _ _) <- queryNodeTable -< ()
244 restrict -< parentId' .== (toNullable $ pgInt4 parentId)
246 let typeId' = maybe 0 nodeTypeId maybeNodeType
248 restrict -< if typeId' > 0
249 then typeId .== (pgInt4 (typeId' :: Int))
251 returnA -< row ) -< ()
255 --type Cmd' a = forall m. (MonadReader env m, HasConnection env, MonadIO m) => m a
258 -- deleteNode :: (MonadReader Connection m, MonadIO m) => Int -> m Int
259 -- deleteNode :: Int -> Cmd' Int
261 deleteNode :: Int -> Cmd Int
262 deleteNode n = mkCmd $ \conn ->
263 fromIntegral <$> runDelete conn nodeTable
264 (\(Node n_id _ _ _ _ _ _) -> n_id .== pgInt4 n)
266 deleteNodes :: [Int] -> Cmd Int
267 deleteNodes ns = mkCmd $ \conn ->
268 fromIntegral <$> runDelete conn nodeTable
269 (\(Node n_id _ _ _ _ _ _) -> in_ ((map pgInt4 ns)) n_id)
272 getNodesWith :: JSONB a => Connection -> Int -> proxy a -> Maybe NodeType
273 -> Maybe Offset -> Maybe Limit -> IO [Node a]
274 getNodesWith conn parentId _ nodeType maybeOffset maybeLimit =
275 runQuery conn $ selectNodesWith parentId nodeType maybeOffset maybeLimit
279 getNodesWithParentId :: Int
280 -> Maybe Text -> Connection -> IO [NodeAny]
281 getNodesWithParentId n _ conn = runQuery conn $ selectNodesWithParentID n
283 getNodesWithParentId' :: Int
284 -> Maybe Text -> Connection -> IO [NodeAny]
285 getNodesWithParentId' n _ conn = runQuery conn $ selectNodesWithParentID n
288 ------------------------------------------------------------------------
289 getDocumentsV3WithParentId :: Connection -> Int -> IO [Node HyperdataDocumentV3]
290 getDocumentsV3WithParentId conn n = runQuery conn $ selectNodesWith' n (Just NodeDocument)
292 getDocumentsWithParentId :: Connection -> Int -> IO [Node HyperdataDocument]
293 getDocumentsWithParentId conn n = runQuery conn $ selectNodesWith' n (Just NodeDocument)
295 getListsWithParentId :: Connection -> Int -> IO [Node HyperdataList]
296 getListsWithParentId conn n = runQuery conn $ selectNodesWith' n (Just NodeList)
298 ------------------------------------------------------------------------
299 selectNodesWithParentID :: Int -> Query NodeRead
300 selectNodesWithParentID n = proc () -> do
301 row@(Node _ _ _ parent_id _ _ _) <- queryNodeTable -< ()
303 then parent_id .== (toNullable $ pgInt4 n)
304 else isNull parent_id
307 selectNodesWithType :: Column PGInt4 -> Query NodeRead
308 selectNodesWithType type_id = proc () -> do
309 row@(Node _ tn _ _ _ _ _) <- queryNodeTable -< ()
310 restrict -< tn .== type_id
313 type JSONB = QueryRunnerColumnDefault PGJsonb
315 getNode :: JSONB a => Connection -> Int -> proxy a -> IO (Node a)
316 getNode conn id _ = do
317 fromMaybe (error $ "Node does node exist: " <> show id) . headMay <$> runQuery conn (limit 1 $ selectNode (pgInt4 id))
319 getNodesWithType :: Connection -> Column PGInt4 -> IO [Node HyperdataDocument]
320 getNodesWithType conn type_id = do
321 runQuery conn $ selectNodesWithType type_id
323 ------------------------------------------------------------------------
325 -- TODO Classe HasDefault where
326 -- default NodeType = Hyperdata
327 ------------------------------------------------------------------------
328 type NodeWrite' = NodePoly (Maybe Int) Int Int (Maybe ParentId) Text (Maybe UTCTime) ByteString
329 ------------------------------------------------------------------------
330 defaultUser :: HyperdataUser
331 defaultUser = HyperdataUser (Just $ (pack . show) EN)
333 nodeUserW :: Maybe Name -> Maybe HyperdataUser -> UserId -> NodeWrite'
334 nodeUserW maybeName maybeHyperdata = node NodeUser name user Nothing
336 name = maybe "User" identity maybeName
337 user = maybe defaultUser identity maybeHyperdata
338 ------------------------------------------------------------------------
339 defaultFolder :: HyperdataFolder
340 defaultFolder = HyperdataFolder (Just "Markdown Description")
342 nodeFolderW :: Maybe Name -> Maybe HyperdataFolder -> ParentId -> UserId -> NodeWrite'
343 nodeFolderW maybeName maybeFolder pid = node NodeFolder name folder (Just pid)
345 name = maybe "Folder" identity maybeName
346 folder = maybe defaultFolder identity maybeFolder
347 ------------------------------------------------------------------------
348 nodeCorpusW :: Maybe Name -> Maybe HyperdataCorpus -> ParentId -> UserId -> NodeWrite'
349 nodeCorpusW maybeName maybeCorpus pId = node NodeCorpus name corpus (Just pId)
351 name = maybe "Corpus" identity maybeName
352 corpus = maybe defaultCorpus identity maybeCorpus
353 --------------------------
354 defaultDocument :: HyperdataDocument
355 defaultDocument = hyperdataDocument
357 nodeDocumentW :: Maybe Name -> Maybe HyperdataDocument -> CorpusId -> UserId -> NodeWrite'
358 nodeDocumentW maybeName maybeDocument cId = node NodeDocument name doc (Just cId)
360 name = maybe "Document" identity maybeName
361 doc = maybe defaultDocument identity maybeDocument
362 ------------------------------------------------------------------------
363 defaultAnnuaire :: HyperdataAnnuaire
364 defaultAnnuaire = HyperdataAnnuaire (Just "Title") (Just "Description")
366 nodeAnnuaireW :: Maybe Name -> Maybe HyperdataAnnuaire -> ParentId -> UserId -> NodeWrite'
367 nodeAnnuaireW maybeName maybeAnnuaire pId = node NodeAnnuaire name annuaire (Just pId)
369 name = maybe "Annuaire" identity maybeName
370 annuaire = maybe defaultAnnuaire identity maybeAnnuaire
371 --------------------------
373 ------------------------------------------------------------------------
374 arbitraryList :: HyperdataList
375 arbitraryList = HyperdataList (Just "Preferences")
377 nodeListW :: Maybe Name -> Maybe HyperdataList -> ParentId -> UserId -> NodeWrite'
378 nodeListW maybeName maybeList pId = node NodeList name list (Just pId)
380 name = maybe "Listes" identity maybeName
381 list = maybe arbitraryList identity maybeList
383 ------------------------------------------------------------------------
384 arbitraryGraph :: HyperdataGraph
385 arbitraryGraph = HyperdataGraph (Just "Preferences")
387 nodeGraphW :: Maybe Name -> Maybe HyperdataGraph -> ParentId -> UserId -> NodeWrite'
388 nodeGraphW maybeName maybeGraph pId = node NodeGraph name graph (Just pId)
390 name = maybe "Graph" identity maybeName
391 graph = maybe arbitraryGraph identity maybeGraph
393 ------------------------------------------------------------------------
395 arbitraryDashboard :: HyperdataDashboard
396 arbitraryDashboard = HyperdataDashboard (Just "Preferences")
398 nodeDashboardW :: Maybe Name -> Maybe HyperdataDashboard -> ParentId -> UserId -> NodeWrite'
399 nodeDashboardW maybeName maybeDashboard pId = node NodeDashboard name dashboard (Just pId)
401 name = maybe "Dashboard" identity maybeName
402 dashboard = maybe arbitraryDashboard identity maybeDashboard
406 ------------------------------------------------------------------------
407 node :: (ToJSON a, Hyperdata a) => NodeType -> Name -> a -> Maybe ParentId -> UserId -> NodeWrite'
408 node nodeType name hyperData parentId userId = Node Nothing typeId userId parentId name Nothing byteData
410 typeId = nodeTypeId nodeType
411 byteData = DB.pack . DBL.unpack $ encode hyperData
413 -------------------------------
414 node2row :: (Functor maybe1, Functor maybe2, Functor maybe3) =>
415 NodePoly (maybe2 Int) Int Int (maybe1 Int)
416 Text (maybe3 UTCTime) ByteString
417 -> (maybe2 (Column PGInt4), Column PGInt4, Column PGInt4, maybe1 (Column PGInt4)
418 , Column PGText, maybe3 (Column PGTimestamptz), Column PGJsonb)
419 node2row (Node id tn ud pid nm dt hp) = ((pgInt4 <$> id)
427 ------------------------------------------------------------------------
428 insertNodesR' :: [NodeWrite'] -> Cmd [Int]
429 insertNodesR' ns = mkCmd $ \c -> insertNodesR ns c
431 insertNodes :: [NodeWrite'] -> Connection -> IO Int64
432 insertNodes ns conn = runInsertMany conn nodeTable' (map node2row ns)
434 insertNodesR :: [NodeWrite'] -> Connection -> IO [Int]
435 insertNodesR ns conn = runInsertManyReturning conn nodeTable' (map node2row ns) (\(i,_,_,_,_,_,_) -> i)
436 -------------------------
437 insertNodesWithParent :: Maybe ParentId -> [NodeWrite'] -> Connection -> IO Int64
438 insertNodesWithParent pid ns conn = insertNodes (map (set node_parentId pid) ns) conn
440 insertNodesWithParentR :: Maybe ParentId -> [NodeWrite'] -> Connection -> IO [Int]
441 insertNodesWithParentR pid ns conn = insertNodesR (map (set node_parentId pid) ns) conn
442 ------------------------------------------------------------------------
443 -- TODO Hierachy of Nodes
444 -- post and get same types Node' and update if changes
446 {- TODO semantic to achieve
447 post c uid pid [ Node' NodeCorpus "name" "{}" []
448 , Node' NodeFolder "name" "{}" [Node' NodeCorpus "test 2" "" [ Node' NodeDocument "title" "metaData" []
449 , Node' NodeDocument "title" "jsonData" []
454 ------------------------------------------------------------------------
457 -- currently this function remove the child relation
458 -- needs a Temporary type between Node' and NodeWriteT
459 node2table :: UserId -> Maybe ParentId -> Node' -> NodeWriteT
460 node2table uid pid (Node' nt txt v []) = ( Nothing, (pgInt4$ nodeTypeId nt), (pgInt4 uid), (fmap pgInt4 pid)
461 , pgStrictText txt, Nothing, pgStrictJSONB $ DB.pack $ DBL.unpack $ encode v)
462 node2table _ _ (Node' _ _ _ _) = panic "node2table: should not happen, Tree insert not implemented yet"
465 data Node' = Node' { _n_type :: NodeType
468 , _n_children :: [Node']
472 type NodeWriteT = ( Maybe (Column PGInt4)
475 , Maybe (Column PGInt4)
477 , Maybe (Column PGTimestamptz)
482 mkNode' :: [NodeWriteT] -> Cmd Int64
483 mkNode' ns = mkCmd $ \conn -> runInsertMany conn nodeTable' ns
485 mkNodeR' :: [NodeWriteT] -> Cmd [Int]
486 mkNodeR' ns = mkCmd $ \conn -> runInsertManyReturning conn nodeTable' ns (\(i,_,_,_,_,_,_) -> i)
488 ------------------------------------------------------------------------
490 data NewNode = NewNode { _newNodeId :: Int
491 , _newNodeChildren :: [Int] }
494 postNode :: UserId -> Maybe ParentId -> Node' -> Cmd NewNode
495 postNode uid pid (Node' nt txt v []) = do
496 pids <- mkNodeR' [node2table uid pid (Node' nt txt v [])]
498 [pid] -> pure $ NewNode pid []
499 _ -> panic "postNode: only one pid expected"
501 postNode uid pid (Node' NodeCorpus txt v ns) = do
502 NewNode pid' _ <- postNode uid pid (Node' NodeCorpus txt v [])
503 pids <- mkNodeR' (concat $ map (\n -> [childWith uid pid' n]) ns)
504 pure $ NewNode pid' pids
506 postNode uid pid (Node' NodeAnnuaire txt v ns) = do
507 NewNode pid' _ <- postNode uid pid (Node' NodeAnnuaire txt v [])
508 pids <- mkNodeR' (concat $ map (\n -> [childWith uid pid' n]) ns)
509 pure $ NewNode pid' pids
510 postNode _ _ (Node' _ _ _ _) = panic "TODO: postNode for this type not implemented yet"
513 childWith :: UserId -> ParentId -> Node' -> NodeWriteT
514 childWith uId pId (Node' NodeDocument txt v []) = node2table uId (Just pId) (Node' NodeDocument txt v [])
515 childWith uId pId (Node' NodeContact txt v []) = node2table uId (Just pId) (Node' NodeContact txt v [])
516 childWith _ _ (Node' _ _ _ _) = panic "This NodeType can not be a child"
519 -- TODO: remove hardcoded userId (with Reader)
520 -- TODO: user Reader in the API and adapt this function
524 mk :: Connection -> NodeType -> Maybe ParentId -> Text -> IO [Int]
525 mk c nt pId name = mk' c nt userId pId name
527 mk' :: Connection -> NodeType -> UserId -> Maybe ParentId -> Text -> IO [Int]
528 mk' c nt uId pId name = map fromIntegral <$> insertNodesWithParentR pId [node nt name hd pId uId] c
530 hd = HyperdataUser . Just . pack $ show EN
534 mk'' :: NodeType -> Maybe ParentId -> UserId -> Name -> Cmd [Int]
535 mk'' NodeUser Nothing uId name = mkCmd $ \c -> mk' c NodeUser uId Nothing name
536 mk'' NodeUser _ _ _ = panic "NodeUser do not have any parent"
537 mk'' _ Nothing _ _ = panic "NodeType does have a parent"
538 mk'' nt pId uId name = mkCmd $ \c -> mk' c nt uId pId name
542 mkRoot :: Username -> UserId -> Cmd [Int]
543 mkRoot uname uId = case uId > 0 of
544 False -> panic "UserId <= 0"
545 True -> mk'' NodeUser Nothing uId uname
547 mkCorpus :: Maybe Name -> Maybe HyperdataCorpus -> ParentId -> UserId -> Cmd [Int]
548 mkCorpus n h p u = insertNodesR' [nodeCorpusW n h p u]
550 mkList :: ParentId -> UserId -> Cmd [Int]
551 mkList p u = insertNodesR' [nodeListW Nothing Nothing p u]
553 mkGraph :: ParentId -> UserId -> Cmd [Int]
554 mkGraph p u = insertNodesR' [nodeGraphW Nothing Nothing p u]
556 mkDashboard :: ParentId -> UserId -> Cmd [Int]
557 mkDashboard p u = insertNodesR' [nodeDashboardW Nothing Nothing p u]
559 mkAnnuaire :: ParentId -> UserId -> Cmd [Int]
560 mkAnnuaire p u = insertNodesR' [nodeAnnuaireW Nothing Nothing p u]
562 -- | Default CorpusId Master and ListId Master