Commit 600461b4 authored by Przemyslaw Kaminski's avatar Przemyslaw Kaminski

[upload] base64-encoded file upload works now

parent e46e871b
Pipeline #1011 canceled with stage
...@@ -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)
......
...@@ -43,6 +43,7 @@ import Test.QuickCheck.Arbitrary (Arbitrary, arbitrary) ...@@ -43,6 +43,7 @@ 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
...@@ -148,6 +149,8 @@ type NodeAPI a = Get '[JSON] (Node a) ...@@ -148,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"
...@@ -223,6 +226,8 @@ nodeAPI p uId id' = withAccess (Proxy :: Proxy (NodeAPI a)) Proxy uId (PathNode ...@@ -223,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,12 +22,14 @@ module Gargantext.API.Node.Corpus.New ...@@ -22,12 +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 as T
import qualified Data.Text.IO as TIO 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
...@@ -178,7 +180,7 @@ instance ToSchema NewWithForm where ...@@ -178,7 +180,7 @@ instance ToSchema NewWithForm where
------------------------------------------------------- -------------------------------------------------------
data NewWithFile = NewWithFile data NewWithFile = NewWithFile
{ _wfi_data :: !Text { _wfi_b64_data :: !Text
, _wfi_lang :: !(Maybe Lang) , _wfi_lang :: !(Maybe Lang)
, _wfi_name :: !Text , _wfi_name :: !Text
} deriving (Eq, Show, Generic) } deriving (Eq, Show, Generic)
...@@ -191,7 +193,12 @@ instance ToSchema NewWithFile where ...@@ -191,7 +193,12 @@ instance ToSchema NewWithFile where
declareNamedSchema = genericDeclareNamedSchema (unPrefixSwagger "_wfi_") declareNamedSchema = genericDeclareNamedSchema (unPrefixSwagger "_wfi_")
instance GPU.SaveFile NewWithFile where instance GPU.SaveFile NewWithFile where
saveFile' fp (NewWithFile d _ _) = TIO.writeFile fp d 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 --instance GPU.ReadFile NewWithFile where
-- readFile' = TIO.readFile -- readFile' = TIO.readFile
......
{-# 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
...@@ -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
...@@ -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