Commit abe0cda2 authored by Przemyslaw Kaminski's avatar Przemyslaw Kaminski

Merge branch 'dev' into dev-doc-annotation-issue

parents 942f8bef 2f9e26f5
Pipeline #1016 failed with stage
...@@ -13,9 +13,8 @@ Institute of Paris Île-de-France (ISC-PIF) and its partners. ...@@ -13,9 +13,8 @@ Institute of Paris Île-de-France (ISC-PIF) and its partners.
## Installation ## Installation
Disclaimer: this project is still on development, this is work in Disclaimer: this project is still in development, this is work in
progress. Please report and improve this documentation if you encounter progress. Please report and improve this documentation if you encounter issues.
issues.
### Build Core Code ### Build Core Code
...@@ -33,8 +32,7 @@ curl -sSL https://gitlab.iscpif.fr/gargantext/haskell-gargantext/raw/master/devo ...@@ -33,8 +32,7 @@ curl -sSL https://gitlab.iscpif.fr/gargantext/haskell-gargantext/raw/master/devo
### Add dependencies ### Add dependencies
1. CoreNLP is needed (EN and FR); This dependency will not be needed 1. CoreNLP is needed (EN and FR); This dependency will not be needed soon.
soon.
``` sh ``` sh
./devops/install-corenlp ./devops/install-corenlp
...@@ -69,9 +67,10 @@ Initialization schema should be loaded automatically (from `devops/postgres/sche ...@@ -69,9 +67,10 @@ Initialization schema should be loaded automatically (from `devops/postgres/sche
Change the passwords in gargantext.ini_toModify then move it: Change the passwords in gargantext.ini_toModify then move it:
``` sh
mv gargantext.ini_toModify gargantext.ini mv gargantext.ini_toModify gargantext.ini
```
(.gitignore avoids adding this file to the repository by mistake) (`.gitignore` avoids adding this file to the repository by mistake)
##### Run Gargantext ##### Run Gargantext
...@@ -104,6 +103,15 @@ docker run --rm -it -p 9000:9000 cgenie/corenlp-garg ...@@ -104,6 +103,15 @@ docker run --rm -it -p 9000:9000 cgenie/corenlp-garg
stack exec gargantext-import -- "corpusCsvHal" "user1" "IMT3" gargantext.ini 10000 ./1000.csv stack exec gargantext-import -- "corpusCsvHal" "user1" "IMT3" gargantext.ini 10000 ./1000.csv
``` ```
### Nix
It is also possible to build everything with [Nix](https://nixos.org/) instead of Docker:
``` sh
stack --nix build
stack --nix exec gargantext-import -- "corpusCsvHal" "user1" "IMT3" gargantext.ini 10000 ./1000.csv
stack --nix exec gargantext-server -- --ini gargantext.ini --run Prod
```
## Use Cases ## Use Cases
### Multi-User with Graphical User Interface (Server Mode) ### Multi-User with Graphical User Interface (Server Mode)
...@@ -112,12 +120,14 @@ stack exec gargantext-import -- "corpusCsvHal" "user1" "IMT3" gargantext.ini 100 ...@@ -112,12 +120,14 @@ stack exec gargantext-import -- "corpusCsvHal" "user1" "IMT3" gargantext.ini 100
~/.local/bin/stack --docker exec gargantext-server -- --ini "gargantext.ini" --run Prod ~/.local/bin/stack --docker exec gargantext-server -- --ini "gargantext.ini" --run Prod
``` ```
Then you can log in with `user1:1resu`. Then you can log in with `user1` / `1resu`.
### Command Line Mode tools ### Command Line Mode tools
#### Simple cooccurrences computation and indexation from a list of Ngrams #### Simple cooccurrences computation and indexation from a list of Ngrams
``` sh
stack --docker exec gargantext-cli -- CorpusFromGarg.csv ListFromGarg.csv Ouput.json stack --docker exec gargantext-cli -- CorpusFromGarg.csv ListFromGarg.csv Ouput.json
```
...@@ -10,7 +10,7 @@ SECRET_KEY = PASSWORD_TO_CHANGE ...@@ -10,7 +10,7 @@ SECRET_KEY = PASSWORD_TO_CHANGE
DATA_FILEPATH = FILEPATH_TO_CHANGE DATA_FILEPATH = FILEPATH_TO_CHANGE
# [external] # [external]
# FRAMES # FRAMES (i.e. iframe sources used in various places on the frontend)
FRAME_WRITE_URL = URL_TO_CHANGE FRAME_WRITE_URL = URL_TO_CHANGE
FRAME_CALC_URL = URL_TO_CHANGE FRAME_CALC_URL = URL_TO_CHANGE
......
name: gargantext name: gargantext
version: '0.0.1.7.3' version: '0.0.1.7.4'
synopsis: Search, map, share synopsis: Search, map, share
description: Please see README.md description: Please see README.md
category: Data category: Data
...@@ -40,6 +40,7 @@ library: ...@@ -40,6 +40,7 @@ library:
- Gargantext.API - Gargantext.API
- Gargantext.API.HashedResponse - Gargantext.API.HashedResponse
- Gargantext.API.Node - Gargantext.API.Node
- Gargantext.API.Node.File
- Gargantext.API.Admin.Settings - Gargantext.API.Admin.Settings
- Gargantext.API.Prelude - Gargantext.API.Prelude
- Gargantext.Core - Gargantext.Core
...@@ -161,6 +162,7 @@ library: ...@@ -161,6 +162,7 @@ library:
- located-base - located-base
- logging-effect - logging-effect
- matrix - matrix
- MissingH
- monad-control - monad-control
- monad-logger - monad-logger
- mtl - mtl
...@@ -208,6 +210,7 @@ library: ...@@ -208,6 +210,7 @@ library:
- servant-xml - servant-xml
- simple-reflect - simple-reflect
- singletons # (IGraph) - singletons # (IGraph)
- wai-app-static
# for mail # for mail
- smtp-mail - smtp-mail
......
...@@ -14,10 +14,10 @@ module Gargantext.API.HashedResponse where ...@@ -14,10 +14,10 @@ module Gargantext.API.HashedResponse where
import Data.Aeson import Data.Aeson
import Data.Swagger import Data.Swagger
import Data.Text (Text) import Data.Text (Text)
import GHC.Generics (Generic)
import Gargantext.Prelude import Gargantext.Prelude
import qualified Gargantext.Core.Crypto.Hash as Crypto (hash) import qualified Gargantext.Core.Crypto.Hash as Crypto (hash)
import GHC.Generics (Generic)
data HashedResponse a = HashedResponse { hash :: Text, value :: a } data HashedResponse a = HashedResponse { hash :: Text, value :: a }
deriving (Generic) deriving (Generic)
......
...@@ -36,9 +36,14 @@ import Data.Maybe ...@@ -36,9 +36,14 @@ import Data.Maybe
import Data.Swagger import Data.Swagger
import Data.Text (Text()) import Data.Text (Text())
import GHC.Generics (Generic) import GHC.Generics (Generic)
import Servant
import Test.QuickCheck (elements)
import Test.QuickCheck.Arbitrary (Arbitrary, arbitrary)
import Gargantext.API.Admin.Auth (withAccess, PathId(..)) import Gargantext.API.Admin.Auth (withAccess, PathId(..))
import Gargantext.API.Metrics import Gargantext.API.Metrics
import Gargantext.API.Ngrams (TabType(..), TableNgramsApi, apiNgramsTableCorpus) import Gargantext.API.Ngrams (TabType(..), TableNgramsApi, apiNgramsTableCorpus)
import Gargantext.API.Node.File
import Gargantext.API.Node.New import Gargantext.API.Node.New
import Gargantext.API.Prelude import Gargantext.API.Prelude
import Gargantext.API.Table import Gargantext.API.Table
...@@ -60,9 +65,6 @@ import Gargantext.Database.Query.Table.NodeNode ...@@ -60,9 +65,6 @@ import Gargantext.Database.Query.Table.NodeNode
import Gargantext.Database.Query.Tree (tree, TreeMode(..)) import Gargantext.Database.Query.Tree (tree, TreeMode(..))
import Gargantext.Prelude import Gargantext.Prelude
import Gargantext.Viz.Phylo.API (PhyloAPI, phyloAPI) import Gargantext.Viz.Phylo.API (PhyloAPI, phyloAPI)
import Servant
import Test.QuickCheck (elements)
import Test.QuickCheck.Arbitrary (Arbitrary, arbitrary)
import qualified Gargantext.API.Node.Share as Share import qualified Gargantext.API.Node.Share as Share
import qualified Gargantext.API.Node.Update as Update import qualified Gargantext.API.Node.Update as Update
import qualified Gargantext.API.Search as Search import qualified Gargantext.API.Search as Search
...@@ -147,6 +149,8 @@ type NodeAPI a = Get '[JSON] (Node a) ...@@ -147,6 +149,8 @@ type NodeAPI a = Get '[JSON] (Node a)
:<|> "move" :> MoveAPI :<|> "move" :> MoveAPI
:<|> "unpublish" :> Share.Unpublish :<|> "unpublish" :> Share.Unpublish
:<|> "file" :> FileApi
-- TODO-ACCESS: check userId CanRenameNode nodeId -- TODO-ACCESS: check userId CanRenameNode nodeId
-- TODO-EVENTS: NodeRenamed RenameNode or re-use some more general NodeEdited... -- TODO-EVENTS: NodeRenamed RenameNode or re-use some more general NodeEdited...
type RenameApi = Summary " Rename Node" type RenameApi = Summary " Rename Node"
...@@ -222,6 +226,8 @@ nodeAPI p uId id' = withAccess (Proxy :: Proxy (NodeAPI a)) Proxy uId (PathNode ...@@ -222,6 +226,8 @@ nodeAPI p uId id' = withAccess (Proxy :: Proxy (NodeAPI a)) Proxy uId (PathNode
-- :<|> postUpload id' -- :<|> postUpload id'
:<|> Share.unPublish id' :<|> Share.unPublish id'
:<|> fileApi uId id'
------------------------------------------------------------------------ ------------------------------------------------------------------------
data RenameNode = RenameNode { r_name :: Text } data RenameNode = RenameNode { r_name :: Text }
......
...@@ -22,10 +22,14 @@ module Gargantext.API.Node.Corpus.New ...@@ -22,10 +22,14 @@ module Gargantext.API.Node.Corpus.New
import Control.Lens hiding (elements, Empty) import Control.Lens hiding (elements, Empty)
import Data.Aeson import Data.Aeson
import Data.Aeson.TH (deriveJSON) import Data.Aeson.TH (deriveJSON)
import qualified Data.ByteString as BS
import qualified Data.ByteString.Base64 as BSB64
import Data.Either import Data.Either
import Data.Maybe (fromMaybe) import Data.Maybe (fromMaybe)
import Data.Swagger import Data.Swagger
import Data.Text (Text) import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import GHC.Generics (Generic) import GHC.Generics (Generic)
import Servant import Servant
import Servant.Job.Core import Servant.Job.Core
...@@ -36,16 +40,24 @@ import Servant.Job.Utils (jsonOptions) ...@@ -36,16 +40,24 @@ import Servant.Job.Utils (jsonOptions)
import Test.QuickCheck.Arbitrary import Test.QuickCheck.Arbitrary
import Web.FormUrlEncoded (FromForm) import Web.FormUrlEncoded (FromForm)
import Gargantext.Prelude
import Gargantext.API.Admin.Orchestrator.Types (JobLog(..)) import Gargantext.API.Admin.Orchestrator.Types (JobLog(..))
import qualified Gargantext.API.Admin.Orchestrator.Types as T import qualified Gargantext.API.Admin.Orchestrator.Types as T
import Gargantext.API.Admin.Settings (HasSettings)
import Gargantext.API.Node.Corpus.New.File import Gargantext.API.Node.Corpus.New.File
import Gargantext.Core (Lang(..){-, allLangs-}) import Gargantext.Core (Lang(..){-, allLangs-})
import Gargantext.Core.Types.Individu (User(..)) import Gargantext.Core.Types.Individu (User(..))
import Gargantext.Core.Utils.Prefix (unPrefix, unPrefixSwagger) import Gargantext.Core.Utils.Prefix (unPrefix, unPrefixSwagger)
import Gargantext.Database.Action.Flow (FlowCmdM, flowCorpus, getDataText, flowDataText, TermType(..), DataOrigin(..){-, allDataOrigins-}) import Gargantext.Database.Action.Flow (FlowCmdM, flowCorpus, getDataText, flowDataText, TermType(..), DataOrigin(..){-, allDataOrigins-})
import Gargantext.Database.Action.Flow.Utils (getUserId)
import Gargantext.Database.Action.Node (mkNodeWithParent)
import Gargantext.Database.Admin.Types.Hyperdata import Gargantext.Database.Admin.Types.Hyperdata
import Gargantext.Database.Admin.Types.Node (CorpusId, UserId) import Gargantext.Database.Admin.Types.Node (CorpusId, NodeType(..), UserId)
import Gargantext.Prelude import Gargantext.Database.Query.Table.Node (getNodeWith)
import Gargantext.Database.Query.Table.Node.UpdateOpaleye (updateHyperdata)
import Gargantext.Database.Schema.Node (node_hyperdata)
import qualified Gargantext.Prelude.Utils as GPU
import qualified Gargantext.Text.Corpus.API as API import qualified Gargantext.Text.Corpus.API as API
import qualified Gargantext.Text.Corpus.Parsers as Parser (FileFormat(..), parseFormat) import qualified Gargantext.Text.Corpus.Parsers as Parser (FileFormat(..), parseFormat)
...@@ -166,6 +178,31 @@ instance FromJSON NewWithForm where ...@@ -166,6 +178,31 @@ instance FromJSON NewWithForm where
instance ToSchema NewWithForm where instance ToSchema NewWithForm where
declareNamedSchema = genericDeclareNamedSchema (unPrefixSwagger "_wf_") declareNamedSchema = genericDeclareNamedSchema (unPrefixSwagger "_wf_")
-------------------------------------------------------
data NewWithFile = NewWithFile
{ _wfi_b64_data :: !Text
, _wfi_lang :: !(Maybe Lang)
, _wfi_name :: !Text
} deriving (Eq, Show, Generic)
makeLenses ''NewWithFile
instance FromForm NewWithFile
instance FromJSON NewWithFile where
parseJSON = genericParseJSON $ jsonOptions "_wfi_"
instance ToSchema NewWithFile where
declareNamedSchema = genericDeclareNamedSchema (unPrefixSwagger "_wfi_")
instance GPU.SaveFile NewWithFile where
saveFile' fp (NewWithFile b64d _ _) = do
let eDecoded = BSB64.decode $ TE.encodeUtf8 b64d
case eDecoded of
Left err -> panic $ T.pack $ "Error decoding: " <> err
Right decoded -> BS.writeFile fp decoded
-- BS.writeFile fp $ BSB64.decodeLenient $ TE.encodeUtf8 b64d
--instance GPU.ReadFile NewWithFile where
-- readFile' = TIO.readFile
------------------------------------------------------------------------ ------------------------------------------------------------------------
type AsyncJobs event ctI input output = type AsyncJobs event ctI input output =
AsyncJobsAPI' 'Unsafe 'Safe ctI '[JSON] Maybe event input output AsyncJobsAPI' 'Unsafe 'Safe ctI '[JSON] Maybe event input output
...@@ -189,14 +226,6 @@ type AddWithFile = Summary "Add with MultipartData to corpus endpoint" ...@@ -189,14 +226,6 @@ type AddWithFile = Summary "Add with MultipartData to corpus endpoint"
:> AsyncJobs JobLog '[JSON] () JobLog :> AsyncJobs JobLog '[JSON] () JobLog
-} -}
type AddWithForm = Summary "Add with FormUrlEncoded to corpus endpoint"
:> "corpus"
:> Capture "corpus_id" CorpusId
:> "add"
:> "form"
:> "async"
:> AsyncJobs JobLog '[FormUrlEncoded] NewWithForm JobLog
------------------------------------------------------------------------ ------------------------------------------------------------------------
-- TODO WithQuery also has a corpus id -- TODO WithQuery also has a corpus id
...@@ -209,10 +238,10 @@ addToCorpusWithQuery :: FlowCmdM env err m ...@@ -209,10 +238,10 @@ addToCorpusWithQuery :: FlowCmdM env err m
addToCorpusWithQuery u cid (WithQuery q dbs l _nid) logStatus = do addToCorpusWithQuery u cid (WithQuery q dbs l _nid) logStatus = do
-- TODO ... -- TODO ...
logStatus JobLog { _scst_succeeded = Just 0 logStatus JobLog { _scst_succeeded = Just 0
, _scst_failed = Just 0 , _scst_failed = Just 0
, _scst_remaining = Just 5 , _scst_remaining = Just 5
, _scst_events = Just [] , _scst_events = Just []
} }
printDebug "addToCorpusWithQuery" (cid, dbs) printDebug "addToCorpusWithQuery" (cid, dbs)
-- TODO add cid -- TODO add cid
-- TODO if cid is folder -> create Corpus -- TODO if cid is folder -> create Corpus
...@@ -221,19 +250,28 @@ addToCorpusWithQuery u cid (WithQuery q dbs l _nid) logStatus = do ...@@ -221,19 +250,28 @@ addToCorpusWithQuery u cid (WithQuery q dbs l _nid) logStatus = do
txts <- mapM (\db -> getDataText db (Multi l) q Nothing) [database2origin dbs] txts <- mapM (\db -> getDataText db (Multi l) q Nothing) [database2origin dbs]
logStatus JobLog { _scst_succeeded = Just 2 logStatus JobLog { _scst_succeeded = Just 2
, _scst_failed = Just 0 , _scst_failed = Just 0
, _scst_remaining = Just 1 , _scst_remaining = Just 1
, _scst_events = Just [] , _scst_events = Just []
} }
cids <- mapM (\txt -> flowDataText u txt (Multi l) cid) txts cids <- mapM (\txt -> flowDataText u txt (Multi l) cid) txts
printDebug "corpus id" cids printDebug "corpus id" cids
-- TODO ... -- TODO ...
pure JobLog { _scst_succeeded = Just 3 pure JobLog { _scst_succeeded = Just 3
, _scst_failed = Just 0 , _scst_failed = Just 0
, _scst_remaining = Just 0 , _scst_remaining = Just 0
, _scst_events = Just [] , _scst_events = Just []
} }
type AddWithForm = Summary "Add with FormUrlEncoded to corpus endpoint"
:> "corpus"
:> Capture "corpus_id" CorpusId
:> "add"
:> "form"
:> "async"
:> AsyncJobs JobLog '[FormUrlEncoded] NewWithForm JobLog
addToCorpusWithForm :: FlowCmdM env err m addToCorpusWithForm :: FlowCmdM env err m
=> User => User
...@@ -243,12 +281,13 @@ addToCorpusWithForm :: FlowCmdM env err m ...@@ -243,12 +281,13 @@ addToCorpusWithForm :: FlowCmdM env err m
-> m JobLog -> m JobLog
addToCorpusWithForm user cid (NewWithForm ft d l _n) logStatus = do addToCorpusWithForm user cid (NewWithForm ft d l _n) logStatus = do
printDebug "Parsing corpus: " cid printDebug "[addToCorpusWithForm] Parsing corpus: " cid
printDebug "[addToCorpusWithForm] fileType" ft
logStatus JobLog { _scst_succeeded = Just 0 logStatus JobLog { _scst_succeeded = Just 0
, _scst_failed = Just 0 , _scst_failed = Just 0
, _scst_remaining = Just 2 , _scst_remaining = Just 2
, _scst_events = Just [] , _scst_events = Just []
} }
let let
parse = case ft of parse = case ft of
CSV_HAL -> Parser.parseFormat Parser.CsvHal CSV_HAL -> Parser.parseFormat Parser.CsvHal
...@@ -263,10 +302,10 @@ addToCorpusWithForm user cid (NewWithForm ft d l _n) logStatus = do ...@@ -263,10 +302,10 @@ addToCorpusWithForm user cid (NewWithForm ft d l _n) logStatus = do
printDebug "Parsing corpus finished : " cid printDebug "Parsing corpus finished : " cid
logStatus JobLog { _scst_succeeded = Just 1 logStatus JobLog { _scst_succeeded = Just 1
, _scst_failed = Just 0 , _scst_failed = Just 0
, _scst_remaining = Just 1 , _scst_remaining = Just 1
, _scst_events = Just [] , _scst_events = Just []
} }
printDebug "Starting extraction : " cid printDebug "Starting extraction : " cid
...@@ -278,10 +317,10 @@ addToCorpusWithForm user cid (NewWithForm ft d l _n) logStatus = do ...@@ -278,10 +317,10 @@ addToCorpusWithForm user cid (NewWithForm ft d l _n) logStatus = do
printDebug "Extraction finished : " cid printDebug "Extraction finished : " cid
pure JobLog { _scst_succeeded = Just 2 pure JobLog { _scst_succeeded = Just 2
, _scst_failed = Just 0 , _scst_failed = Just 0
, _scst_remaining = Just 0 , _scst_remaining = Just 0
, _scst_events = Just [] , _scst_events = Just []
} }
{- {-
addToCorpusWithFile :: FlowCmdM env err m addToCorpusWithFile :: FlowCmdM env err m
...@@ -307,3 +346,49 @@ addToCorpusWithFile cid input filetype logStatus = do ...@@ -307,3 +346,49 @@ addToCorpusWithFile cid input filetype logStatus = do
-} -}
type AddWithFile = Summary "Add with FileUrlEncoded to corpus endpoint"
:> "corpus"
:> Capture "corpus_id" CorpusId
:> "add"
:> "file"
:> "async"
:> AsyncJobs JobLog '[FormUrlEncoded] NewWithFile JobLog
addToCorpusWithFile :: (HasSettings env, FlowCmdM env err m)
=> User
-> CorpusId
-> NewWithFile
-> (JobLog -> m ())
-> m JobLog
addToCorpusWithFile user cid nwf@(NewWithFile _d _l fName) logStatus = do
printDebug "[addToCorpusWithFile] Uploading file to corpus: " cid
logStatus JobLog { _scst_succeeded = Just 0
, _scst_failed = Just 0
, _scst_remaining = Just 1
, _scst_events = Just []
}
fPath <- GPU.writeFile nwf
printDebug "[addToCorpusWithFile] File saved as: " fPath
uId <- getUserId user
nIds <- mkNodeWithParent NodeFile (Just cid) uId fName
_ <- case nIds of
[nId] -> do
node <- getNodeWith nId (Proxy :: Proxy HyperdataFile)
let hl = node ^. node_hyperdata
_ <- updateHyperdata nId $ hl { _hff_name = fName
, _hff_path = T.pack fPath }
printDebug "[addToCorpusWithFile] Created node with id: " nId
_ -> pure ()
printDebug "[addToCorpusWithFile] File upload to corpus finished: " cid
pure $ JobLog { _scst_succeeded = Just 1
, _scst_failed = Just 0
, _scst_remaining = Just 0
, _scst_events = Just []
}
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE TypeOperators #-}
{-# OPTIONS_GHC -fno-warn-orphans -fno-warn-unused-matches -fno-warn-unused-imports #-}
module Gargantext.API.Node.File where
import Control.Lens ((^.))
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as BSL
import qualified Data.MIME.Types as DMT
import Data.Monoid (mempty)
import Data.Swagger
import Data.Text
import Data.Text.Encoding
import qualified Data.Text.IO as TIO
import GHC.Generics (Generic)
import qualified Network.HTTP.Media as M
import Network.Wai.Application.Static
import Servant
import Servant.API.Raw (Raw)
import Servant.Server.Internal
import Gargantext.Prelude
import qualified Gargantext.Prelude.Utils as GPU
import Gargantext.API.Admin.Settings (HasSettings)
import Gargantext.API.Prelude
import Gargantext.Database.Action.Flow.Types
import Gargantext.Database.Admin.Types.Hyperdata.File
import Gargantext.Database.Admin.Types.Node
import Gargantext.Database.Query.Table.Node (getNodeWith)
import Gargantext.Database.Schema.Node (node_hyperdata)
data RESPONSE deriving Typeable
instance Accept RESPONSE where
contentType _ = "text" M.// "*"
instance MimeRender RESPONSE BSResponse where
mimeRender _ (BSResponse val) = BSL.fromStrict $ val
type FileApi = Summary "File download"
:> "download"
:> Get '[RESPONSE] (Headers '[Servant.Header "Content-Type" Text] BSResponse)
fileApi :: UserId -> NodeId -> GargServer FileApi
fileApi uId nId = fileDownload uId nId
newtype Contents = Contents BS.ByteString
instance GPU.ReadFile Contents where
readFile' fp = do
c <- BS.readFile fp
pure $ Contents c
newtype BSResponse = BSResponse BS.ByteString
deriving (Generic)
instance ToSchema BSResponse where
declareNamedSchema _ = declareNamedSchema (Proxy :: Proxy BSResponse)
fileDownload :: (HasSettings env, FlowCmdM env err m)
=> UserId
-> NodeId
-> m (Headers '[Servant.Header "Content-Type" Text] BSResponse)
fileDownload uId nId = do
printDebug "[fileDownload] uId" uId
printDebug "[fileDownload] nId" nId
node <- getNodeWith nId (Proxy :: Proxy HyperdataFile)
let (HyperdataFile { _hff_name = name'
, _hff_path = path }) = node ^. node_hyperdata
Contents c <- GPU.readFile $ unpack path
let (mMime, _) = DMT.guessType DMT.defaultmtd False $ unpack name'
mime = case mMime of
Just m -> m
Nothing -> "text/plain"
pure $ addHeader (pack mime) $ BSResponse c
--pure c
-- let settings = embeddedSettings [("", encodeUtf8 c)]
-- Tagged $ staticApp settings
-- let settings = embeddedSettings [("", "hello")]
-- Tagged $ staticApp settings
...@@ -24,6 +24,12 @@ import Data.Aeson ...@@ -24,6 +24,12 @@ import Data.Aeson
import Data.Swagger import Data.Swagger
import Data.Text (Text) import Data.Text (Text)
import GHC.Generics (Generic) import GHC.Generics (Generic)
import Servant
import Servant.Job.Async
import Test.QuickCheck (elements)
import Test.QuickCheck.Arbitrary
import Web.FormUrlEncoded (FromForm)
import Gargantext.API.Admin.Orchestrator.Types (JobLog(..)) import Gargantext.API.Admin.Orchestrator.Types (JobLog(..))
import Gargantext.API.Node.Corpus.New (AsyncJobs) import Gargantext.API.Node.Corpus.New (AsyncJobs)
import Gargantext.API.Prelude import Gargantext.API.Prelude
...@@ -35,11 +41,6 @@ import Gargantext.Database.Query.Table.Node.Error (HasNodeError(..)) ...@@ -35,11 +41,6 @@ import Gargantext.Database.Query.Table.Node.Error (HasNodeError(..))
import Gargantext.Database.Query.Table.Node.User import Gargantext.Database.Query.Table.Node.User
import Gargantext.Database.Schema.Node import Gargantext.Database.Schema.Node
import Gargantext.Prelude import Gargantext.Prelude
import Servant
import Servant.Job.Async
import Test.QuickCheck (elements)
import Test.QuickCheck.Arbitrary
import Web.FormUrlEncoded (FromForm)
------------------------------------------------------------------------ ------------------------------------------------------------------------
data PostNode = PostNode { pn_name :: Text data PostNode = PostNode { pn_name :: Text
...@@ -87,25 +88,25 @@ postNodeAsync uId nId (PostNode nodeName tn) logStatus = do ...@@ -87,25 +88,25 @@ postNodeAsync uId nId (PostNode nodeName tn) logStatus = do
printDebug "postNodeAsync" nId printDebug "postNodeAsync" nId
logStatus JobLog { _scst_succeeded = Just 1 logStatus JobLog { _scst_succeeded = Just 1
, _scst_failed = Just 0 , _scst_failed = Just 0
, _scst_remaining = Just 2 , _scst_remaining = Just 2
, _scst_events = Just [] , _scst_events = Just []
} }
nodeUser <- getNodeUser (NodeId uId) nodeUser <- getNodeUser (NodeId uId)
-- _ <- threadDelay 1000 -- _ <- threadDelay 1000
logStatus JobLog { _scst_succeeded = Just 1 logStatus JobLog { _scst_succeeded = Just 1
, _scst_failed = Just 0 , _scst_failed = Just 0
, _scst_remaining = Just 2 , _scst_remaining = Just 2
, _scst_events = Just [] , _scst_events = Just []
} }
let uId' = nodeUser ^. node_userId let uId' = nodeUser ^. node_userId
_ <- mkNodeWithParent tn (Just nId) uId' nodeName _ <- mkNodeWithParent tn (Just nId) uId' nodeName
pure JobLog { _scst_succeeded = Just 3 pure JobLog { _scst_succeeded = Just 3
, _scst_failed = Just 0 , _scst_failed = Just 0
, _scst_remaining = Just 0 , _scst_remaining = Just 0
, _scst_events = Just [] , _scst_events = Just []
} }
...@@ -144,7 +144,8 @@ type GargPrivateAPI' = ...@@ -144,7 +144,8 @@ type GargPrivateAPI' =
:> TreeAPI :> TreeAPI
-- :<|> New.Upload -- :<|> New.Upload
:<|> New.AddWithForm :<|> New.AddWithForm
:<|> New.AddWithFile
:<|> New.AddWithQuery :<|> New.AddWithQuery
-- :<|> "annuaire" :> Annuaire.AddWithForm -- :<|> "annuaire" :> Annuaire.AddWithForm
...@@ -222,6 +223,7 @@ serverPrivateGargAPI' (AuthenticatedUser (NodeId uid)) ...@@ -222,6 +223,7 @@ serverPrivateGargAPI' (AuthenticatedUser (NodeId uid))
<$> PathNode <*> treeAPI <$> PathNode <*> treeAPI
-- TODO access -- TODO access
:<|> addCorpusWithForm (RootId (NodeId uid)) :<|> addCorpusWithForm (RootId (NodeId uid))
:<|> addCorpusWithFile (RootId (NodeId uid))
:<|> addCorpusWithQuery (RootId (NodeId uid)) :<|> addCorpusWithQuery (RootId (NodeId uid))
-- :<|> addAnnuaireWithForm -- :<|> addAnnuaireWithForm
...@@ -271,6 +273,16 @@ addCorpusWithForm user cid = ...@@ -271,6 +273,16 @@ addCorpusWithForm user cid =
liftBase $ log x liftBase $ log x
in New.addToCorpusWithForm user cid i log') in New.addToCorpusWithForm user cid i log')
addCorpusWithFile :: User -> GargServer New.AddWithFile
addCorpusWithFile user cid =
serveJobsAPI $
JobFunction (\i log ->
let
log' x = do
printDebug "addToCorpusWithFile" x
liftBase $ log x
in New.addToCorpusWithFile user cid i log')
addAnnuaireWithForm :: GargServer Annuaire.AddWithForm addAnnuaireWithForm :: GargServer Annuaire.AddWithForm
addAnnuaireWithForm cid = addAnnuaireWithForm cid =
serveJobsAPI $ serveJobsAPI $
......
...@@ -58,7 +58,7 @@ readConfig fp = do ...@@ -58,7 +58,7 @@ readConfig fp = do
defaultConfig :: GargConfig defaultConfig :: GargConfig
defaultConfig = GargConfig "gargantua" defaultConfig = GargConfig "gargantua"
"secret" "secret"
"data/" "data"
"https://frame_write.url" "https://frame_write.url"
"https://frame_calc.url" "https://frame_calc.url"
"https://frame_searx.url" "https://frame_searx.url"
......
...@@ -17,34 +17,53 @@ TODO: NodeError ...@@ -17,34 +17,53 @@ TODO: NodeError
module Gargantext.Database.Action.Delete module Gargantext.Database.Action.Delete
where where
import Control.Lens (view, (^.))
import Data.Text
import Servant
import Gargantext.API.Admin.Settings
import Gargantext.Core.Types.Individu (User(..)) import Gargantext.Core.Types.Individu (User(..))
import Gargantext.Database.Action.Flow.Utils (getUserId) import Gargantext.Database.Action.Flow.Utils (getUserId)
import Gargantext.Database.Admin.Config (hasNodeType) import Gargantext.Database.Action.Share (delFolderTeam)
import Gargantext.Database.Admin.Types.Node (NodeId) import Gargantext.Database.Admin.Config (nodeTypeId)
import Gargantext.Database.Admin.Types.Hyperdata.File
import Gargantext.Database.Admin.Types.Node -- (NodeType(..)) import Gargantext.Database.Admin.Types.Node -- (NodeType(..))
import Gargantext.Database.Prelude (Cmd) import Gargantext.Database.Prelude (Cmd', HasConfig, HasConnectionPool)
import qualified Gargantext.Database.Query.Table.Node as N (getNode, deleteNode)
import Gargantext.Database.Query.Table.Node (getNodeWith)
import Gargantext.Database.Query.Table.Node.Error (HasNodeError) import Gargantext.Database.Query.Table.Node.Error (HasNodeError)
import Gargantext.Database.Schema.Node import Gargantext.Database.Schema.Node
import Gargantext.Prelude import Gargantext.Prelude
import qualified Gargantext.Database.Query.Table.Node as N (getNode, deleteNode) import qualified Gargantext.Prelude.Utils as GPU
import Gargantext.Database.Action.Share (delFolderTeam)
------------------------------------------------------------------------ ------------------------------------------------------------------------
deleteNode :: HasNodeError err
deleteNode :: (HasConfig env, HasConnectionPool env, HasNodeError err, HasSettings env)
=> User => User
-> NodeId -> NodeId
-> Cmd err Int -> Cmd' env err Int
deleteNode u nodeId = do deleteNode u nodeId = do
node' <- N.getNode nodeId node' <- N.getNode nodeId
if hasNodeType node' NodeUser case (view node_typename node') of
then panic "Not allowed to delete NodeUser (yet)" nt | nt == nodeTypeId NodeUser -> panic "Not allowed to delete NodeUser (yet)"
else if hasNodeType node' NodeTeam nt | nt == nodeTypeId NodeTeam -> do
then do uId <- getUserId u
uId <- getUserId u if _node_userId node' == uId
if _node_userId node' == uId then N.deleteNode nodeId
then N.deleteNode nodeId else delFolderTeam u nodeId
else delFolderTeam u nodeId nt | nt == nodeTypeId NodeFile -> do
else N.deleteNode nodeId node <- getNodeWith nodeId (Proxy :: Proxy HyperdataFile)
let (HyperdataFile { _hff_path = path }) = node ^. node_hyperdata
GPU.removeFile $ unpack path
N.deleteNode nodeId
_ -> N.deleteNode nodeId
-- if hasNodeType node' NodeUser
-- then panic "Not allowed to delete NodeUser (yet)"
-- else if hasNodeType node' NodeTeam
-- then do
-- uId <- getUserId u
-- if _node_userId node' == uId
-- then N.deleteNode nodeId
-- else delFolderTeam u nodeId
-- else N.deleteNode nodeId
...@@ -66,6 +66,8 @@ nodeTypeId n = ...@@ -66,6 +66,8 @@ nodeTypeId n =
NodeDashboard -> 71 NodeDashboard -> 71
-- NodeNoteBook -> 88 -- NodeNoteBook -> 88
NodeFile -> 101
NodeFrameWrite -> 991 NodeFrameWrite -> 991
NodeFrameCalc -> 992 NodeFrameCalc -> 992
......
...@@ -17,6 +17,7 @@ module Gargantext.Database.Admin.Types.Hyperdata ...@@ -17,6 +17,7 @@ module Gargantext.Database.Admin.Types.Hyperdata
, module Gargantext.Database.Admin.Types.Hyperdata.Corpus , module Gargantext.Database.Admin.Types.Hyperdata.Corpus
, module Gargantext.Database.Admin.Types.Hyperdata.Dashboard , module Gargantext.Database.Admin.Types.Hyperdata.Dashboard
, module Gargantext.Database.Admin.Types.Hyperdata.Document , module Gargantext.Database.Admin.Types.Hyperdata.Document
, module Gargantext.Database.Admin.Types.Hyperdata.File
, module Gargantext.Database.Admin.Types.Hyperdata.Folder , module Gargantext.Database.Admin.Types.Hyperdata.Folder
, module Gargantext.Database.Admin.Types.Hyperdata.Frame , module Gargantext.Database.Admin.Types.Hyperdata.Frame
, module Gargantext.Database.Admin.Types.Hyperdata.List , module Gargantext.Database.Admin.Types.Hyperdata.List
...@@ -34,6 +35,7 @@ import Gargantext.Database.Admin.Types.Hyperdata.Contact ...@@ -34,6 +35,7 @@ import Gargantext.Database.Admin.Types.Hyperdata.Contact
import Gargantext.Database.Admin.Types.Hyperdata.Corpus import Gargantext.Database.Admin.Types.Hyperdata.Corpus
import Gargantext.Database.Admin.Types.Hyperdata.Dashboard import Gargantext.Database.Admin.Types.Hyperdata.Dashboard
import Gargantext.Database.Admin.Types.Hyperdata.Document import Gargantext.Database.Admin.Types.Hyperdata.Document
import Gargantext.Database.Admin.Types.Hyperdata.File
import Gargantext.Database.Admin.Types.Hyperdata.Folder import Gargantext.Database.Admin.Types.Hyperdata.Folder
import Gargantext.Database.Admin.Types.Hyperdata.Frame import Gargantext.Database.Admin.Types.Hyperdata.Frame
import Gargantext.Database.Admin.Types.Hyperdata.List import Gargantext.Database.Admin.Types.Hyperdata.List
......
...@@ -53,6 +53,8 @@ data DefaultHyperdata = ...@@ -53,6 +53,8 @@ data DefaultHyperdata =
| DefaultFrameWrite HyperdataFrame | DefaultFrameWrite HyperdataFrame
| DefaultFrameCalc HyperdataFrame | DefaultFrameCalc HyperdataFrame
| DefaultFile HyperdataFile
instance Hyperdata DefaultHyperdata instance Hyperdata DefaultHyperdata
instance ToJSON DefaultHyperdata where instance ToJSON DefaultHyperdata where
...@@ -82,6 +84,8 @@ instance ToJSON DefaultHyperdata where ...@@ -82,6 +84,8 @@ instance ToJSON DefaultHyperdata where
toJSON (DefaultFrameWrite x) = toJSON x toJSON (DefaultFrameWrite x) = toJSON x
toJSON (DefaultFrameCalc x) = toJSON x toJSON (DefaultFrameCalc x) = toJSON x
toJSON (DefaultFile x) = toJSON x
defaultHyperdata :: NodeType -> DefaultHyperdata defaultHyperdata :: NodeType -> DefaultHyperdata
defaultHyperdata NodeUser = DefaultUser defaultHyperdataUser defaultHyperdata NodeUser = DefaultUser defaultHyperdataUser
...@@ -109,3 +113,5 @@ defaultHyperdata NodeDashboard = DefaultDashboard defaultHyperdataDashboard ...@@ -109,3 +113,5 @@ defaultHyperdata NodeDashboard = DefaultDashboard defaultHyperdataDashboard
defaultHyperdata NodeFrameWrite = DefaultFrameWrite defaultHyperdataFrame defaultHyperdata NodeFrameWrite = DefaultFrameWrite defaultHyperdataFrame
defaultHyperdata NodeFrameCalc = DefaultFrameCalc defaultHyperdataFrame defaultHyperdata NodeFrameCalc = DefaultFrameCalc defaultHyperdataFrame
defaultHyperdata NodeFile = DefaultFile defaultHyperdataFile
{-|
Module : Gargantext.Database.Admin.Types.Hyperdata.File
Description :
Copyright : (c) CNRS, 2017-Present
License : AGPL + CECILL v3
Maintainer : team@gargantext.org
Stability : experimental
Portability : POSIX
-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TemplateHaskell #-}
module Gargantext.Database.Admin.Types.Hyperdata.File
where
import Gargantext.Prelude
import Gargantext.Database.Admin.Types.Hyperdata.Prelude
------------------------------------------------------------------------
data HyperdataFile =
HyperdataFile { _hff_name :: !Text
, _hff_path :: !Text
, _hff_mime :: !Text
}
deriving (Generic)
defaultHyperdataFile :: HyperdataFile
defaultHyperdataFile = HyperdataFile "" "" ""
------------------------------------------------------------------------
-- Instances
------------------------------------------------------------------------
-- | Specific Gargantext instance
instance Hyperdata HyperdataFile
makeLenses ''HyperdataFile
-- | All Json instances
$(deriveJSON (unPrefix "_hff_") ''HyperdataFile)
-- | Arbitrary instances for tests
instance Arbitrary HyperdataFile where
arbitrary = pure defaultHyperdataFile
instance FromField HyperdataFile
where
fromField = fromField'
instance QueryRunnerColumnDefault PGJsonb HyperdataFile
where
queryRunnerColumnDefault = fieldQueryRunnerColumn
instance ToSchema HyperdataFile where
declareNamedSchema proxy =
genericDeclareNamedSchema (unPrefixSwagger "_hff_") proxy
& mapped.schema.description ?~ "File Hyperdata"
& mapped.schema.example ?~ toJSON defaultHyperdataFile
...@@ -259,6 +259,7 @@ data NodeType = NodeUser ...@@ -259,6 +259,7 @@ data NodeType = NodeUser
-- Optional Nodes -- Optional Nodes
| NodeFrameWrite | NodeFrameCalc | NodeFrameWrite | NodeFrameCalc
| NodeFile
deriving (Show, Read, Eq, Generic, Bounded, Enum) deriving (Show, Read, Eq, Generic, Bounded, Enum)
...@@ -293,6 +294,8 @@ defaultName NodeDashboard = "Dashboard" ...@@ -293,6 +294,8 @@ defaultName NodeDashboard = "Dashboard"
defaultName NodeFrameWrite = "Frame Write" defaultName NodeFrameWrite = "Frame Write"
defaultName NodeFrameCalc = "Frame Calc" defaultName NodeFrameCalc = "Frame Calc"
defaultName NodeFile = "File"
instance FromJSON NodeType instance FromJSON NodeType
instance ToJSON NodeType instance ToJSON NodeType
......
...@@ -14,22 +14,25 @@ Portability : POSIX ...@@ -14,22 +14,25 @@ Portability : POSIX
module Gargantext.Prelude.Utils module Gargantext.Prelude.Utils
where where
import Control.Exception
import Control.Lens (view) import Control.Lens (view)
import Control.Monad.Reader (ask, MonadReader)
import Control.Monad.Random.Class (MonadRandom) import Control.Monad.Random.Class (MonadRandom)
import Control.Monad.Reader (MonadReader)
import Control.Monad.Reader (ask)
import Data.Text (Text) import Data.Text (Text)
import qualified Data.Text as Text
import GHC.IO (FilePath) import GHC.IO (FilePath)
import Gargantext.Config
import Gargantext.API.Admin.Settings
import Gargantext.Database.Admin.Types.Node (NodeId, NodeType)
import Gargantext.Prelude
import Gargantext.Core.Crypto.Hash
import System.Directory (createDirectoryIfMissing) import System.Directory (createDirectoryIfMissing)
import qualified System.Directory as SD
import System.IO.Error
import System.Random (newStdGen) import System.Random (newStdGen)
import qualified Data.Text as Text
import qualified System.Random.Shuffle as SRS import qualified System.Random.Shuffle as SRS
import Gargantext.API.Admin.Settings
import Gargantext.Config
import Gargantext.Core.Crypto.Hash
import Gargantext.Database.Admin.Types.Node (NodeId, NodeType)
import Gargantext.Prelude
-------------------------------------------------------------------------- --------------------------------------------------------------------------
shuffle :: MonadRandom m => [a] -> m [a] shuffle :: MonadRandom m => [a] -> m [a]
shuffle ns = SRS.shuffleM ns shuffle ns = SRS.shuffleM ns
...@@ -50,7 +53,7 @@ type FileName = FilePath ...@@ -50,7 +53,7 @@ type FileName = FilePath
-- ("gar/gan","texthello") -- ("gar/gan","texthello")
toPath :: Int -> Text -> (FolderPath,FileName) toPath :: Int -> Text -> (FolderPath, FileName)
toPath n x = (Text.unpack $ Text.intercalate "/" [x1,x2], Text.unpack xs) toPath n x = (Text.unpack $ Text.intercalate "/" [x1,x2], Text.unpack xs)
where where
(x1,x') = Text.splitAt n x (x1,x') = Text.splitAt n x
...@@ -63,17 +66,26 @@ class ReadFile a where ...@@ -63,17 +66,26 @@ class ReadFile a where
readFile' :: FilePath -> IO a readFile' :: FilePath -> IO a
folderFilePath :: (MonadReader env m, MonadBase IO m) => m (FolderPath, FileName)
folderFilePath = do
(foldPath, fileName) <- liftBase $ (toPath 3) . hash . show <$> newStdGen
pure (foldPath, fileName)
writeFile :: (MonadReader env m, MonadBase IO m, HasSettings env, SaveFile a) writeFile :: (MonadReader env m, MonadBase IO m, HasSettings env, SaveFile a)
=> a -> m FilePath => a -> m FilePath
writeFile a = do writeFile a = do
dataPath <- view (settings . config . gc_datafilepath) <$> ask dataPath <- view (settings . config . gc_datafilepath) <$> ask
(fp,fn) <- liftBase $ (toPath 3) . hash . show <$> newStdGen
let foldPath = dataPath <> "/" <> fp (foldPath, fileName) <- folderFilePath
filePath = foldPath <> "/" <> fn
let filePath = foldPath <> "/" <> fileName
dataFoldPath = dataPath <> "/" <> foldPath
dataFileName = dataPath <> "/" <> filePath
_ <- liftBase $ createDirectoryIfMissing True foldPath _ <- liftBase $ createDirectoryIfMissing True dataFoldPath
_ <- liftBase $ saveFile' filePath a _ <- liftBase $ saveFile' dataFileName a
pure filePath pure filePath
...@@ -83,3 +95,13 @@ readFile :: (MonadReader env m, MonadBase IO m, HasSettings env, ReadFile a) ...@@ -83,3 +95,13 @@ readFile :: (MonadReader env m, MonadBase IO m, HasSettings env, ReadFile a)
readFile fp = do readFile fp = do
dataPath <- view (settings . config . gc_datafilepath) <$> ask dataPath <- view (settings . config . gc_datafilepath) <$> ask
liftBase $ readFile' $ dataPath <> "/" <> fp liftBase $ readFile' $ dataPath <> "/" <> fp
removeFile :: (MonadReader env m, MonadBase IO m, HasSettings env)
=> FilePath -> m ()
removeFile fp = do
dataPath <- view (settings . config . gc_datafilepath) <$> ask
liftBase $ SD.removeFile (dataPath <> "/" <> fp) `catch` handleExists
where
handleExists e
| isDoesNotExistError e = return ()
| otherwise = throwIO e
...@@ -20,11 +20,16 @@ module Gargantext.Viz.Graph.API ...@@ -20,11 +20,16 @@ module Gargantext.Viz.Graph.API
import Control.Lens (set, (^.), _Just, (^?)) import Control.Lens (set, (^.), _Just, (^?))
import Data.Aeson import Data.Aeson
import qualified Data.Map as Map
import Data.Maybe (Maybe(..)) import Data.Maybe (Maybe(..))
import Data.Swagger import Data.Swagger
import Data.Text import Data.Text
import Debug.Trace (trace) import Debug.Trace (trace)
import GHC.Generics (Generic) import GHC.Generics (Generic)
import Servant
import Servant.Job.Async
import Servant.XML
import Gargantext.API.Admin.Orchestrator.Types import Gargantext.API.Admin.Orchestrator.Types
import Gargantext.API.Ngrams (NgramsRepo, r_version) import Gargantext.API.Ngrams (NgramsRepo, r_version)
import Gargantext.API.Ngrams.Tools import Gargantext.API.Ngrams.Tools
...@@ -45,10 +50,6 @@ import Gargantext.Viz.Graph ...@@ -45,10 +50,6 @@ import Gargantext.Viz.Graph
import Gargantext.Viz.Graph.GEXF () import Gargantext.Viz.Graph.GEXF ()
import Gargantext.Viz.Graph.Tools -- (cooc2graph) import Gargantext.Viz.Graph.Tools -- (cooc2graph)
import Gargantext.Viz.Graph.Distances (Distance(..), GraphMetric(..)) import Gargantext.Viz.Graph.Distances (Distance(..), GraphMetric(..))
import Servant
import Servant.Job.Async
import Servant.XML
import qualified Data.Map as Map
------------------------------------------------------------------------ ------------------------------------------------------------------------
-- | There is no Delete specific API for Graph since it can be deleted -- | There is no Delete specific API for Graph since it can be deleted
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment