2 Module : Gargantext.API.Node
3 Description : Server API
4 Copyright : (c) CNRS, 2017-Present
5 License : AGPL + CECILL v3
6 Maintainer : team@gargantext.org
7 Stability : experimental
13 {-# OPTIONS_GHC -fno-warn-name-shadowing -fno-warn-orphans #-}
15 {-# LANGUAGE DataKinds #-}
16 {-# LANGUAGE DeriveGeneric #-}
17 {-# LANGUAGE FlexibleContexts #-}
18 {-# LANGUAGE NoImplicitPrelude #-}
19 {-# LANGUAGE OverloadedStrings #-}
20 {-# LANGUAGE RankNTypes #-}
21 {-# LANGUAGE TemplateHaskell #-}
22 {-# LANGUAGE TypeOperators #-}
24 -------------------------------------------------------------------
25 module Gargantext.API.Node
26 ( module Gargantext.API.Node
28 , HyperdataAnnuaire(..)
30 , HyperdataResource(..)
32 , HyperdataDocument(..)
33 , HyperdataDocumentV3(..)
35 -------------------------------------------------------------------
36 import Control.Lens (prism', set)
37 import Control.Monad.IO.Class (liftIO)
38 import Control.Monad ((>>))
39 --import System.IO (putStrLn, readFile)
41 import Data.Aeson (FromJSON, ToJSON)
42 import Data.Text (Text())
44 import Data.Time (UTCTime)
46 import GHC.Generics (Generic)
49 import Gargantext.API.Ngrams (TabType(..), TableNgramsApi, TableNgramsApiGet, tableNgramsPatch, getTableNgrams)
50 import Gargantext.Prelude
51 import Gargantext.Database.Types.Node
52 import Gargantext.Database.Utils (Cmd, CmdM)
53 import Gargantext.Database.Schema.Node ( getNodesWithParentId, getNode, deleteNode, deleteNodes, mk, JSONB, NodeError(..), HasNodeError(..))
54 import Gargantext.Database.Node.Children (getChildren)
55 import qualified Gargantext.Database.Node.Update as U (update, Update(..))
56 import Gargantext.Database.Facet (FacetDoc , runViewDocuments, OrderBy(..),FacetChart,runViewAuthorsDoc)
57 import Gargantext.Database.Tree (treeDB, HasTreeError(..), TreeError(..))
58 import Gargantext.Database.Schema.NodeNode (nodesToFavorite, nodesToTrash)
59 import Gargantext.API.Search ( SearchAPI, searchIn, SearchInQuery)
61 --import Gargantext.Text.Flow
62 import Gargantext.Viz.Graph hiding (Node)-- (Graph(_graph_metadata),LegendField(..), GraphMetadata(..),readGraphFromJson,defaultGraph)
63 -- import Gargantext.Core (Lang(..))
64 import Gargantext.Core.Types (Offset, Limit)
65 import Gargantext.Core.Types.Main (Tree, NodeTree)
66 import Gargantext.Database.Types.Node (CorpusId, ContactId)
67 -- import Gargantext.Text.Terms (TermType(..))
69 import Test.QuickCheck (elements)
70 import Test.QuickCheck.Arbitrary (Arbitrary, arbitrary)
72 type GargServer api = forall env m. CmdM env ServantErr m => ServerT api m
74 -------------------------------------------------------------------
75 -- | TODO : access by admin only
76 type NodesAPI = Delete '[JSON] Int
79 -- Be careful: really delete nodes
80 -- Access by admin only
81 nodesAPI :: [NodeId] -> GargServer NodesAPI
82 nodesAPI ids = deleteNodes ids
84 ------------------------------------------------------------------------
85 -- | TODO: access by admin only
86 -- To manager the Users roots
87 type Roots = Get '[JSON] [NodeAny]
88 :<|> Put '[JSON] Int -- TODO
90 -- | TODO: access by admin only
91 roots :: GargServer Roots
92 roots = (liftIO (putStrLn ( "/user" :: Text)) >> getNodesWithParentId 0 Nothing)
93 :<|> pure (panic "not implemented yet") -- TODO use patch map to update what we need
95 -------------------------------------------------------------------
96 -- | Node API Types management
97 -- TODO : access by users
98 type NodeAPI a = Get '[JSON] (Node a)
99 :<|> "rename" :> RenameApi
102 :<|> Delete '[JSON] Int
103 :<|> "children" :> ChildrenApi a
106 :<|> "table" :> TableApi
107 :<|> "list" :> TableNgramsApi
108 :<|> "listGet" :> TableNgramsApiGet
109 :<|> "pairing" :> PairingApi
111 :<|> "chart" :> ChartApi
112 :<|> "favorites" :> FavApi
113 :<|> "documents" :> DocsApi
114 :<|> "search":> Summary "Node Search"
115 :> ReqBody '[JSON] SearchInQuery
116 :> QueryParam "offset" Int
117 :> QueryParam "limit" Int
118 :> QueryParam "order" OrderBy
121 type RenameApi = Summary " Rename Node"
122 :> ReqBody '[JSON] RenameNode
125 type PostNodeApi = Summary " PostNode Node with ParentId as {id}"
126 :> ReqBody '[JSON] PostNode
127 :> Post '[JSON] [NodeId]
129 type ChildrenApi a = Summary " Summary children"
130 :> QueryParam "type" NodeType
131 :> QueryParam "offset" Int
132 :> QueryParam "limit" Int
133 :> Get '[JSON] [Node a]
134 ------------------------------------------------------------------------
135 -- TODO: make the NodeId type indexed by `a`, then we no longer need the proxy.
136 nodeAPI :: JSONB a => proxy a -> NodeId -> GargServer (NodeAPI a)
137 nodeAPI p id = getNode id p
142 :<|> getChildren id p
146 :<|> tableNgramsPatch id
147 :<|> getTableNgrams id
157 ------------------------------------------------------------------------
158 data RenameNode = RenameNode { r_name :: Text }
161 instance FromJSON RenameNode
162 instance ToJSON RenameNode
163 instance ToSchema RenameNode
164 instance Arbitrary RenameNode where
165 arbitrary = elements [RenameNode "test"]
166 ------------------------------------------------------------------------
167 data PostNode = PostNode { pn_name :: Text
168 , pn_typename :: NodeType}
171 instance FromJSON PostNode
172 instance ToJSON PostNode
173 instance ToSchema PostNode
174 instance Arbitrary PostNode where
175 arbitrary = elements [PostNode "Node test" NodeCorpus]
177 ------------------------------------------------------------------------
178 type DocsApi = Summary "Docs : Move to trash"
179 :> ReqBody '[JSON] Documents
180 :> Delete '[JSON] [Int]
182 data Documents = Documents { documents :: [NodeId]}
185 instance FromJSON Documents
186 instance ToJSON Documents
187 instance ToSchema Documents
189 delDocs :: CorpusId -> Documents -> Cmd err [Int]
190 delDocs cId ds = nodesToTrash $ map (\n -> (cId, n, True)) $ documents ds
192 ------------------------------------------------------------------------
193 type FavApi = Summary " Favorites label"
194 :> ReqBody '[JSON] Favorites
196 :<|> Summary " Favorites unlabel"
197 :> ReqBody '[JSON] Favorites
198 :> Delete '[JSON] [Int]
200 data Favorites = Favorites { favorites :: [NodeId]}
203 instance FromJSON Favorites
204 instance ToJSON Favorites
205 instance ToSchema Favorites
207 putFav :: CorpusId -> Favorites -> Cmd err [Int]
208 putFav cId fs = nodesToFavorite $ map (\n -> (cId, n, True)) $ favorites fs
210 delFav :: CorpusId -> Favorites -> Cmd err [Int]
211 delFav cId fs = nodesToFavorite $ map (\n -> (cId, n, False)) $ favorites fs
213 favApi :: CorpusId -> GargServer FavApi
214 favApi cId = putFav cId :<|> delFav cId
216 ------------------------------------------------------------------------
217 type TableApi = Summary " Table API"
218 :> QueryParam "view" TabType
219 :> QueryParam "offset" Int
220 :> QueryParam "limit" Int
221 :> QueryParam "order" OrderBy
222 :> Get '[JSON] [FacetDoc]
224 -- TODO adapt FacetDoc -> ListDoc (and add type of document as column)
225 type PairingApi = Summary " Pairing API"
226 :> QueryParam "view" TabType -- TODO change TabType -> DocType (CorpusId for pairing)
227 :> QueryParam "offset" Int
228 :> QueryParam "limit" Int
229 :> QueryParam "order" OrderBy
230 :> Get '[JSON] [FacetDoc]
232 ------------------------------------------------------------------------
233 type ChartApi = Summary " Chart API"
234 :> QueryParam "from" UTCTime
235 :> QueryParam "to" UTCTime
236 :> Get '[JSON] [FacetChart]
238 -- Depending on the Type of the Node, we could post
239 -- New documents for a corpus
240 -- New map list terms
241 -- :<|> "process" :> MultipartForm MultipartData :> Post '[JSON] Text
243 -- To launch a query and update the corpus
244 -- :<|> "query" :> Capture "string" Text :> Get '[JSON] Text
246 ------------------------------------------------------------------------
247 type GraphAPI = Get '[JSON] Graph
248 graphAPI :: NodeId -> GargServer GraphAPI
251 nodeGraph <- getNode nId HyperdataGraph
253 let title = "IMT - Scientific publications - 1982-2017 - English"
254 let metadata = GraphMetadata title [maybe 0 identity $ _node_parentId nodeGraph] [ LegendField 6 "#FFF" "Data processing"
255 , LegendField 7 "#FFF" "Networks"
256 , LegendField 1 "#FFF" "Material science"
257 , LegendField 5 "#FFF" "Energy / Environment"
259 -- (map (\n -> LegendField n "#FFFFFF" (pack $ show n)) [1..10])
261 graph <- set graph_metadata (Just metadata)
262 <$> maybe defaultGraph identity
263 <$> readGraphFromJson "purescript-gargantext/dist/examples/imtNew.json"
266 -- t <- textFlow (Mono EN) (Contexts contextText)
267 -- liftIO $ liftIO $ pure $ maybe t identity maybeGraph
268 -- TODO what do we get about the node? to replace contextText
270 instance HasNodeError ServantErr where
271 _NodeError = prism' mk (const Nothing) -- $ panic "HasNodeError ServantErr: not a prism")
274 mk NoListFound = err404 { errBody = e <> "No list found" }
275 mk NoRootFound = err404 { errBody = e <> "No Root found" }
276 mk NoCorpusFound = err404 { errBody = e <> "No Corpus found" }
277 mk NoUserFound = err404 { errBody = e <> "No User found" }
279 mk MkNode = err500 { errBody = e <> "Cannot mk node" }
280 mk NegativeId = err500 { errBody = e <> "Node with negative Id" }
281 mk UserNoParent = err500 { errBody = e <> "Should not have parent"}
282 mk HasParent = err500 { errBody = e <> "NodeType has parent" }
283 mk NotImplYet = err500 { errBody = e <> "Not implemented yet" }
284 mk ManyParents = err500 { errBody = e <> "Too many parents" }
285 mk ManyNodeUsers = err500 { errBody = e <> "Many userNode/user" }
287 -- TODO(orphan): There should be a proper APIError data type with a case TreeError.
288 instance HasTreeError ServantErr where
289 _TreeError = prism' mk (const Nothing) -- $ panic "HasTreeError ServantErr: not a prism")
292 mk NoRoot = err404 { errBody = e <> "Root node not found" }
293 mk EmptyRoot = err500 { errBody = e <> "Root node should not be empty" }
294 mk TooManyRoots = err500 { errBody = e <> "Too many root nodes" }
296 type TreeAPI = Get '[JSON] (Tree NodeTree)
297 treeAPI :: NodeId -> GargServer TreeAPI
300 ------------------------------------------------------------------------
301 -- | Check if the name is less than 255 char
302 rename :: NodeId -> RenameNode -> Cmd err [Int]
303 rename nId (RenameNode name) = U.update (U.Rename nId name)
305 getTable :: NodeId -> Maybe TabType
306 -> Maybe Offset -> Maybe Limit
307 -> Maybe OrderBy -> Cmd err [FacetDoc]
308 getTable cId ft o l order = case ft of
309 (Just Docs) -> runViewDocuments cId False o l order
310 (Just Trash) -> runViewDocuments cId True o l order
311 _ -> panic "not implemented"
313 getPairing :: ContactId -> Maybe TabType
314 -> Maybe Offset -> Maybe Limit
315 -> Maybe OrderBy -> Cmd err [FacetDoc]
316 getPairing cId ft o l order = case ft of
317 (Just Docs) -> runViewAuthorsDoc cId False o l order
318 (Just Trash) -> runViewAuthorsDoc cId True o l order
319 _ -> panic "not implemented"
322 getChart :: NodeId -> Maybe UTCTime -> Maybe UTCTime
323 -> Cmd err [FacetChart]
324 getChart _ _ _ = undefined -- TODO
326 postNode :: NodeId -> PostNode -> Cmd err [NodeId]
327 postNode pId (PostNode name nt) = mk nt (Just pId) name
329 putNode :: NodeId -> Cmd err Int
330 putNode = undefined -- TODO
332 query :: Monad m => Text -> m Text
337 -- TODO Is it possible to adapt the function according to iValue input ?
338 --upload :: MultipartData -> Handler Text
339 --upload multipartData = do
341 -- putStrLn "Inputs:"
342 -- forM_ (inputs multipartData) $ \input ->
343 -- putStrLn $ " " <> show (iName input)
344 -- <> " -> " <> show (iValue input)
346 -- forM_ (files multipartData) $ \file -> do
347 -- content <- readFile (fdFilePath file)
348 -- putStrLn $ "Content of " <> show (fdFileName file)
349 -- <> " at " <> fdFilePath file
351 -- pure (pack "Data loaded")