Commit 0e5860a2 authored by Alfredo Di Napoli's avatar Alfredo Di Napoli

Implement /publish endpoint

parent 3d494648
Pipeline #6983 passed with stages
in 63 minutes and 18 seconds
...@@ -156,6 +156,7 @@ library ...@@ -156,6 +156,7 @@ library
Gargantext.API.Routes.Named.Node Gargantext.API.Routes.Named.Node
Gargantext.API.Routes.Named.Private Gargantext.API.Routes.Named.Private
Gargantext.API.Routes.Named.Public Gargantext.API.Routes.Named.Public
Gargantext.API.Routes.Named.Publish
Gargantext.API.Routes.Named.Search Gargantext.API.Routes.Named.Search
Gargantext.API.Routes.Named.Share Gargantext.API.Routes.Named.Share
Gargantext.API.Routes.Named.Table Gargantext.API.Routes.Named.Table
......
...@@ -20,6 +20,7 @@ module Gargantext.API.Auth.PolicyCheck ( ...@@ -20,6 +20,7 @@ module Gargantext.API.Auth.PolicyCheck (
, nodePublishedRead , nodePublishedRead
, nodePublishedEdit , nodePublishedEdit
, moveChecks , moveChecks
, publishChecks
, userMe , userMe
, alwaysAllow , alwaysAllow
, alwaysDeny , alwaysDeny
...@@ -256,6 +257,10 @@ moveChecks (SourceId sourceId) (TargetId targetId) = ...@@ -256,6 +257,10 @@ moveChecks (SourceId sourceId) (TargetId targetId) =
BAnd (nodeUser sourceId `BOr` nodeSuper sourceId) BAnd (nodeUser sourceId `BOr` nodeSuper sourceId)
(nodeUser targetId `BOr` nodeUser targetId) (nodeUser targetId `BOr` nodeUser targetId)
publishChecks :: NodeId -> BoolExpr AccessCheck
publishChecks nodeId =
(nodeUser nodeId `BOr` nodeSuper nodeId)
alwaysAllow :: BoolExpr AccessCheck alwaysAllow :: BoolExpr AccessCheck
alwaysAllow = BConst . Positive $ AC_always_allow alwaysAllow = BConst . Positive $ AC_always_allow
......
...@@ -31,7 +31,7 @@ module Gargantext.API.Node ...@@ -31,7 +31,7 @@ module Gargantext.API.Node
import Gargantext.API.Admin.Auth (withNamedAccess, withNamedPolicyT, withPolicy, withPolicy) import Gargantext.API.Admin.Auth (withNamedAccess, withNamedPolicyT, withPolicy, withPolicy)
import Gargantext.API.Admin.Auth.Types (PathId(..), AuthenticatedUser (..), auth_node_id, auth_user_id) import Gargantext.API.Admin.Auth.Types (PathId(..), AuthenticatedUser (..), auth_node_id, auth_user_id)
import Gargantext.API.Admin.EnvTypes (Env) import Gargantext.API.Admin.EnvTypes (Env)
import Gargantext.API.Auth.PolicyCheck ( nodeReadChecks, nodeWriteChecks, moveChecks, AccessPolicyManager ) import Gargantext.API.Auth.PolicyCheck ( nodeReadChecks, nodeWriteChecks, moveChecks, AccessPolicyManager, publishChecks )
import Gargantext.API.Errors.Types (BackendInternalError) import Gargantext.API.Errors.Types (BackendInternalError)
import Gargantext.API.Metrics import Gargantext.API.Metrics
import Gargantext.API.Ngrams.Types (TabType(..)) import Gargantext.API.Ngrams.Types (TabType(..))
...@@ -47,6 +47,7 @@ import Gargantext.API.Prelude ( GargM, GargServer, IsGargServer ) ...@@ -47,6 +47,7 @@ import Gargantext.API.Prelude ( GargM, GargServer, IsGargServer )
import Gargantext.API.Routes.Named.File qualified as Named import Gargantext.API.Routes.Named.File qualified as Named
import Gargantext.API.Routes.Named.Node qualified as Named import Gargantext.API.Routes.Named.Node qualified as Named
import Gargantext.API.Routes.Named.Private qualified as Named import Gargantext.API.Routes.Named.Private qualified as Named
import Gargantext.API.Routes.Named.Publish qualified as Named
import Gargantext.API.Routes.Named.Share qualified as Named import Gargantext.API.Routes.Named.Share qualified as Named
import Gargantext.API.Search qualified as Search import Gargantext.API.Search qualified as Search
import Gargantext.API.Server.Named.Ngrams (apiNgramsTableCorpus) import Gargantext.API.Server.Named.Ngrams (apiNgramsTableCorpus)
...@@ -62,7 +63,7 @@ import Gargantext.Database.Prelude (Cmd, JSONB) ...@@ -62,7 +63,7 @@ import Gargantext.Database.Prelude (Cmd, JSONB)
import Gargantext.Database.Query.Table.Node import Gargantext.Database.Query.Table.Node
import Gargantext.Database.Query.Table.Node.Children (getChildren) import Gargantext.Database.Query.Table.Node.Children (getChildren)
import Gargantext.Database.Query.Table.Node.Update (Update(..), update) import Gargantext.Database.Query.Table.Node.Update (Update(..), update)
import Gargantext.Database.Query.Table.Node.Update qualified as U (update, Update(..)) import Gargantext.Database.Query.Table.Node.Update qualified as U (update, Update(..), publish)
import Gargantext.Database.Query.Table.Node.UpdateOpaleye (updateHyperdata) import Gargantext.Database.Query.Table.Node.UpdateOpaleye (updateHyperdata)
import Gargantext.Database.Query.Table.NodeContext (nodeContextsCategory, nodeContextsScore) import Gargantext.Database.Query.Table.NodeContext (nodeContextsCategory, nodeContextsScore)
import Gargantext.Database.Query.Table.NodeNode import Gargantext.Database.Query.Table.NodeNode
...@@ -248,6 +249,9 @@ genericNodeAPI' _ authenticatedUser targetNode = Named.NodeAPI ...@@ -248,6 +249,9 @@ genericNodeAPI' _ authenticatedUser targetNode = Named.NodeAPI
, scoreAPI = Named.ScoreAPI $ scoreApi targetNode , scoreAPI = Named.ScoreAPI $ scoreApi targetNode
, searchAPI = Search.api targetNode , searchAPI = Search.api targetNode
, shareAPI = Named.ShareNode $ Share.api userRootId targetNode , shareAPI = Named.ShareNode $ Share.api userRootId targetNode
, unshareEp = Share.unShare targetNode
, publishAPI = withNamedPolicyT authenticatedUser (publishChecks targetNode) $
Named.PublishAPI $ \Named.PublishRequest{pubrq_policy} -> U.publish loggedInUserId targetNode pubrq_policy
---- Pairing utilities ---- Pairing utilities
, pairWithEp = pairWith targetNode , pairWithEp = pairWith targetNode
, pairsEp = pairs targetNode , pairsEp = pairs targetNode
...@@ -261,7 +265,6 @@ genericNodeAPI' _ authenticatedUser targetNode = Named.NodeAPI ...@@ -261,7 +265,6 @@ genericNodeAPI' _ authenticatedUser targetNode = Named.NodeAPI
, moveAPI = Named.MoveAPI $ \parentId -> , moveAPI = Named.MoveAPI $ \parentId ->
withPolicy authenticatedUser (moveChecks (SourceId targetNode) (TargetId parentId)) $ withPolicy authenticatedUser (moveChecks (SourceId targetNode) (TargetId parentId)) $
moveNode loggedInUserId targetNode parentId moveNode loggedInUserId targetNode parentId
, unshareEp = Share.unShare targetNode
, fileAPI = Named.FileAPI $ fileApi targetNode , fileAPI = Named.FileAPI $ fileApi targetNode
, fileAsyncAPI = fileAsyncApi authenticatedUser targetNode , fileAsyncAPI = fileAsyncApi authenticatedUser targetNode
, dfwnAPI = DFWN.api authenticatedUser targetNode , dfwnAPI = DFWN.api authenticatedUser targetNode
......
...@@ -28,25 +28,26 @@ module Gargantext.API.Routes.Named.Node ( ...@@ -28,25 +28,26 @@ module Gargantext.API.Routes.Named.Node (
, UpdateNodeParams(..) , UpdateNodeParams(..)
) where ) where
import GHC.Generics
import Gargantext.API.Admin.Orchestrator.Types (JobLog(..), AsyncJobs) import Gargantext.API.Admin.Orchestrator.Types (JobLog(..), AsyncJobs)
import Gargantext.API.Auth.PolicyCheck ( PolicyChecked ) import Gargantext.API.Auth.PolicyCheck ( PolicyChecked )
import Gargantext.API.Ngrams.Types (TabType(..)) import Gargantext.API.Ngrams.Types (TabType(..))
import Gargantext.API.Node.New.Types ( PostNode(..) )
import Gargantext.API.Node.Types ( RenameNode(..), NodesToScore(..), NodesToCategory(..) )
import Gargantext.API.Node.Update.Types ( UpdateNodeParams(..), Charts(..), Granularity(..), Method(..) )
import Gargantext.API.Routes.Named.Document import Gargantext.API.Routes.Named.Document
import Gargantext.API.Routes.Named.File import Gargantext.API.Routes.Named.File
import Gargantext.API.Routes.Named.FrameCalc import Gargantext.API.Routes.Named.FrameCalc
import Gargantext.API.Routes.Named.Metrics import Gargantext.API.Routes.Named.Metrics
import Gargantext.API.Routes.Named.Viz import Gargantext.API.Routes.Named.Publish (PublishAPI)
import Gargantext.API.Routes.Named.Search import Gargantext.API.Routes.Named.Search
import Gargantext.API.Routes.Named.Share as Share import Gargantext.API.Routes.Named.Share as Share
import Gargantext.API.Routes.Named.Table import Gargantext.API.Routes.Named.Table
import Gargantext.API.Node.Types ( RenameNode(..), NodesToScore(..), NodesToCategory(..) ) import Gargantext.API.Routes.Named.Viz
import Gargantext.API.Node.New.Types ( PostNode(..) )
import Gargantext.API.Node.Update.Types ( UpdateNodeParams(..), Charts(..), Granularity(..), Method(..) )
import Gargantext.Core.Types import Gargantext.Core.Types
import Gargantext.Core.Types.Query import Gargantext.Core.Types.Query
import Gargantext.Database.Admin.Types.Hyperdata.User ( HyperdataUser ) import Gargantext.Database.Admin.Types.Hyperdata.User ( HyperdataUser )
import Gargantext.Database.Query.Facet.Types ( FacetDoc, OrderBy(..) ) import Gargantext.Database.Query.Facet.Types ( FacetDoc, OrderBy(..) )
import GHC.Generics
import Prelude import Prelude
import Servant import Servant
...@@ -82,6 +83,7 @@ data NodeAPI a mode = NodeAPI ...@@ -82,6 +83,7 @@ data NodeAPI a mode = NodeAPI
, searchAPI :: mode :- "search" :> NamedRoutes (SearchAPI SearchResult) , searchAPI :: mode :- "search" :> NamedRoutes (SearchAPI SearchResult)
, shareAPI :: mode :- "share" :> NamedRoutes ShareNode , shareAPI :: mode :- "share" :> NamedRoutes ShareNode
, unshareEp :: mode :- "unshare" :> NamedRoutes Share.UnshareNode , unshareEp :: mode :- "unshare" :> NamedRoutes Share.UnshareNode
, publishAPI :: mode :- "publish" :> (PolicyChecked (NamedRoutes PublishAPI))
---- Pairing utilities ---- Pairing utilities
, pairWithEp :: mode :- "pairwith" :> NamedRoutes PairWith , pairWithEp :: mode :- "pairwith" :> NamedRoutes PairWith
, pairsEp :: mode :- "pairs" :> NamedRoutes Pairs , pairsEp :: mode :- "pairs" :> NamedRoutes Pairs
......
{-# LANGUAGE TypeOperators #-}
module Gargantext.API.Routes.Named.Publish (
PublishRequest(..)
, PublishAPI(..)
) where
import Data.Aeson as JS
import Data.Swagger
import Gargantext.Database.Query.Table.NodeNode (NodePublishPolicy)
import GHC.Generics (Generic)
import Prelude
import Servant
import Test.QuickCheck
newtype PublishRequest = PublishRequest
{ pubrq_policy :: NodePublishPolicy
} deriving (Show, Eq, Generic)
instance ToSchema PublishRequest
instance ToJSON PublishRequest where
toJSON (PublishRequest pol) =
JS.object [ "policy" JS..= toJSON pol ]
instance FromJSON PublishRequest where
parseJSON = withObject "PublishRequest" $ \o -> do
pubrq_policy <- o JS..: "policy"
pure $ PublishRequest{..}
instance Arbitrary PublishRequest where
arbitrary = PublishRequest <$> arbitraryBoundedEnum
newtype PublishAPI mode = PublishAPI
{ publishEp :: mode :- Summary "Publish a Corpus Node"
:> ReqBody '[JSON] PublishRequest
:> Delete '[JSON] Int
} deriving Generic
...@@ -13,6 +13,7 @@ Portability : POSIX ...@@ -13,6 +13,7 @@ Portability : POSIX
module Gargantext.Database.Query.Table.Node.Update ( module Gargantext.Database.Query.Table.Node.Update (
Update(..) Update(..)
, update , update
, publish
) )
where where
...@@ -72,7 +73,7 @@ update loggedInUserId (Move sourceId targetId) = do ...@@ -72,7 +73,7 @@ update loggedInUserId (Move sourceId targetId) = do
-> -- both are not read-only, normal move -> -- both are not read-only, normal move
move_db_update sourceId targetId move_db_update sourceId targetId
(False, True) (False, True)
-> publish_node (SourceId sourceId) (TargetId targetId) NPP_publish_no_edits_allowed -> (:[]) <$> publish_node (SourceId sourceId) (TargetId targetId) NPP_publish_no_edits_allowed
(True, False) (True, False)
-> -- the source is read only. If we are the owner we allow unpublishing. -> -- the source is read only. If we are the owner we allow unpublishing.
-- FIXME(adn) is this check enough? -- FIXME(adn) is this check enough?
...@@ -92,7 +93,12 @@ update loggedInUserId (Move sourceId targetId) = do ...@@ -92,7 +93,12 @@ update loggedInUserId (Move sourceId targetId) = do
pure ids pure ids
publish_node :: HasNodeError err => SourceId -> TargetId -> NodePublishPolicy -> Cmd err [Int] publish :: HasNodeError err => UserId -> NodeId -> NodePublishPolicy -> Cmd err Int
publish loggedInUserId sourceId policy = do
targetId <- _node_id <$> getUserRootPublicNode loggedInUserId
publish_node (SourceId sourceId) (TargetId targetId) policy
publish_node :: HasNodeError err => SourceId -> TargetId -> NodePublishPolicy -> Cmd err Int
publish_node (SourceId sourceId) (TargetId targetId) policy = do publish_node (SourceId sourceId) (TargetId targetId) policy = do
sourceNode <- getNode sourceId sourceNode <- getNode sourceId
targetNode <- getNode targetId targetNode <- getNode targetId
...@@ -109,7 +115,7 @@ publish_node (SourceId sourceId) (TargetId targetId) policy = do ...@@ -109,7 +115,7 @@ publish_node (SourceId sourceId) (TargetId targetId) policy = do
-- way by disallowing further edits on the original node, -- way by disallowing further edits on the original node,
-- including edits from the owner itself! -- including edits from the owner itself!
publishNode policy (SourceId sourceId) (TargetId targetId) publishNode policy (SourceId sourceId) (TargetId targetId)
pure [ _NodeId $ sourceId] pure (_NodeId $ sourceId)
_ -> nodeError (NodeIsReadOnly targetId "Target is read only, but not a public folder.") _ -> nodeError (NodeIsReadOnly targetId "Target is read only, but not a public folder.")
......
...@@ -139,5 +139,20 @@ tests = sequential $ aroundAll withTestDBAndPort $ do ...@@ -139,5 +139,20 @@ tests = sequential $ aroundAll withTestDBAndPort $ do
res' <- runClientM (move_node token (SourceId fId'') (TargetId alicePublicFolderId)) clientEnv res' <- runClientM (move_node token (SourceId fId'') (TargetId alicePublicFolderId)) clientEnv
res' `shouldFailWith` EC_403__node_move_error res' `shouldFailWith` EC_403__node_move_error
it "allows publishing via the /publish endpoint" $ \(SpecContext testEnv serverPort app _) -> do
withApplication app $ do
aliceCorpusId <- withValidLogin serverPort "alice" (GargPassword "alice") $ \clientEnv token -> do
liftIO $ do
cId <- newCorpusForUser testEnv "alice"
void $ runClientM (publish_node token cId NPP_publish_no_edits_allowed) clientEnv
pure cId
-- bob should be able to see it
withValidLogin serverPort "bob" (GargPassword "bob") $ \clientEnv token -> do
tree <- liftIO $ do
bobNodeId <- myUserNodeId testEnv "bob"
checkEither $ runClientM (get_tree token bobNodeId) clientEnv
containsNode aliceCorpusId tree `shouldBe` True
containsNode :: NodeId -> Tree NodeTree -> Bool containsNode :: NodeId -> Tree NodeTree -> Bool
containsNode target (TreeN r c) = _nt_id r == target || any (containsNode target) c containsNode target (TreeN r c) = _nt_id r == target || any (containsNode target) c
...@@ -22,6 +22,7 @@ module Test.API.Routes ( ...@@ -22,6 +22,7 @@ module Test.API.Routes (
, get_table_ngrams , get_table_ngrams
, get_tree , get_tree
, move_node , move_node
, publish_node
, put_table_ngrams , put_table_ngrams
, update_node , update_node
, delete_node , delete_node
...@@ -37,6 +38,7 @@ import Gargantext.API.Ngrams.Types ( NgramsTable, NgramsTablePatch, OrderBy, Tab ...@@ -37,6 +38,7 @@ import Gargantext.API.Ngrams.Types ( NgramsTable, NgramsTablePatch, OrderBy, Tab
import Gargantext.API.Routes.Named import Gargantext.API.Routes.Named
import Gargantext.API.Routes.Named.Node hiding (treeAPI) import Gargantext.API.Routes.Named.Node hiding (treeAPI)
import Gargantext.API.Routes.Named.Private hiding (tableNgramsAPI) import Gargantext.API.Routes.Named.Private hiding (tableNgramsAPI)
import Gargantext.API.Routes.Named.Publish (PublishRequest(..))
import Gargantext.API.Routes.Named.Table import Gargantext.API.Routes.Named.Table
import Gargantext.API.Routes.Named.Tree (nodeTreeEp) import Gargantext.API.Routes.Named.Tree (nodeTreeEp)
import Gargantext.API.Types () -- MimeUnrender instances import Gargantext.API.Types () -- MimeUnrender instances
...@@ -56,6 +58,7 @@ import Servant.Client (ClientM) ...@@ -56,6 +58,7 @@ import Servant.Client (ClientM)
import Servant.Client.Core (RunClient, HasClient(..), Request) import Servant.Client.Core (RunClient, HasClient(..), Request)
import Servant.Client.Generic ( genericClient, AsClientT ) import Servant.Client.Generic ( genericClient, AsClientT )
import Servant.Job.Async import Servant.Job.Async
import Gargantext.API.Routes.Named.Publish (PublishAPI(..))
instance RunClient m => HasClient m WS.WebSocketPending where instance RunClient m => HasClient m WS.WebSocketPending where
...@@ -290,3 +293,21 @@ delete_node (toServantToken -> token) nodeId = ...@@ -290,3 +293,21 @@ delete_node (toServantToken -> token) nodeId =
& nodeEndpointAPI & nodeEndpointAPI
& ($ nodeId) & ($ nodeId)
& deleteEp & deleteEp
publish_node :: Token -> NodeId -> NodePublishPolicy -> ClientM NodeId
publish_node (toServantToken -> token) sourceId policy = fmap UnsafeMkNodeId $
clientRoutes & apiWithCustomErrorScheme
& ($ GES_new)
& backendAPI
& backendAPI'
& mkBackEndAPI
& gargAPIVersion
& gargPrivateAPI
& mkPrivateAPI
& ($ token)
& nodeEp
& nodeEndpointAPI
& ($ sourceId)
& publishAPI
& publishEp
& ($ PublishRequest policy)
...@@ -6,15 +6,16 @@ ...@@ -6,15 +6,16 @@
module Test.Offline.JSON (tests) where module Test.Offline.JSON (tests) where
import Data.Aeson import Data.Aeson
import Data.ByteString qualified as B
import Data.ByteString.Lazy.Char8 qualified as C8 import Data.ByteString.Lazy.Char8 qualified as C8
import Data.ByteString qualified as B
import Data.Either import Data.Either
import Gargantext.API.Errors import Gargantext.API.Errors
import Gargantext.API.Node.Corpus.Types import Gargantext.API.Node.Corpus.Types
import Gargantext.API.Node.Types import Gargantext.API.Node.Types
import Gargantext.API.Routes.Named.Publish (PublishRequest)
import Gargantext.API.Viz.Types import Gargantext.API.Viz.Types
import Gargantext.Core.Types.Phylo import Gargantext.Core.Types.Phylo
import qualified Gargantext.Core.Viz.Phylo as VizPhylo import Gargantext.Core.Viz.Phylo qualified as VizPhylo
import Gargantext.Database.Admin.Types.Node import Gargantext.Database.Admin.Types.Node
import Paths_gargantext import Paths_gargantext
import Prelude import Prelude
...@@ -54,6 +55,7 @@ tests = testGroup "JSON" [ ...@@ -54,6 +55,7 @@ tests = testGroup "JSON" [
, testProperty "RootId roundtrips" (jsonRoundtrip @RootId) , testProperty "RootId roundtrips" (jsonRoundtrip @RootId)
, testProperty "Datafield roundtrips" (jsonRoundtrip @Datafield) , testProperty "Datafield roundtrips" (jsonRoundtrip @Datafield)
, testProperty "WithQuery roundtrips" (jsonRoundtrip @WithQuery) , testProperty "WithQuery roundtrips" (jsonRoundtrip @WithQuery)
, testProperty "PublishRequest roundtrips" (jsonRoundtrip @PublishRequest)
, testProperty "FrontendError roundtrips" jsonFrontendErrorRoundtrip , testProperty "FrontendError roundtrips" jsonFrontendErrorRoundtrip
, testProperty "BackendErrorCode roundtrips" (jsonEnumRoundtrip (Dict @_ @BackendErrorCode)) , testProperty "BackendErrorCode roundtrips" (jsonEnumRoundtrip (Dict @_ @BackendErrorCode))
, testProperty "NodeType roundtrips" (jsonEnumRoundtrip (Dict @_ @NodeType)) , testProperty "NodeType roundtrips" (jsonEnumRoundtrip (Dict @_ @NodeType))
......
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