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, head)
44 import Gargantext.Core.Types.Main (UserId)
46 import Control.Applicative (Applicative)
47 import Control.Arrow (returnA)
48 import Control.Lens.TH (makeLensesWith, abbreviatedFields)
49 import Control.Monad.IO.Class
50 import Control.Monad.Reader
52 import Data.Maybe (Maybe, fromMaybe)
53 import Data.Text (Text)
54 import Data.Profunctor.Product.TH (makeAdaptorAndInstance)
56 import qualified Data.ByteString as DB
57 import qualified Data.ByteString.Lazy as DBL
58 import Data.ByteString (ByteString)
60 import Database.PostgreSQL.Simple (Connection)
61 import Opaleye hiding (FromField)
62 import Opaleye.Internal.QueryArr (Query)
63 import qualified Data.Profunctor.Product as PP
65 ------------------------------------------------------------------------
66 ------------------------------------------------------------------------
67 {- | Reader Monad reinvented here:
69 newtype Cmd a = Cmd { unCmd :: Connection -> IO a }
71 instance Monad Cmd where
72 return a = Cmd $ \_ -> return a
74 m >>= f = Cmd $ \c -> do
78 newtype Cmd a = Cmd (ReaderT Connection IO a)
79 deriving (Functor, Applicative, Monad, MonadReader Connection, MonadIO)
81 runCmd :: Connection -> Cmd a -> IO a
82 runCmd c (Cmd f) = runReaderT f c
84 mkCmd :: (Connection -> IO a) -> Cmd a
87 ------------------------------------------------------------------------
88 ------------------------------------------------------------------------
89 instance FromField HyperdataAny
91 fromField = fromField'
93 instance FromField HyperdataCorpus
95 fromField = fromField'
97 instance FromField HyperdataDocument
99 fromField = fromField'
101 instance FromField HyperdataDocumentV3
103 fromField = fromField'
105 instance FromField HyperdataUser
107 fromField = fromField'
109 instance FromField HyperdataList
111 fromField = fromField'
113 instance FromField HyperdataAnnuaire
115 fromField = fromField'
116 ------------------------------------------------------------------------
117 instance QueryRunnerColumnDefault PGJsonb HyperdataAny
119 queryRunnerColumnDefault = fieldQueryRunnerColumn
121 instance QueryRunnerColumnDefault PGJsonb HyperdataDocument
123 queryRunnerColumnDefault = fieldQueryRunnerColumn
125 instance QueryRunnerColumnDefault PGJsonb HyperdataDocumentV3
127 queryRunnerColumnDefault = fieldQueryRunnerColumn
129 instance QueryRunnerColumnDefault PGJsonb HyperdataCorpus
131 queryRunnerColumnDefault = fieldQueryRunnerColumn
133 instance QueryRunnerColumnDefault PGJsonb HyperdataUser
135 queryRunnerColumnDefault = fieldQueryRunnerColumn
137 instance QueryRunnerColumnDefault PGJsonb HyperdataList
139 queryRunnerColumnDefault = fieldQueryRunnerColumn
141 instance QueryRunnerColumnDefault PGJsonb HyperdataAnnuaire
143 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"
154 , _node_parentId = required "parent_id"
155 , _node_name = required "name"
156 , _node_date = optional "date"
157 , _node_hyperdata = required "hyperdata"
158 -- , node_titleAbstract = optional "title_abstract"
163 nodeTable' :: Table (Maybe (Column PGInt4)
166 ,Maybe (Column PGInt4)
168 ,Maybe (Column PGTimestamptz)
176 ,(Column PGTimestamptz)
180 nodeTable' = Table "nodes" (PP.p7 ( optional "id"
181 , required "typename"
183 , optional "parent_id"
186 , required "hyperdata"
191 queryNodeTable :: Query NodeRead
192 queryNodeTable = queryTable nodeTable
194 selectNode :: Column PGInt4 -> Query NodeRead
195 selectNode id = proc () -> do
196 row <- queryNodeTable -< ()
197 restrict -< _node_id row .== id
200 runGetNodes :: Query NodeRead -> Cmd [NodeAny]
201 runGetNodes q = mkCmd $ \conn -> runQuery conn q
203 ------------------------------------------------------------------------
204 ------------------------------------------------------------------------
206 -- | order by publication date
207 -- Favorites (Bool), node_ngrams
208 selectNodesWith :: ParentId -> Maybe NodeType
209 -> Maybe Offset -> Maybe Limit -> Query NodeRead
210 selectNodesWith parentId maybeNodeType maybeOffset maybeLimit =
211 --offset' maybeOffset $ limit' maybeLimit $ orderBy (asc (hyperdataDocument_Publication_date . node_hyperdata)) $ selectNodesWith' parentId typeId
212 limit' maybeLimit $ offset' maybeOffset
213 $ orderBy (asc _node_id)
214 $ selectNodesWith' parentId maybeNodeType
216 selectNodesWith' :: ParentId -> Maybe NodeType -> Query NodeRead
217 selectNodesWith' parentId maybeNodeType = proc () -> do
218 node <- (proc () -> do
219 row@(Node _ typeId _ parentId' _ _ _) <- queryNodeTable -< ()
220 restrict -< parentId' .== (toNullable $ pgInt4 parentId)
222 let typeId' = maybe 0 nodeTypeId maybeNodeType
224 restrict -< if typeId' > 0
225 then typeId .== (pgInt4 (typeId' :: Int))
227 returnA -< row ) -< ()
231 --type Cmd' a = forall m. (MonadReader env m, HasConnection env, MonadIO m) => m a
234 -- deleteNode :: (MonadReader Connection m, MonadIO m) => Int -> m Int
235 -- deleteNode :: Int -> Cmd' Int
237 deleteNode :: Int -> Cmd Int
238 deleteNode n = mkCmd $ \conn ->
239 fromIntegral <$> runDelete conn nodeTable
240 (\(Node n_id _ _ _ _ _ _) -> n_id .== pgInt4 n)
242 deleteNodes :: [Int] -> Cmd Int
243 deleteNodes ns = mkCmd $ \conn ->
244 fromIntegral <$> runDelete conn nodeTable
245 (\(Node n_id _ _ _ _ _ _) -> in_ ((map pgInt4 ns)) n_id)
248 getNodesWith :: JSONB a => Connection -> Int -> proxy a -> Maybe NodeType
249 -> Maybe Offset -> Maybe Limit -> IO [Node a]
250 getNodesWith conn parentId _ nodeType maybeOffset maybeLimit =
251 runQuery conn $ selectNodesWith parentId nodeType maybeOffset maybeLimit
255 getNodesWithParentId :: Int
256 -> Maybe Text -> Connection -> IO [NodeAny]
257 getNodesWithParentId n _ conn = runQuery conn $ selectNodesWithParentID n
259 getNodesWithParentId' :: Int
260 -> Maybe Text -> Connection -> IO [NodeAny]
261 getNodesWithParentId' n _ conn = runQuery conn $ selectNodesWithParentID n
264 ------------------------------------------------------------------------
265 getDocumentsV3WithParentId :: Connection -> Int -> IO [Node HyperdataDocumentV3]
266 getDocumentsV3WithParentId conn n = runQuery conn $ selectNodesWith' n (Just NodeDocument)
268 getDocumentsWithParentId :: Connection -> Int -> IO [Node HyperdataDocument]
269 getDocumentsWithParentId conn n = runQuery conn $ selectNodesWith' n (Just NodeDocument)
271 getListsWithParentId :: Connection -> Int -> IO [Node HyperdataList]
272 getListsWithParentId conn n = runQuery conn $ selectNodesWith' n (Just NodeList)
274 getCorporaWithParentId :: Connection -> Int -> IO [Node HyperdataCorpus]
275 getCorporaWithParentId conn n = runQuery conn $ selectNodesWith' n (Just NodeCorpus)
277 getCorporaWithParentId' :: Int -> Cmd [Node HyperdataCorpus]
278 getCorporaWithParentId' n = mkCmd $ \conn -> runQuery conn $ selectNodesWith' n (Just NodeCorpus)
281 ------------------------------------------------------------------------
282 selectNodesWithParentID :: Int -> Query NodeRead
283 selectNodesWithParentID n = proc () -> do
284 row@(Node _ _ _ parent_id _ _ _) <- queryNodeTable -< ()
286 then parent_id .== (toNullable $ pgInt4 n)
287 else isNull parent_id
290 selectNodesWithType :: Column PGInt4 -> Query NodeRead
291 selectNodesWithType type_id = proc () -> do
292 row@(Node _ tn _ _ _ _ _) <- queryNodeTable -< ()
293 restrict -< tn .== type_id
296 type JSONB = QueryRunnerColumnDefault PGJsonb
298 getNode :: JSONB a => Connection -> Int -> proxy a -> IO (Node a)
299 getNode conn id _ = do
300 fromMaybe (error $ "Node does node exist: " <> show id) . headMay <$> runQuery conn (limit 1 $ selectNode (pgInt4 id))
302 getNodesWithType :: Connection -> Column PGInt4 -> IO [Node HyperdataDocument]
303 getNodesWithType conn type_id = do
304 runQuery conn $ selectNodesWithType type_id
306 ------------------------------------------------------------------------
308 -- TODO Classe HasDefault where
309 -- default NodeType = Hyperdata
310 ------------------------------------------------------------------------
311 type NodeWrite' = NodePoly (Maybe Int) Int Int (Maybe ParentId) Text (Maybe UTCTime) ByteString
312 ------------------------------------------------------------------------
313 defaultUser :: HyperdataUser
314 defaultUser = HyperdataUser (Just $ (pack . show) EN)
316 nodeUserW :: Maybe Name -> Maybe HyperdataUser -> UserId -> NodeWrite'
317 nodeUserW maybeName maybeHyperdata = node NodeUser name user Nothing
319 name = maybe "User" identity maybeName
320 user = maybe defaultUser identity maybeHyperdata
321 ------------------------------------------------------------------------
322 defaultFolder :: HyperdataFolder
323 defaultFolder = HyperdataFolder (Just "Markdown Description")
325 nodeFolderW :: Maybe Name -> Maybe HyperdataFolder -> ParentId -> UserId -> NodeWrite'
326 nodeFolderW maybeName maybeFolder pid = node NodeFolder name folder (Just pid)
328 name = maybe "Folder" identity maybeName
329 folder = maybe defaultFolder identity maybeFolder
330 ------------------------------------------------------------------------
331 nodeCorpusW :: Maybe Name -> Maybe HyperdataCorpus -> ParentId -> UserId -> NodeWrite'
332 nodeCorpusW maybeName maybeCorpus pId = node NodeCorpus name corpus (Just pId)
334 name = maybe "Corpus" identity maybeName
335 corpus = maybe defaultCorpus identity maybeCorpus
336 --------------------------
337 defaultDocument :: HyperdataDocument
338 defaultDocument = hyperdataDocument
340 nodeDocumentW :: Maybe Name -> Maybe HyperdataDocument -> CorpusId -> UserId -> NodeWrite'
341 nodeDocumentW maybeName maybeDocument cId = node NodeDocument name doc (Just cId)
343 name = maybe "Document" identity maybeName
344 doc = maybe defaultDocument identity maybeDocument
345 ------------------------------------------------------------------------
346 defaultAnnuaire :: HyperdataAnnuaire
347 defaultAnnuaire = HyperdataAnnuaire (Just "Title") (Just "Description")
349 nodeAnnuaireW :: Maybe Name -> Maybe HyperdataAnnuaire -> ParentId -> UserId -> NodeWrite'
350 nodeAnnuaireW maybeName maybeAnnuaire pId = node NodeAnnuaire name annuaire (Just pId)
352 name = maybe "Annuaire" identity maybeName
353 annuaire = maybe defaultAnnuaire identity maybeAnnuaire
354 --------------------------
356 ------------------------------------------------------------------------
357 arbitraryList :: HyperdataList
358 arbitraryList = HyperdataList (Just "Preferences")
360 nodeListW :: Maybe Name -> Maybe HyperdataList -> ParentId -> UserId -> NodeWrite'
361 nodeListW maybeName maybeList pId = node NodeList name list (Just pId)
363 name = maybe "Listes" identity maybeName
364 list = maybe arbitraryList identity maybeList
366 ------------------------------------------------------------------------
367 arbitraryGraph :: HyperdataGraph
368 arbitraryGraph = HyperdataGraph (Just "Preferences")
370 nodeGraphW :: Maybe Name -> Maybe HyperdataGraph -> ParentId -> UserId -> NodeWrite'
371 nodeGraphW maybeName maybeGraph pId = node NodeGraph name graph (Just pId)
373 name = maybe "Graph" identity maybeName
374 graph = maybe arbitraryGraph identity maybeGraph
376 ------------------------------------------------------------------------
378 arbitraryDashboard :: HyperdataDashboard
379 arbitraryDashboard = HyperdataDashboard (Just "Preferences")
381 nodeDashboardW :: Maybe Name -> Maybe HyperdataDashboard -> ParentId -> UserId -> NodeWrite'
382 nodeDashboardW maybeName maybeDashboard pId = node NodeDashboard name dashboard (Just pId)
384 name = maybe "Dashboard" identity maybeName
385 dashboard = maybe arbitraryDashboard identity maybeDashboard
389 ------------------------------------------------------------------------
390 node :: (ToJSON a, Hyperdata a) => NodeType -> Name -> a -> Maybe ParentId -> UserId -> NodeWrite'
391 node nodeType name hyperData parentId userId = Node Nothing typeId userId parentId name Nothing byteData
393 typeId = nodeTypeId nodeType
394 byteData = DB.pack . DBL.unpack $ encode hyperData
396 -------------------------------
397 node2row :: (Functor maybe1, Functor maybe2, Functor maybe3) =>
398 NodePoly (maybe2 Int) Int Int (maybe1 Int)
399 Text (maybe3 UTCTime) ByteString
400 -> (maybe2 (Column PGInt4), Column PGInt4, Column PGInt4, maybe1 (Column PGInt4)
401 , Column PGText, maybe3 (Column PGTimestamptz), Column PGJsonb)
402 node2row (Node id tn ud pid nm dt hp) = ((pgInt4 <$> id)
410 ------------------------------------------------------------------------
411 insertNodesR' :: [NodeWrite'] -> Cmd [Int]
412 insertNodesR' ns = mkCmd $ \c -> insertNodesR ns c
414 insertNodes :: [NodeWrite'] -> Connection -> IO Int64
415 insertNodes ns conn = runInsertMany conn nodeTable' (map node2row ns)
417 insertNodesR :: [NodeWrite'] -> Connection -> IO [Int]
418 insertNodesR ns conn = runInsertManyReturning conn nodeTable' (map node2row ns) (\(i,_,_,_,_,_,_) -> i)
419 -------------------------
420 insertNodesWithParent :: Maybe ParentId -> [NodeWrite'] -> Connection -> IO Int64
421 insertNodesWithParent pid ns conn = insertNodes (map (set node_parentId pid) ns) conn
423 insertNodesWithParentR :: Maybe ParentId -> [NodeWrite'] -> Connection -> IO [Int]
424 insertNodesWithParentR pid ns conn = insertNodesR (map (set node_parentId pid) ns) conn
425 ------------------------------------------------------------------------
426 -- TODO Hierachy of Nodes
427 -- post and get same types Node' and update if changes
429 {- TODO semantic to achieve
430 post c uid pid [ Node' NodeCorpus "name" "{}" []
431 , Node' NodeFolder "name" "{}" [Node' NodeCorpus "test 2" "" [ Node' NodeDocument "title" "metaData" []
432 , Node' NodeDocument "title" "jsonData" []
437 ------------------------------------------------------------------------
440 -- currently this function remove the child relation
441 -- needs a Temporary type between Node' and NodeWriteT
442 node2table :: UserId -> Maybe ParentId -> Node' -> NodeWriteT
443 node2table uid pid (Node' nt txt v []) = ( Nothing, (pgInt4$ nodeTypeId nt), (pgInt4 uid), (fmap pgInt4 pid)
444 , pgStrictText txt, Nothing, pgStrictJSONB $ DB.pack $ DBL.unpack $ encode v)
445 node2table _ _ (Node' _ _ _ _) = panic "node2table: should not happen, Tree insert not implemented yet"
448 data Node' = Node' { _n_type :: NodeType
451 , _n_children :: [Node']
455 type NodeWriteT = ( Maybe (Column PGInt4)
458 , Maybe (Column PGInt4)
460 , Maybe (Column PGTimestamptz)
465 mkNode' :: [NodeWriteT] -> Cmd Int64
466 mkNode' ns = mkCmd $ \conn -> runInsertMany conn nodeTable' ns
468 mkNodeR' :: [NodeWriteT] -> Cmd [Int]
469 mkNodeR' ns = mkCmd $ \conn -> runInsertManyReturning conn nodeTable' ns (\(i,_,_,_,_,_,_) -> i)
471 ------------------------------------------------------------------------
473 data NewNode = NewNode { _newNodeId :: Int
474 , _newNodeChildren :: [Int] }
477 postNode :: UserId -> Maybe ParentId -> Node' -> Cmd NewNode
478 postNode uid pid (Node' nt txt v []) = do
479 pids <- mkNodeR' [node2table uid pid (Node' nt txt v [])]
481 [pid] -> pure $ NewNode pid []
482 _ -> panic "postNode: only one pid expected"
484 postNode uid pid (Node' NodeCorpus txt v ns) = do
485 NewNode pid' _ <- postNode uid pid (Node' NodeCorpus txt v [])
486 pids <- mkNodeR' (concat $ map (\n -> [childWith uid pid' n]) ns)
487 pure $ NewNode pid' pids
489 postNode uid pid (Node' NodeAnnuaire txt v ns) = do
490 NewNode pid' _ <- postNode uid pid (Node' NodeAnnuaire txt v [])
491 pids <- mkNodeR' (concat $ map (\n -> [childWith uid pid' n]) ns)
492 pure $ NewNode pid' pids
493 postNode _ _ (Node' _ _ _ _) = panic "TODO: postNode for this type not implemented yet"
496 childWith :: UserId -> ParentId -> Node' -> NodeWriteT
497 childWith uId pId (Node' NodeDocument txt v []) = node2table uId (Just pId) (Node' NodeDocument txt v [])
498 childWith uId pId (Node' NodeContact txt v []) = node2table uId (Just pId) (Node' NodeContact txt v [])
499 childWith _ _ (Node' _ _ _ _) = panic "This NodeType can not be a child"
502 -- TODO: remove hardcoded userId (with Reader)
503 -- TODO: user Reader in the API and adapt this function
507 mk :: Connection -> NodeType -> Maybe ParentId -> Text -> IO [Int]
508 mk c nt pId name = mk' c nt userId pId name
510 mk' :: Connection -> NodeType -> UserId -> Maybe ParentId -> Text -> IO [Int]
511 mk' c nt uId pId name = map fromIntegral <$> insertNodesWithParentR pId [node nt name hd pId uId] c
513 hd = HyperdataUser . Just . pack $ show EN
517 mk'' :: NodeType -> Maybe ParentId -> UserId -> Name -> Cmd [Int]
518 mk'' NodeUser Nothing uId name = mkCmd $ \c -> mk' c NodeUser uId Nothing name
519 mk'' NodeUser _ _ _ = panic "NodeUser do not have any parent"
520 mk'' _ Nothing _ _ = panic "NodeType does have a parent"
521 mk'' nt pId uId name = mkCmd $ \c -> mk' c nt uId pId name
524 mkRoot :: Username -> UserId -> Cmd [Int]
525 mkRoot uname uId = case uId > 0 of
526 False -> panic "UserId <= 0"
527 True -> mk'' NodeUser Nothing uId uname
529 mkCorpus :: Maybe Name -> Maybe HyperdataCorpus -> ParentId -> UserId -> Cmd [Int]
530 mkCorpus n h p u = insertNodesR' [nodeCorpusW n h p u]
532 mkList :: ParentId -> UserId -> Cmd [Int]
533 mkList p u = insertNodesR' [nodeListW Nothing Nothing p u]
535 mkGraph :: ParentId -> UserId -> Cmd [Int]
536 mkGraph p u = insertNodesR' [nodeGraphW Nothing Nothing p u]
538 mkDashboard :: ParentId -> UserId -> Cmd [Int]
539 mkDashboard p u = insertNodesR' [nodeDashboardW Nothing Nothing p u]
541 mkAnnuaire :: ParentId -> UserId -> Cmd [Int]
542 mkAnnuaire p u = insertNodesR' [nodeAnnuaireW Nothing Nothing p u]
544 -- | Default CorpusId Master and ListId Master