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
276 parentId nodeType maybeOffset maybeLimit
280 getNodesWithParentId :: Int
281 -> Maybe Text -> Connection -> IO [NodeAny]
282 getNodesWithParentId n _ conn = runQuery conn $ selectNodesWithParentID n
284 getNodesWithParentId' :: Int
285 -> Maybe Text -> Connection -> IO [NodeAny]
286 getNodesWithParentId' n _ conn = runQuery conn $ selectNodesWithParentID n
289 ------------------------------------------------------------------------
290 getDocumentsV3WithParentId :: Connection -> Int -> IO [Node HyperdataDocumentV3]
291 getDocumentsV3WithParentId conn n = runQuery conn $ selectNodesWith' n (Just NodeDocument)
293 getDocumentsWithParentId :: Connection -> Int -> IO [Node HyperdataDocument]
294 getDocumentsWithParentId conn n = runQuery conn $ selectNodesWith' n (Just NodeDocument)
296 getListsWithParentId :: Connection -> Int -> IO [Node HyperdataList]
297 getListsWithParentId conn n = runQuery conn $ selectNodesWith' n (Just NodeList)
299 ------------------------------------------------------------------------
300 selectNodesWithParentID :: Int -> Query NodeRead
301 selectNodesWithParentID n = proc () -> do
302 row@(Node _ _ _ parent_id _ _ _) <- queryNodeTable -< ()
304 then parent_id .== (toNullable $ pgInt4 n)
305 else isNull parent_id
308 selectNodesWithType :: Column PGInt4 -> Query NodeRead
309 selectNodesWithType type_id = proc () -> do
310 row@(Node _ tn _ _ _ _ _) <- queryNodeTable -< ()
311 restrict -< tn .== type_id
314 type JSONB = QueryRunnerColumnDefault PGJsonb
316 getNode :: JSONB a => Connection -> Int -> proxy a -> IO (Node a)
317 getNode conn id _ = do
318 fromMaybe (error $ "Node does node exist: " <> show id) . headMay <$> runQuery conn (limit 1 $ selectNode (pgInt4 id))
320 getNodesWithType :: Connection -> Column PGInt4 -> IO [Node HyperdataDocument]
321 getNodesWithType conn type_id = do
322 runQuery conn $ selectNodesWithType type_id
324 ------------------------------------------------------------------------
326 -- TODO Classe HasDefault where
327 -- default NodeType = Hyperdata
328 ------------------------------------------------------------------------
329 type NodeWrite' = NodePoly (Maybe Int) Int Int (Maybe ParentId) Text (Maybe UTCTime) ByteString
330 ------------------------------------------------------------------------
331 defaultUser :: HyperdataUser
332 defaultUser = HyperdataUser (Just $ (pack . show) EN)
334 nodeUserW :: Maybe Name -> Maybe HyperdataUser -> UserId -> NodeWrite'
335 nodeUserW maybeName maybeHyperdata = node NodeUser name user Nothing
337 name = maybe "User" identity maybeName
338 user = maybe defaultUser identity maybeHyperdata
339 ------------------------------------------------------------------------
340 defaultFolder :: HyperdataFolder
341 defaultFolder = HyperdataFolder (Just "Markdown Description")
343 nodeFolderW :: Maybe Name -> Maybe HyperdataFolder -> ParentId -> UserId -> NodeWrite'
344 nodeFolderW maybeName maybeFolder pid = node NodeFolder name folder (Just pid)
346 name = maybe "Folder" identity maybeName
347 folder = maybe defaultFolder identity maybeFolder
348 ------------------------------------------------------------------------
349 nodeCorpusW :: Maybe Name -> Maybe HyperdataCorpus -> ParentId -> UserId -> NodeWrite'
350 nodeCorpusW maybeName maybeCorpus pId = node NodeCorpus name corpus (Just pId)
352 name = maybe "Corpus" identity maybeName
353 corpus = maybe defaultCorpus identity maybeCorpus
354 --------------------------
355 defaultDocument :: HyperdataDocument
356 defaultDocument = hyperdataDocument
358 nodeDocumentW :: Maybe Name -> Maybe HyperdataDocument -> CorpusId -> UserId -> NodeWrite'
359 nodeDocumentW maybeName maybeDocument cId = node NodeDocument name doc (Just cId)
361 name = maybe "Document" identity maybeName
362 doc = maybe defaultDocument identity maybeDocument
363 ------------------------------------------------------------------------
364 defaultAnnuaire :: HyperdataAnnuaire
365 defaultAnnuaire = HyperdataAnnuaire (Just "Title") (Just "Description")
367 nodeAnnuaireW :: Maybe Name -> Maybe HyperdataAnnuaire -> ParentId -> UserId -> NodeWrite'
368 nodeAnnuaireW maybeName maybeAnnuaire pId = node NodeAnnuaire name annuaire (Just pId)
370 name = maybe "Annuaire" identity maybeName
371 annuaire = maybe defaultAnnuaire identity maybeAnnuaire
372 --------------------------
374 ------------------------------------------------------------------------
375 arbitraryList :: HyperdataList
376 arbitraryList = HyperdataList (Just "Preferences")
378 nodeListW :: Maybe Name -> Maybe HyperdataList -> ParentId -> UserId -> NodeWrite'
379 nodeListW maybeName maybeList pId = node NodeList name list (Just pId)
381 name = maybe "Listes" identity maybeName
382 list = maybe arbitraryList identity maybeList
384 ------------------------------------------------------------------------
385 arbitraryGraph :: HyperdataGraph
386 arbitraryGraph = HyperdataGraph (Just "Preferences")
388 nodeGraphW :: Maybe Name -> Maybe HyperdataGraph -> ParentId -> UserId -> NodeWrite'
389 nodeGraphW maybeName maybeGraph pId = node NodeGraph name graph (Just pId)
391 name = maybe "Graph" identity maybeName
392 graph = maybe arbitraryGraph identity maybeGraph
394 ------------------------------------------------------------------------
396 arbitraryDashboard :: HyperdataDashboard
397 arbitraryDashboard = HyperdataDashboard (Just "Preferences")
399 nodeDashboardW :: Maybe Name -> Maybe HyperdataDashboard -> ParentId -> UserId -> NodeWrite'
400 nodeDashboardW maybeName maybeDashboard pId = node NodeDashboard name dashboard (Just pId)
402 name = maybe "Dashboard" identity maybeName
403 dashboard = maybe arbitraryDashboard identity maybeDashboard
407 ------------------------------------------------------------------------
408 node :: (ToJSON a, Hyperdata a) => NodeType -> Name -> a -> Maybe ParentId -> UserId -> NodeWrite'
409 node nodeType name hyperData parentId userId = Node Nothing typeId userId parentId name Nothing byteData
411 typeId = nodeTypeId nodeType
412 byteData = DB.pack . DBL.unpack $ encode hyperData
414 -------------------------------
415 node2row :: (Functor maybe1, Functor maybe2, Functor maybe3) =>
416 NodePoly (maybe2 Int) Int Int (maybe1 Int)
417 Text (maybe3 UTCTime) ByteString
418 -> (maybe2 (Column PGInt4), Column PGInt4, Column PGInt4, maybe1 (Column PGInt4)
419 , Column PGText, maybe3 (Column PGTimestamptz), Column PGJsonb)
420 node2row (Node id tn ud pid nm dt hp) = ((pgInt4 <$> id)
428 ------------------------------------------------------------------------
429 insertNodesR' :: [NodeWrite'] -> Cmd [Int]
430 insertNodesR' ns = mkCmd $ \c -> insertNodesR ns c
432 insertNodes :: [NodeWrite'] -> Connection -> IO Int64
433 insertNodes ns conn = runInsertMany conn nodeTable' (map node2row ns)
435 insertNodesR :: [NodeWrite'] -> Connection -> IO [Int]
436 insertNodesR ns conn = runInsertManyReturning conn nodeTable' (map node2row ns) (\(i,_,_,_,_,_,_) -> i)
437 -------------------------
438 insertNodesWithParent :: Maybe ParentId -> [NodeWrite'] -> Connection -> IO Int64
439 insertNodesWithParent pid ns conn = insertNodes (map (set node_parentId pid) ns) conn
441 insertNodesWithParentR :: Maybe ParentId -> [NodeWrite'] -> Connection -> IO [Int]
442 insertNodesWithParentR pid ns conn = insertNodesR (map (set node_parentId pid) ns) conn
443 ------------------------------------------------------------------------
444 -- TODO Hierachy of Nodes
445 -- post and get same types Node' and update if changes
447 {- TODO semantic to achieve
448 post c uid pid [ Node' NodeCorpus "name" "{}" []
449 , Node' NodeFolder "name" "{}" [Node' NodeCorpus "test 2" "" [ Node' NodeDocument "title" "metaData" []
450 , Node' NodeDocument "title" "jsonData" []
455 ------------------------------------------------------------------------
458 -- currently this function remove the child relation
459 -- needs a Temporary type between Node' and NodeWriteT
460 node2table :: UserId -> Maybe ParentId -> Node' -> NodeWriteT
461 node2table uid pid (Node' nt txt v []) = ( Nothing, (pgInt4$ nodeTypeId nt), (pgInt4 uid), (fmap pgInt4 pid)
462 , pgStrictText txt, Nothing, pgStrictJSONB $ DB.pack $ DBL.unpack $ encode v)
463 node2table _ _ (Node' _ _ _ _) = panic "node2table: should not happen, Tree insert not implemented yet"
466 data Node' = Node' { _n_type :: NodeType
469 , _n_children :: [Node']
473 type NodeWriteT = ( Maybe (Column PGInt4)
476 , Maybe (Column PGInt4)
478 , Maybe (Column PGTimestamptz)
483 mkNode' :: [NodeWriteT] -> Cmd Int64
484 mkNode' ns = mkCmd $ \conn -> runInsertMany conn nodeTable' ns
486 mkNodeR' :: [NodeWriteT] -> Cmd [Int]
487 mkNodeR' ns = mkCmd $ \conn -> runInsertManyReturning conn nodeTable' ns (\(i,_,_,_,_,_,_) -> i)
489 ------------------------------------------------------------------------
491 data NewNode = NewNode { _newNodeId :: Int
492 , _newNodeChildren :: [Int] }
495 postNode :: UserId -> Maybe ParentId -> Node' -> Cmd NewNode
496 postNode uid pid (Node' nt txt v []) = do
497 pids <- mkNodeR' [node2table uid pid (Node' nt txt v [])]
499 [pid] -> pure $ NewNode pid []
500 _ -> panic "postNode: only one pid expected"
502 postNode uid pid (Node' NodeCorpus txt v ns) = do
503 NewNode pid' _ <- postNode uid pid (Node' NodeCorpus txt v [])
504 pids <- mkNodeR' (concat $ map (\n -> [childWith uid pid' n]) ns)
505 pure $ NewNode pid' pids
507 postNode uid pid (Node' NodeAnnuaire txt v ns) = do
508 NewNode pid' _ <- postNode uid pid (Node' NodeAnnuaire txt v [])
509 pids <- mkNodeR' (concat $ map (\n -> [childWith uid pid' n]) ns)
510 pure $ NewNode pid' pids
511 postNode _ _ (Node' _ _ _ _) = panic "TODO: postNode for this type not implemented yet"
514 childWith :: UserId -> ParentId -> Node' -> NodeWriteT
515 childWith uId pId (Node' NodeDocument txt v []) = node2table uId (Just pId) (Node' NodeDocument txt v [])
516 childWith uId pId (Node' NodeContact txt v []) = node2table uId (Just pId) (Node' NodeContact txt v [])
517 childWith _ _ (Node' _ _ _ _) = panic "This NodeType can not be a child"
520 -- TODO: remove hardcoded userId (with Reader)
521 -- TODO: user Reader in the API and adapt this function
525 mk :: Connection -> NodeType -> Maybe ParentId -> Text -> IO [Int]
526 mk c nt pId name = mk' c nt userId pId name
528 mk' :: Connection -> NodeType -> UserId -> Maybe ParentId -> Text -> IO [Int]
529 mk' c nt uId pId name = map fromIntegral <$> insertNodesWithParentR pId [node nt name hd pId uId] c
531 hd = HyperdataUser . Just . pack $ show EN
535 mk'' :: NodeType -> Maybe ParentId -> UserId -> Name -> Cmd [Int]
536 mk'' NodeUser Nothing uId name = mkCmd $ \c -> mk' c NodeUser uId Nothing name
537 mk'' NodeUser _ _ _ = panic "NodeUser do not have any parent"
538 mk'' _ Nothing _ _ = panic "NodeType does have a parent"
539 mk'' nt pId uId name = mkCmd $ \c -> mk' c nt uId pId name
543 mkRoot :: Username -> UserId -> Cmd [Int]
544 mkRoot uname uId = case uId > 0 of
545 False -> panic "UserId <= 0"
546 True -> mk'' NodeUser Nothing uId uname
548 mkCorpus :: Maybe Name -> Maybe HyperdataCorpus -> ParentId -> UserId -> Cmd [Int]
549 mkCorpus n h p u = insertNodesR' [nodeCorpusW n h p u]
551 mkList :: ParentId -> UserId -> Cmd [Int]
552 mkList p u = insertNodesR' [nodeListW Nothing Nothing p u]
554 mkGraph :: ParentId -> UserId -> Cmd [Int]
555 mkGraph p u = insertNodesR' [nodeGraphW Nothing Nothing p u]
557 mkDashboard :: ParentId -> UserId -> Cmd [Int]
558 mkDashboard p u = insertNodesR' [nodeDashboardW Nothing Nothing p u]
560 mkAnnuaire :: ParentId -> UserId -> Cmd [Int]
561 mkAnnuaire p u = insertNodesR' [nodeAnnuaireW Nothing Nothing p u]
563 -- | Default CorpusId Master and ListId Master