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.Core.Types.Individu (Username)
39 import Gargantext.Database.Utils (fromField')
40 import Gargantext.Database.Types.Node (NodeType, defaultCorpus, Hyperdata)
41 import Gargantext.Database.Queries
42 import Gargantext.Database.Config (nodeTypeId)
43 import Gargantext.Prelude hiding (sum)
45 import Control.Applicative (Applicative)
46 import Control.Arrow (returnA)
47 import Control.Lens.TH (makeLensesWith, abbreviatedFields)
48 import Control.Monad.IO.Class
49 import Control.Monad.Reader
51 import Data.Maybe (Maybe, fromMaybe)
52 import Data.Text (Text)
53 import Data.Profunctor.Product.TH (makeAdaptorAndInstance)
55 import qualified Data.ByteString as DB
56 import qualified Data.ByteString.Lazy as DBL
57 import Data.ByteString (ByteString)
59 import Database.PostgreSQL.Simple (Connection)
60 import Opaleye hiding (FromField)
61 import Opaleye.Internal.QueryArr (Query)
62 import qualified Data.Profunctor.Product as PP
64 ------------------------------------------------------------------------
65 ------------------------------------------------------------------------
66 {- | Reader Monad reinvented here:
68 newtype Cmd a = Cmd { unCmd :: Connection -> IO a }
70 instance Monad Cmd where
71 return a = Cmd $ \_ -> return a
73 m >>= f = Cmd $ \c -> do
77 newtype Cmd a = Cmd (ReaderT Connection IO a)
78 deriving (Functor, Applicative, Monad, MonadReader Connection, MonadIO)
80 runCmd :: Connection -> Cmd a -> IO a
81 runCmd c (Cmd f) = runReaderT f c
83 mkCmd :: (Connection -> IO a) -> Cmd a
86 ------------------------------------------------------------------------
93 ------------------------------------------------------------------------
94 instance FromField HyperdataAny
96 fromField = fromField'
98 instance FromField HyperdataCorpus
100 fromField = fromField'
102 instance FromField HyperdataDocument
104 fromField = fromField'
106 instance FromField HyperdataDocumentV3
108 fromField = fromField'
110 instance FromField HyperdataUser
112 fromField = fromField'
114 instance FromField HyperdataList
116 fromField = fromField'
118 instance FromField HyperdataAnnuaire
120 fromField = fromField'
121 ------------------------------------------------------------------------
122 instance QueryRunnerColumnDefault PGJsonb HyperdataAny
124 queryRunnerColumnDefault = fieldQueryRunnerColumn
126 instance QueryRunnerColumnDefault PGJsonb HyperdataDocument
128 queryRunnerColumnDefault = fieldQueryRunnerColumn
130 instance QueryRunnerColumnDefault PGJsonb HyperdataDocumentV3
132 queryRunnerColumnDefault = fieldQueryRunnerColumn
134 instance QueryRunnerColumnDefault PGJsonb HyperdataCorpus
136 queryRunnerColumnDefault = fieldQueryRunnerColumn
138 instance QueryRunnerColumnDefault PGJsonb HyperdataUser
140 queryRunnerColumnDefault = fieldQueryRunnerColumn
142 instance QueryRunnerColumnDefault PGJsonb HyperdataList
144 queryRunnerColumnDefault = fieldQueryRunnerColumn
146 instance QueryRunnerColumnDefault PGJsonb HyperdataAnnuaire
148 queryRunnerColumnDefault = fieldQueryRunnerColumn
149 ------------------------------------------------------------------------
151 $(makeAdaptorAndInstance "pNode" ''NodePoly)
152 $(makeLensesWith abbreviatedFields ''NodePoly)
155 nodeTable :: Table NodeWrite NodeRead
156 nodeTable = Table "nodes" (pNode Node { _node_id = optional "id"
157 , _node_typename = required "typename"
158 , _node_userId = required "user_id"
159 , _node_parentId = required "parent_id"
160 , _node_name = required "name"
161 , _node_date = optional "date"
162 , _node_hyperdata = required "hyperdata"
163 -- , node_titleAbstract = optional "title_abstract"
168 nodeTable' :: Table (Maybe (Column PGInt4)
171 ,Maybe (Column PGInt4)
173 ,Maybe (Column PGTimestamptz)
181 ,(Column PGTimestamptz)
185 nodeTable' = Table "nodes" (PP.p7 ( optional "id"
186 , required "typename"
188 , optional "parent_id"
191 , required "hyperdata"
196 queryNodeTable :: Query NodeRead
197 queryNodeTable = queryTable nodeTable
199 selectNode :: Column PGInt4 -> Query NodeRead
200 selectNode id = proc () -> do
201 row <- queryNodeTable -< ()
202 restrict -< _node_id row .== id
205 runGetNodes :: Query NodeRead -> Cmd [NodeAny]
206 runGetNodes q = mkCmd $ \conn -> runQuery conn q
208 ------------------------------------------------------------------------
209 ------------------------------------------------------------------------
211 -- | order by publication date
212 -- Favorites (Bool), node_ngrams
213 selectNodesWith :: ParentId -> Maybe NodeType
214 -> Maybe Offset -> Maybe Limit -> Query NodeRead
215 selectNodesWith parentId maybeNodeType maybeOffset maybeLimit =
216 --offset' maybeOffset $ limit' maybeLimit $ orderBy (asc (hyperdataDocument_Publication_date . node_hyperdata)) $ selectNodesWith' parentId typeId
217 limit' maybeLimit $ offset' maybeOffset
218 $ orderBy (asc _node_id)
219 $ selectNodesWith' parentId maybeNodeType
221 selectNodesWith' :: ParentId -> Maybe NodeType -> Query NodeRead
222 selectNodesWith' parentId maybeNodeType = proc () -> do
223 node <- (proc () -> do
224 row@(Node _ typeId _ parentId' _ _ _) <- queryNodeTable -< ()
225 restrict -< parentId' .== (toNullable $ pgInt4 parentId)
227 let typeId' = maybe 0 nodeTypeId maybeNodeType
229 restrict -< if typeId' > 0
230 then typeId .== (pgInt4 (typeId' :: Int))
232 returnA -< row ) -< ()
236 --type Cmd' a = forall m. (MonadReader env m, HasConnection env, MonadIO m) => m a
239 -- deleteNode :: (MonadReader Connection m, MonadIO m) => Int -> m Int
240 -- deleteNode :: Int -> Cmd' Int
242 deleteNode :: Int -> Cmd Int
243 deleteNode n = mkCmd $ \conn ->
244 fromIntegral <$> runDelete conn nodeTable
245 (\(Node n_id _ _ _ _ _ _) -> n_id .== pgInt4 n)
247 deleteNodes :: [Int] -> Cmd Int
248 deleteNodes ns = mkCmd $ \conn ->
249 fromIntegral <$> runDelete conn nodeTable
250 (\(Node n_id _ _ _ _ _ _) -> in_ ((map pgInt4 ns)) n_id)
253 getNodesWith :: JSONB a => Connection -> Int -> proxy a -> Maybe NodeType
254 -> Maybe Offset -> Maybe Limit -> IO [Node a]
255 getNodesWith conn parentId _ nodeType maybeOffset maybeLimit =
256 runQuery conn $ selectNodesWith parentId nodeType maybeOffset maybeLimit
260 getNodesWithParentId :: Int
261 -> Maybe Text -> Connection -> IO [NodeAny]
262 getNodesWithParentId n _ conn = runQuery conn $ selectNodesWithParentID n
264 getNodesWithParentId' :: Int
265 -> Maybe Text -> Connection -> IO [NodeAny]
266 getNodesWithParentId' n _ conn = runQuery conn $ selectNodesWithParentID n
269 ------------------------------------------------------------------------
270 getDocumentsV3WithParentId :: Connection -> Int -> IO [Node HyperdataDocumentV3]
271 getDocumentsV3WithParentId conn n = runQuery conn $ selectNodesWith' n (Just NodeDocument)
273 getDocumentsWithParentId :: Connection -> Int -> IO [Node HyperdataDocument]
274 getDocumentsWithParentId conn n = runQuery conn $ selectNodesWith' n (Just NodeDocument)
276 getListsWithParentId :: Connection -> Int -> IO [Node HyperdataList]
277 getListsWithParentId conn n = runQuery conn $ selectNodesWith' n (Just NodeList)
279 ------------------------------------------------------------------------
280 selectNodesWithParentID :: Int -> Query NodeRead
281 selectNodesWithParentID n = proc () -> do
282 row@(Node _ _ _ parent_id _ _ _) <- queryNodeTable -< ()
284 then parent_id .== (toNullable $ pgInt4 n)
285 else isNull parent_id
288 selectNodesWithType :: Column PGInt4 -> Query NodeRead
289 selectNodesWithType type_id = proc () -> do
290 row@(Node _ tn _ _ _ _ _) <- queryNodeTable -< ()
291 restrict -< tn .== type_id
294 type JSONB = QueryRunnerColumnDefault PGJsonb
296 getNode :: JSONB a => Connection -> Int -> proxy a -> IO (Node a)
297 getNode conn id _ = do
298 fromMaybe (error $ "Node does node exist: " <> show id) . headMay <$> runQuery conn (limit 1 $ selectNode (pgInt4 id))
300 getNodesWithType :: Connection -> Column PGInt4 -> IO [Node HyperdataDocument]
301 getNodesWithType conn type_id = do
302 runQuery conn $ selectNodesWithType type_id
304 ------------------------------------------------------------------------
306 -- TODO Classe HasDefault where
307 -- default NodeType = Hyperdata
308 ------------------------------------------------------------------------
309 type NodeWrite' = NodePoly (Maybe Int) Int Int (Maybe ParentId) Text (Maybe UTCTime) ByteString
310 ------------------------------------------------------------------------
311 defaultUser :: HyperdataUser
312 defaultUser = HyperdataUser (Just $ (pack . show) EN)
314 nodeUserW :: Maybe Name -> Maybe HyperdataUser -> UserId -> NodeWrite'
315 nodeUserW maybeName maybeHyperdata = node NodeUser name user Nothing
317 name = maybe "User" identity maybeName
318 user = maybe defaultUser identity maybeHyperdata
319 ------------------------------------------------------------------------
320 defaultFolder :: HyperdataFolder
321 defaultFolder = HyperdataFolder (Just "Markdown Description")
323 nodeFolderW :: Maybe Name -> Maybe HyperdataFolder -> ParentId -> UserId -> NodeWrite'
324 nodeFolderW maybeName maybeFolder pid = node NodeFolder name folder (Just pid)
326 name = maybe "Folder" identity maybeName
327 folder = maybe defaultFolder identity maybeFolder
328 ------------------------------------------------------------------------
329 nodeCorpusW :: Maybe Name -> Maybe HyperdataCorpus -> ParentId -> UserId -> NodeWrite'
330 nodeCorpusW maybeName maybeCorpus pId = node NodeCorpus name corpus (Just pId)
332 name = maybe "Corpus" identity maybeName
333 corpus = maybe defaultCorpus identity maybeCorpus
334 --------------------------
335 defaultDocument :: HyperdataDocument
336 defaultDocument = hyperdataDocument
338 nodeDocumentW :: Maybe Name -> Maybe HyperdataDocument -> CorpusId -> UserId -> NodeWrite'
339 nodeDocumentW maybeName maybeDocument cId = node NodeDocument name doc (Just cId)
341 name = maybe "Document" identity maybeName
342 doc = maybe defaultDocument identity maybeDocument
343 ------------------------------------------------------------------------
344 defaultAnnuaire :: HyperdataAnnuaire
345 defaultAnnuaire = HyperdataAnnuaire (Just "Title") (Just "Description")
347 nodeAnnuaireW :: Maybe Name -> Maybe HyperdataAnnuaire -> ParentId -> UserId -> NodeWrite'
348 nodeAnnuaireW maybeName maybeAnnuaire pId = node NodeAnnuaire name annuaire (Just pId)
350 name = maybe "Annuaire" identity maybeName
351 annuaire = maybe defaultAnnuaire identity maybeAnnuaire
352 --------------------------
354 ------------------------------------------------------------------------
355 arbitraryList :: HyperdataList
356 arbitraryList = HyperdataList (Just "Preferences")
358 nodeListW :: Maybe Name -> Maybe HyperdataList -> ParentId -> UserId -> NodeWrite'
359 nodeListW maybeName maybeList pId = node NodeList name list (Just pId)
361 name = maybe "Listes" identity maybeName
362 list = maybe arbitraryList identity maybeList
364 ------------------------------------------------------------------------
365 arbitraryGraph :: HyperdataGraph
366 arbitraryGraph = HyperdataGraph (Just "Preferences")
368 nodeGraphW :: Maybe Name -> Maybe HyperdataGraph -> ParentId -> UserId -> NodeWrite'
369 nodeGraphW maybeName maybeGraph pId = node NodeGraph name graph (Just pId)
371 name = maybe "Graph" identity maybeName
372 graph = maybe arbitraryGraph identity maybeGraph
374 ------------------------------------------------------------------------
376 arbitraryDashboard :: HyperdataDashboard
377 arbitraryDashboard = HyperdataDashboard (Just "Preferences")
379 nodeDashboardW :: Maybe Name -> Maybe HyperdataDashboard -> ParentId -> UserId -> NodeWrite'
380 nodeDashboardW maybeName maybeDashboard pId = node NodeDashboard name dashboard (Just pId)
382 name = maybe "Dashboard" identity maybeName
383 dashboard = maybe arbitraryDashboard identity maybeDashboard
387 ------------------------------------------------------------------------
388 node :: (ToJSON a, Hyperdata a) => NodeType -> Name -> a -> Maybe ParentId -> UserId -> NodeWrite'
389 node nodeType name hyperData parentId userId = Node Nothing typeId userId parentId name Nothing byteData
391 typeId = nodeTypeId nodeType
392 byteData = DB.pack . DBL.unpack $ encode hyperData
394 -------------------------------
395 node2row :: (Functor maybe1, Functor maybe2, Functor maybe3) =>
396 NodePoly (maybe2 Int) Int Int (maybe1 Int)
397 Text (maybe3 UTCTime) ByteString
398 -> (maybe2 (Column PGInt4), Column PGInt4, Column PGInt4, maybe1 (Column PGInt4)
399 , Column PGText, maybe3 (Column PGTimestamptz), Column PGJsonb)
400 node2row (Node id tn ud pid nm dt hp) = ((pgInt4 <$> id)
408 ------------------------------------------------------------------------
409 insertNodesR' :: [NodeWrite'] -> Cmd [Int]
410 insertNodesR' ns = mkCmd $ \c -> insertNodesR ns c
412 insertNodes :: [NodeWrite'] -> Connection -> IO Int64
413 insertNodes ns conn = runInsertMany conn nodeTable' (map node2row ns)
415 insertNodesR :: [NodeWrite'] -> Connection -> IO [Int]
416 insertNodesR ns conn = runInsertManyReturning conn nodeTable' (map node2row ns) (\(i,_,_,_,_,_,_) -> i)
417 -------------------------
418 insertNodesWithParent :: Maybe ParentId -> [NodeWrite'] -> Connection -> IO Int64
419 insertNodesWithParent pid ns conn = insertNodes (map (set node_parentId pid) ns) conn
421 insertNodesWithParentR :: Maybe ParentId -> [NodeWrite'] -> Connection -> IO [Int]
422 insertNodesWithParentR pid ns conn = insertNodesR (map (set node_parentId pid) ns) conn
423 ------------------------------------------------------------------------
424 -- TODO Hierachy of Nodes
425 -- post and get same types Node' and update if changes
427 {- TODO semantic to achieve
428 post c uid pid [ Node' NodeCorpus "name" "{}" []
429 , Node' NodeFolder "name" "{}" [Node' NodeCorpus "test 2" "" [ Node' NodeDocument "title" "metaData" []
430 , Node' NodeDocument "title" "jsonData" []
435 ------------------------------------------------------------------------
438 -- currently this function remove the child relation
439 -- needs a Temporary type between Node' and NodeWriteT
440 node2table :: UserId -> Maybe ParentId -> Node' -> NodeWriteT
441 node2table uid pid (Node' nt txt v []) = ( Nothing, (pgInt4$ nodeTypeId nt), (pgInt4 uid), (fmap pgInt4 pid)
442 , pgStrictText txt, Nothing, pgStrictJSONB $ DB.pack $ DBL.unpack $ encode v)
443 node2table _ _ (Node' _ _ _ _) = panic "node2table: should not happen, Tree insert not implemented yet"
446 data Node' = Node' { _n_type :: NodeType
449 , _n_children :: [Node']
453 type NodeWriteT = ( Maybe (Column PGInt4)
456 , Maybe (Column PGInt4)
458 , Maybe (Column PGTimestamptz)
463 mkNode' :: [NodeWriteT] -> Cmd Int64
464 mkNode' ns = mkCmd $ \conn -> runInsertMany conn nodeTable' ns
466 mkNodeR' :: [NodeWriteT] -> Cmd [Int]
467 mkNodeR' ns = mkCmd $ \conn -> runInsertManyReturning conn nodeTable' ns (\(i,_,_,_,_,_,_) -> i)
469 ------------------------------------------------------------------------
471 data NewNode = NewNode { _newNodeId :: Int
472 , _newNodeChildren :: [Int] }
475 postNode :: UserId -> Maybe ParentId -> Node' -> Cmd NewNode
476 postNode uid pid (Node' nt txt v []) = do
477 pids <- mkNodeR' [node2table uid pid (Node' nt txt v [])]
479 [pid] -> pure $ NewNode pid []
480 _ -> panic "postNode: only one pid expected"
482 postNode uid pid (Node' NodeCorpus txt v ns) = do
483 NewNode pid' _ <- postNode uid pid (Node' NodeCorpus txt v [])
484 pids <- mkNodeR' (concat $ map (\n -> [childWith uid pid' n]) ns)
485 pure $ NewNode pid' pids
487 postNode uid pid (Node' NodeAnnuaire txt v ns) = do
488 NewNode pid' _ <- postNode uid pid (Node' NodeAnnuaire txt v [])
489 pids <- mkNodeR' (concat $ map (\n -> [childWith uid pid' n]) ns)
490 pure $ NewNode pid' pids
491 postNode _ _ (Node' _ _ _ _) = panic "TODO: postNode for this type not implemented yet"
494 childWith :: UserId -> ParentId -> Node' -> NodeWriteT
495 childWith uId pId (Node' NodeDocument txt v []) = node2table uId (Just pId) (Node' NodeDocument txt v [])
496 childWith uId pId (Node' NodeContact txt v []) = node2table uId (Just pId) (Node' NodeContact txt v [])
497 childWith _ _ (Node' _ _ _ _) = panic "This NodeType can not be a child"
500 -- TODO: remove hardcoded userId (with Reader)
501 -- TODO: user Reader in the API and adapt this function
505 mk :: Connection -> NodeType -> Maybe ParentId -> Text -> IO [Int]
506 mk c nt pId name = mk' c nt userId pId name
508 mk' :: Connection -> NodeType -> UserId -> Maybe ParentId -> Text -> IO [Int]
509 mk' c nt uId pId name = map fromIntegral <$> insertNodesWithParentR pId [node nt name hd pId uId] c
511 hd = HyperdataUser . Just . pack $ show EN
515 mk'' :: NodeType -> Maybe ParentId -> UserId -> Name -> Cmd [Int]
516 mk'' NodeUser Nothing uId name = mkCmd $ \c -> mk' c NodeUser uId Nothing name
517 mk'' NodeUser _ _ _ = panic "NodeUser do not have any parent"
518 mk'' _ Nothing _ _ = panic "NodeType does have a parent"
519 mk'' nt pId uId name = mkCmd $ \c -> mk' c nt uId pId name
522 mkRoot :: Username -> UserId -> Cmd [Int]
523 mkRoot uname uId = case uId > 0 of
524 False -> panic "UserId <= 0"
525 True -> mk'' NodeUser Nothing uId uname
527 mkCorpus :: Maybe Name -> Maybe HyperdataCorpus -> ParentId -> UserId -> Cmd [Int]
528 mkCorpus n h p u = insertNodesR' [nodeCorpusW n h p u]
530 mkList :: ParentId -> UserId -> Cmd [Int]
531 mkList p u = insertNodesR' [nodeListW Nothing Nothing p u]
533 mkGraph :: ParentId -> UserId -> Cmd [Int]
534 mkGraph p u = insertNodesR' [nodeGraphW Nothing Nothing p u]
536 mkDashboard :: ParentId -> UserId -> Cmd [Int]
537 mkDashboard p u = insertNodesR' [nodeDashboardW Nothing Nothing p u]
539 mkAnnuaire :: ParentId -> UserId -> Cmd [Int]
540 mkAnnuaire p u = insertNodesR' [nodeAnnuaireW Nothing Nothing p u]
542 -- | Default CorpusId Master and ListId Master