mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-16 09:51:59 +03:00
a7cc5df95d
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/2399 GitOrigin-RevId: 009ee6ec2a3834951e464c232eb517d636ab940a
276 lines
8.9 KiB
Haskell
276 lines
8.9 KiB
Haskell
module Hasura.RQL.DDL.Action
|
|
( CreateAction (..),
|
|
runCreateAction,
|
|
resolveAction,
|
|
UpdateAction,
|
|
runUpdateAction,
|
|
DropAction,
|
|
runDropAction,
|
|
dropActionInMetadata,
|
|
CreateActionPermission (..),
|
|
runCreateActionPermission,
|
|
DropActionPermission,
|
|
runDropActionPermission,
|
|
dropActionPermissionInMetadata,
|
|
)
|
|
where
|
|
|
|
import Control.Lens ((.~), (^.))
|
|
import Data.Aeson qualified as J
|
|
import Data.Aeson.TH qualified as J
|
|
import Data.Dependent.Map qualified as DMap
|
|
import Data.Environment qualified as Env
|
|
import Data.HashMap.Strict qualified as Map
|
|
import Data.HashMap.Strict.InsOrd qualified as OMap
|
|
import Data.Text.Extended
|
|
import Hasura.Base.Error
|
|
import Hasura.EncJSON
|
|
import Hasura.Metadata.Class
|
|
import Hasura.Prelude
|
|
import Hasura.RQL.DDL.CustomTypes (lookupPGScalar)
|
|
import Hasura.RQL.DDL.RequestTransform
|
|
import Hasura.RQL.Types
|
|
import Hasura.SQL.Tag
|
|
import Hasura.Session
|
|
import Language.GraphQL.Draft.Syntax qualified as G
|
|
|
|
getActionInfo ::
|
|
(QErrM m, CacheRM m) =>
|
|
ActionName ->
|
|
m ActionInfo
|
|
getActionInfo actionName = do
|
|
actionMap <- scActions <$> askSchemaCache
|
|
onNothing (Map.lookup actionName actionMap) $
|
|
throw400 NotExists $ "action with name " <> actionName <<> " does not exist"
|
|
|
|
data CreateAction = CreateAction
|
|
{ _caName :: !ActionName,
|
|
_caDefinition :: !ActionDefinitionInput,
|
|
_caComment :: !(Maybe Text),
|
|
_caTransform :: !(Maybe MetadataTransform)
|
|
}
|
|
|
|
$(J.deriveJSON hasuraJSON ''CreateAction)
|
|
|
|
runCreateAction ::
|
|
(QErrM m, CacheRWM m, MetadataM m) =>
|
|
CreateAction ->
|
|
m EncJSON
|
|
runCreateAction createAction = do
|
|
-- check if action with same name exists already
|
|
actionMap <- scActions <$> askSchemaCache
|
|
void $
|
|
onJust (Map.lookup actionName actionMap) $
|
|
const $
|
|
throw400 AlreadyExists $
|
|
"action with name " <> actionName <<> " already exists"
|
|
let metadata =
|
|
ActionMetadata
|
|
actionName
|
|
(_caComment createAction)
|
|
(_caDefinition createAction)
|
|
[]
|
|
(_caTransform createAction)
|
|
buildSchemaCacheFor (MOAction actionName) $
|
|
MetadataModifier $
|
|
metaActions %~ OMap.insert actionName metadata
|
|
pure successMsg
|
|
where
|
|
actionName = _caName createAction
|
|
|
|
-- | Note [Postgres scalars in action input arguments]
|
|
-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
-- It's very comfortable to be able to reference Postgres scalars in actions
|
|
-- input arguments. For example, see the following action mutation:
|
|
--
|
|
-- extend type mutation_root {
|
|
-- create_user (
|
|
-- name: String!
|
|
-- created_at: timestamptz
|
|
-- ): User
|
|
-- }
|
|
--
|
|
-- The timestamptz is a Postgres scalar. We need to validate the presence of
|
|
-- timestamptz type in the Postgres database. So, the 'resolveAction' function
|
|
-- takes all Postgres scalar types as one of the inputs and returns the set of
|
|
-- referred scalars.
|
|
resolveAction ::
|
|
QErrM m =>
|
|
Env.Environment ->
|
|
AnnotatedCustomTypes ->
|
|
ActionDefinitionInput ->
|
|
DMap.DMap BackendTag ScalarSet -> -- See Note [Postgres scalars in custom types]
|
|
m
|
|
( ResolvedActionDefinition,
|
|
AnnotatedObjectType
|
|
)
|
|
resolveAction env AnnotatedCustomTypes {..} ActionDefinition {..} allScalars = do
|
|
resolvedArguments <- forM _adArguments $ \argumentDefinition -> do
|
|
forM argumentDefinition $ \argumentType -> do
|
|
let gType = unGraphQLType argumentType
|
|
argumentBaseType = G.getBaseType gType
|
|
(gType,)
|
|
<$> if
|
|
| Just noCTScalar <- lookupPGScalar allScalars argumentBaseType (NOCTScalar . ASTReusedScalar argumentBaseType) ->
|
|
pure noCTScalar
|
|
| Just nonObjectType <- Map.lookup argumentBaseType _actNonObjects ->
|
|
pure nonObjectType
|
|
| otherwise ->
|
|
throw400 InvalidParams $
|
|
"the type: " <> dquote argumentBaseType
|
|
<> " is not defined in custom types or it is not a scalar/enum/input_object"
|
|
|
|
-- Check if the response type is an object
|
|
let outputType = unGraphQLType _adOutputType
|
|
outputBaseType = G.getBaseType outputType
|
|
outputObject <-
|
|
onNothing (Map.lookup outputBaseType _actObjects) $
|
|
throw400 NotExists $
|
|
"the type: " <> dquote outputBaseType
|
|
<> " is not an object type defined in custom types"
|
|
resolvedWebhook <- resolveWebhook env _adHandler
|
|
pure
|
|
( ActionDefinition
|
|
resolvedArguments
|
|
_adOutputType
|
|
_adType
|
|
_adHeaders
|
|
_adForwardClientHeaders
|
|
_adTimeout
|
|
resolvedWebhook,
|
|
outputObject
|
|
)
|
|
|
|
data UpdateAction = UpdateAction
|
|
{ _uaName :: !ActionName,
|
|
_uaDefinition :: !ActionDefinitionInput,
|
|
_uaComment :: !(Maybe Text),
|
|
_uaTransform :: !(Maybe MetadataTransform)
|
|
}
|
|
|
|
$(J.deriveFromJSON hasuraJSON ''UpdateAction)
|
|
|
|
runUpdateAction ::
|
|
forall m.
|
|
(QErrM m, CacheRWM m, MetadataM m) =>
|
|
UpdateAction ->
|
|
m EncJSON
|
|
runUpdateAction (UpdateAction actionName actionDefinition actionComment transform) = do
|
|
sc <- askSchemaCache
|
|
let actionsMap = scActions sc
|
|
void $
|
|
onNothing (Map.lookup actionName actionsMap) $
|
|
throw400 NotExists $ "action with name " <> actionName <<> " does not exist"
|
|
buildSchemaCacheFor (MOAction actionName) $ updateActionMetadataModifier actionDefinition actionComment
|
|
pure successMsg
|
|
where
|
|
updateActionMetadataModifier :: ActionDefinitionInput -> Maybe Text -> MetadataModifier
|
|
updateActionMetadataModifier def comment =
|
|
MetadataModifier $
|
|
(metaActions . ix actionName . amDefinition .~ def)
|
|
. (metaActions . ix actionName . amComment .~ comment)
|
|
. (metaActions . ix actionName . amMetadataTransform .~ transform)
|
|
|
|
newtype ClearActionData = ClearActionData {unClearActionData :: Bool}
|
|
deriving (Show, Eq, J.FromJSON, J.ToJSON)
|
|
|
|
shouldClearActionData :: ClearActionData -> Bool
|
|
shouldClearActionData = unClearActionData
|
|
|
|
defaultClearActionData :: ClearActionData
|
|
defaultClearActionData = ClearActionData True
|
|
|
|
data DropAction = DropAction
|
|
{ _daName :: !ActionName,
|
|
_daClearData :: !(Maybe ClearActionData)
|
|
}
|
|
deriving (Show, Eq)
|
|
|
|
$(J.deriveJSON hasuraJSON ''DropAction)
|
|
|
|
runDropAction ::
|
|
( CacheRWM m,
|
|
MetadataM m,
|
|
MonadMetadataStorageQueryAPI m
|
|
) =>
|
|
DropAction ->
|
|
m EncJSON
|
|
runDropAction (DropAction actionName clearDataM) = do
|
|
void $ getActionInfo actionName
|
|
withNewInconsistentObjsCheck $
|
|
buildSchemaCache $
|
|
dropActionInMetadata actionName
|
|
when (shouldClearActionData clearData) $ deleteActionData actionName
|
|
return successMsg
|
|
where
|
|
-- When clearData is not present we assume that
|
|
-- the data needs to be retained
|
|
clearData = fromMaybe defaultClearActionData clearDataM
|
|
|
|
dropActionInMetadata :: ActionName -> MetadataModifier
|
|
dropActionInMetadata name =
|
|
MetadataModifier $ metaActions %~ OMap.delete name
|
|
|
|
newtype ActionMetadataField = ActionMetadataField {unActionMetadataField :: Text}
|
|
deriving (Show, Eq, J.FromJSON, J.ToJSON)
|
|
|
|
doesActionPermissionExist :: Metadata -> ActionName -> RoleName -> Bool
|
|
doesActionPermissionExist metadata actionName roleName =
|
|
any ((== roleName) . _apmRole) $ metadata ^. (metaActions . ix actionName . amPermissions)
|
|
|
|
data CreateActionPermission = CreateActionPermission
|
|
{ _capAction :: !ActionName,
|
|
_capRole :: !RoleName,
|
|
_capDefinition :: !(Maybe J.Value),
|
|
_capComment :: !(Maybe Text)
|
|
}
|
|
|
|
$(J.deriveFromJSON hasuraJSON ''CreateActionPermission)
|
|
|
|
runCreateActionPermission ::
|
|
(QErrM m, CacheRWM m, MetadataM m) =>
|
|
CreateActionPermission ->
|
|
m EncJSON
|
|
runCreateActionPermission createActionPermission = do
|
|
metadata <- getMetadata
|
|
when (doesActionPermissionExist metadata actionName roleName) $
|
|
throw400 AlreadyExists $
|
|
"permission for role " <> roleName
|
|
<<> " is already defined on " <>> actionName
|
|
buildSchemaCacheFor (MOActionPermission actionName roleName) $
|
|
MetadataModifier $
|
|
metaActions . ix actionName . amPermissions
|
|
%~ (:) (ActionPermissionMetadata roleName comment)
|
|
pure successMsg
|
|
where
|
|
CreateActionPermission actionName roleName _ comment = createActionPermission
|
|
|
|
data DropActionPermission = DropActionPermission
|
|
{ _dapAction :: !ActionName,
|
|
_dapRole :: !RoleName
|
|
}
|
|
deriving (Show, Eq)
|
|
|
|
$(J.deriveJSON hasuraJSON ''DropActionPermission)
|
|
|
|
runDropActionPermission ::
|
|
(QErrM m, CacheRWM m, MetadataM m) =>
|
|
DropActionPermission ->
|
|
m EncJSON
|
|
runDropActionPermission dropActionPermission = do
|
|
metadata <- getMetadata
|
|
unless (doesActionPermissionExist metadata actionName roleName) $
|
|
throw400 NotExists $
|
|
"permission for role: " <> roleName <<> " is not defined on " <>> actionName
|
|
buildSchemaCacheFor (MOActionPermission actionName roleName) $
|
|
dropActionPermissionInMetadata actionName roleName
|
|
return successMsg
|
|
where
|
|
actionName = _dapAction dropActionPermission
|
|
roleName = _dapRole dropActionPermission
|
|
|
|
dropActionPermissionInMetadata :: ActionName -> RoleName -> MetadataModifier
|
|
dropActionPermissionInMetadata name role =
|
|
MetadataModifier $
|
|
metaActions . ix name . amPermissions %~ filter ((/=) role . _apmRole)
|