module Gargantext.Components.GraphQL where import Gargantext.Prelude import Affjax.RequestHeader as ARH import Control.Monad.Error.Class as Errors import Data.Argonaut.Core as AC import Data.Argonaut.Decode (JsonDecodeError) import Data.Array as A import Data.Bifunctor (lmap) import Data.Either (Either(..)) import Data.List.Types (NonEmptyList) import Data.Maybe (Maybe(..)) import Effect (Effect) import Effect.Aff (Aff, throwError, error) import Effect.Class (liftEffect) import FFI.Simple ((.?)) import Foreign as Foreign import Gargantext.Components.GraphQL.Contact (AnnuaireContact) import Gargantext.Components.GraphQL.Context as GQLCTX import Gargantext.Components.GraphQL.IMT as GQLIMT import Gargantext.Components.GraphQL.NLP as GQLNLP import Gargantext.Components.GraphQL.Node as GQLNode import Gargantext.Components.GraphQL.Tree (TreeFirstLevel, BreadcrumbInfo) import Gargantext.Components.GraphQL.User (User, UserInfo, UserInfoM, UserPubmedAPIKeyM, UserEPOAPIUserM, UserEPOAPITokenM) import Gargantext.Components.GraphQL.Team (Team, TeamDeleteM) -- import Gargantext.Config.REST (AffRESTError, FrontendError, RESTError(..)) import Gargantext.Config.REST as REST import Gargantext.Ends (Backend(..)) import Gargantext.Sessions (Session(..)) import Gargantext.Utils.Reactix as R2 import GraphQL.Client.Args (type (==>)) import GraphQL.Client.BaseClients.Urql (UrqlClient, createClient) -- import GraphQL.Client.Operation (OpMutation) import GraphQL.Client.Query (queryFullRes, queryWithDecoder, decodeGqlRes, getFullRes, queryFullRes) --, mutationJson) import GraphQL.Client.SafeQueryName (safeQueryName) import GraphQL.Client.ToGqlString (toGqlQueryString) import GraphQL.Client.Types (class GqlQuery, GqlRes, Client(..), class QueryClient, clientQuery, defQueryOpts, clientMutation, defMutationOpts) import GraphQL.Client.Variables (class VarsTypeChecked, getVarsJson, getVarsTypeNames) import Simple.JSON as JSON import Type.Proxy (Proxy(..)) import Unsafe.Coerce (unsafeCoerce) here :: R2.Here here = R2.here "Gargantext.Components.GraphQL" -- TODO Rewrite this to REST.RESTError type E a = Either JsonDecodeError a -- | Run a graphQL query with a custom decoder gqlQuery :: forall client schema query returns a b queryOpts mutationOpts . QueryClient client queryOpts mutationOpts => GqlQuery schema query returns -- VarsTypeChecked query => => JSON.ReadForeign returns --(queryOpts -> queryOpts) -> => Client client schema a b -> String -> query -> REST.AffRESTError returns gqlQuery c@(Client client) queryNameUnsafe q = do let opts = defQueryOpts client json <- clientQuery opts client queryName (getVarsTypeNames q <> toGqlQueryString q) (getVarsJson q) pure $ parseGQLJson json where queryName = safeQueryName queryNameUnsafe -- | Run a graphQL mutation with a custom decoder gqlMutation :: forall client schema query returns qOpts mOpts a b . QueryClient client qOpts mOpts => GqlQuery schema query returns => VarsTypeChecked query => JSON.ReadForeign returns => Client client a schema b -> Proxy schema -> String -> query -> REST.AffRESTError returns gqlMutation c@(Client client) _ queryNameUnsafe q = do let opts = defMutationOpts client json <- clientMutation opts client queryName (getVarsTypeNames q <> toGqlQueryString q) (getVarsJson q) liftEffect $ here.log2 "[gqlMutation] json" json pure $ parseGQLJson json where queryName = safeQueryName queryNameUnsafe parseGQLJson json = -- NOTE 'json' contains interesting data, in particular the gql operation, error, etc case json .? "error" of -- what 'decodeGqlRes' does is take 'json.data' and decode it using our custom decode function Nothing -> lmap (REST.ReadJSONError <<< unsafeCoerce) (decodeGqlRes decodeFn json) Just err -> -- Surely, an error must be returned, but which one? Left $ case err .? "graphQLErrors" of Nothing -> REST.CustomError "[gqlQuery] no 'graphQLErrors'" Just gqlErrors -> case A.head gqlErrors of Nothing -> REST.CustomError "[gqlQuery] empty 'graphQLErrors'" Just gqlErr -> case gqlErr .? "extensions" of Nothing -> REST.CustomError "[gqlQuery] graphQLError no 'extensions'" Just ext -> case (Foreign.unsafeToForeign >>> JSON.read $ ext) of Left _err -> do -- OK so maybe we don't have "type" in error, try to parse the message only case (Foreign.unsafeToForeign >>> JSON.read $ gqlErr :: Either Foreign.MultipleErrors { message :: String }) of Left err' -> REST.CustomError $ "[gqlQuery] don't know how to parse error: " <> show err' Right { message } -> REST.CustomError message Right err' -> REST.FE err' where decodeFn = Foreign.unsafeToForeign >>> JSON.read >>> lmap toJsonError toJsonError :: Foreign.MultipleErrors -> JsonDecodeError toJsonError = unsafeCoerce -- map ForeignErrors to JsonDecodeError as you wish getClient :: Session -> Effect (Client UrqlClient Schema Mutation Void) getClient (Session { token, backend: Backend b }) = createClient { headers, url: b.baseUrl <> "/gql" } where headers = [ ARH.RequestHeader "Authorization" $ "Bearer " <> token , ARH.RequestHeader "X-Garg-Error-Scheme" $ "new" ] queryGql :: forall query returns . GqlQuery Schema query returns => JSON.ReadForeign returns => Session -> String -> query -> REST.AffRESTError returns queryGql session name q = do client <- liftEffect $ getClient session gqlQuery (client :: Client UrqlClient Schema Mutation Void) name q mutationGql :: forall query returns . GqlQuery Mutation query returns => JSON.ReadForeign returns => Session -> String -> query -> REST.AffRESTError returns mutationGql session name q = do client <- liftEffect $ getClient session gqlMutation (client :: Client UrqlClient Schema Mutation Void) (Proxy :: Proxy Mutation) name q -- mutationJson (defQueryOpts client) (client :: Client UrqlClient Schema Mutation Void) name q -- Schema type Schema = { annuaire_contacts :: { contact_id :: Int } ==> Array AnnuaireContact , context_ngrams :: { context_id :: Int, list_id :: Int } ==> Array String , contexts :: { context_id :: Int, node_id :: Int } ==> Array GQLCTX.NodeContext , contexts_for_ngrams :: { corpus_id :: Int, ngrams_terms :: Array String } ==> Array GQLCTX.Context , imt_schools :: {} ==> Array GQLIMT.School , languages :: {} ==> Array GQLNLP.Language , node_parent :: { node_id :: Int, parent_type :: String } ==> Array GQLNode.Node -- TODO: parent_type :: NodeType , nodes :: { node_id :: Int } ==> Array GQLNode.Node , nodes_corpus :: { corpus_id :: Int } ==> Array GQLNode.Corpus , user_infos :: { user_id :: Int } ==> Array UserInfo , users :: { user_id :: Int } ==> Array User , team :: { team_node_id :: Int } ==> Team , tree :: { root_id :: Int } ==> TreeFirstLevel , tree_branch :: { node_id :: Int } ==> BreadcrumbInfo } type Mutation = { update_user_info :: UserInfoM ==> Int , update_user_pubmed_api_key :: UserPubmedAPIKeyM ==> Int , update_user_epo_api_user :: UserEPOAPIUserM ==> Int , update_user_epo_api_token :: UserEPOAPITokenM ==> Int , delete_team_membership :: TeamDeleteM ==> Array Int , update_node_context_category :: GQLCTX.NodeContextCategoryM ==> Array Int }