anyone here implemented Dialog flow fullfilment on graphql server? How do you handle it? Do you handle fulfillment as a mutation or you implement it as a separate rest endpoint?
I am able to expose my local server using ngrok but I am not sure how to go about setting up the fulfillment. I had separated my DF code from GraphQL code such that the DF module only exposes the methods that handle event and text queries to Dialog flow:
// df/index.js
module.exports={
text: ()=>{
self=module.exports
// ...
return self.getResult()
},
event: ()=>{
self=module.exports
// ...
return self.getResult()
},
getResult:()=>{
//...
return{
query,
response,
cards,
quickReply
}
}
}
Then this is passed through the graphQL context and exposed to the bot.resolver.js module where respective mutations for handling text and events are defined as shown
// schema/resolvers/bot.resolver.js
module.exports={
// Mutation
Mutation:{
sendText: (parent,args,context) => {
const {df}=context;
const response = df.text(args);
return response;
},
sendEvent: (parent,args,context) => {
const {df}=context;
const response = df.event(args);
return response;
},
},
};
The corresponding graphQL types are defined in bot.type.js as shown:
const { gql } = require('apollo-server-express');
module.exports=gql`
type Course {
id:ID
header:String!
image:String!
description:String
price:String!
}
type Option {
value:String!
payload:String
link:String
}
type QuickReply {
text:String!
options:[Option!]!
}
type Bot {
query:String!,
response:String!
cards:[Course!]
quickReply:QuickReply
}
type Mutation {
sendText(text: String!, userId:String!, parameters:String): Bot!
sendEvent(name: String!, userId:String!, parameters:String): Bot!
}
`;
Please advise where I can write the code below that sets up dialog flow fulfillment
dialogflow-fulfillment setup code
๐Surprisingly it was as simple as writing it as a middleware on my graphQl api.
// import the required dependencies
const express = require('express');
const bodyParser = require('body-parser')
const cors = require('cors');
const { ApolloServer, } = require('apollo-server-express');
// do not forget your graphQl schema definition
const schema = require('./schema');
// we shall also need to import the data source.
// I will assume an array for our data source defined as below
const models ={
Course:[
{id:1, name:'Chatbots',}
{id:2, name:'React',}
{id:3, name:'Express',}
{id:4, name:'GraphQl',}
],
Book:[
{id:1, title:'Fundermentals in Chatbots',courseId:1},
{id:2, title:'Express for Newbies',courseId:3},
{id:3, title:'Advanced AI on Bots',courseId:1},
{id:4, title:'GraphQl Simplified',courseId:4},
]
}
// Import the WebhookClient class
const { WebhookClient } = require('dialogflow-fulfillment');
// Do the graphQl gymnastics ... I find Apollo server 2 just on point.
const server = new ApolloServer(schema);
const path='/'
const port = process.env.PORT || 4000
const app = express(); // we will merge express with the Apollo server 2
// do the express gymnastics ...
app.use(path,cors(),bodyParser.json(),)
// **IT'S HERE THAT WE DEFINE DIALOG FLOW'S WEB-HOOK AS A MIDDLEWARE**
app.use('/webhook', async (request,response,next)=>{
const agent = new WebhookClient({ request, response });
const {parameters}=request.body.queryResult;
const course =parameters['course'];
// ... do the database logic here ...
// eg get the title of available text books for the particular course
// I will assume
const {id} = await models.Course.find(({name})=>name ===course)
const books = await model.Book.filter(({courseId})=>courseId===id)
const booksTitleArray = books.map(({title})=>title)
let titleList = booksTitle.Array.toString();
titleList.replace(',', ' , ') // put space btn comas
titleList.replace(/\,(?=[^,]*$)/, "and")
let intentMap = new Map();
const recommendBooks courses=>{
agent.add(`For ${course}, We use the following books: ${titleList}`);
};
intentMap.set('course.recommended.books',recommendBooks);
agent.handleRequest(intentMap);
next();
})
server.applyMiddleware({ app, path });
app.listen(port,()=>{
console.log( `Apollo Server Running on http://localhost:${port}`)
})
I feel like writing an article on this because I tried looking for help almost everywhere in vain. Incase I get the time to do so, I will provide it in the comments.๐๐๐ค๐คญ
Guys, we should not forget the ngrok magic if we are testing from localhost ๐
Related
I want to get a list with the Elrond's esdt tokens (and balances) from an address (a wallet). I don't have any example, I tried things such as:
const { address, account } = useGetAccountInfo();
const objAddress = new Address(address);
// const data1 = getAccount(address);
const { network } = useGetNetworkConfig();
const proxy = new ProxyProvider(network.apiAddress);
proxy
.getAddressEsdtList(objAddress)
.then(({ returnData }) => {
console.log(returnData);
})
.catch((err) => {
console.error('Unable to call VM query', err);
});
But in the console I get "undefined".
Thanks a lot!
In the latest erdjs version I don't have any getAddressEsdtList function for the provider; what could work is to extend the network providers.
So we know we can extend the available network providers with other functions and we now need to make a request with an address and receive all the tokens that the address helds. api.elrond.com has an endpoint that does this at accounts/{address}/tokens.
As we have an endpoint to make a request to, we can now extend the ApiNetworkProvider, making use of doGetGeneric.
// CustomNetworkProvider.js
import { ApiNetworkProvider } from "#elrondnetwork/erdjs-network-providers";
export class CustomNetworkProvider extends ApiNetworkProvider {
async getTokens(address) {
return await this.doGetGeneric(`accounts/${address}/tokens`);
}
}
// index.js
import {CustomNetworkProvider} from "./CustomNetworkProvider.js";
const getProvider = () => {
return new CustomNetworkProvider('https://api.elrond.com', { timeout: 5000 });
}
const provider = getProvider();
const address = 'erd1rf4hv70arudgzus0ymnnsnc4pml0jkywg2xjvzslg0mz4nn2tg7q7k0t6p';
const tokens = await provider.getTokens(address);
console.log(tokens);
I need help building a GraphQL Api that wraps the ChuckNorris.io API
The API sholud have aQuery type that resolves all Categories
(https://api.chuckmorris.io/jokes/categories)
The Api should have Query type that resolves a random joke given as an argument (https://api.chucknorris.io/jokes/random?category={category})
const express=require('express');
const {ApolloServer,gql}=require('apollo-server-express');
const fetch=require('node-fetch');
const typeDefs=gql`
type Joke{
icon_url:String,
id:String,
url:String
value: String
}
type Category{
animal:String
career:String
celebrity:String
dev:String
explicit:String
fashion:String
food:String
history:String
money:String
movie:String
music:String
political:Strig
religion:String
science:String
sport:String
travel:String
}
type Query{
getCategory(category:String!):Joke
category:Category
}
`
const resolvers={
Query:{
getCategory: async(_,{category})=>{
const response=await fetch(`https://api.chucknorris.io/jokes/random?category=${category}`)
return response.json();
},
category: async(_,{})=>{
const response=await fetch('https://api.chucknorris.io/jokes/categories')
return response.json();
}
}
}
const server= new ApolloServer({typeDefs,resolvers});
const app=express();
server.applyMiddleware({app});
app.listen({port:4000},()=>
console.log('Now browse to http://localhost:4000' + server.graphqlPath)
)
your query for type category should return a list of strings (array)
so
export const typeDefs = gql`
type Joke {
value: String!
id:ID!
icon_url:String!
}
type Query {
getAllCategories:[String!]!
randomJoke(category: String!):Joke
}
`;
for your resolver, you don't need fetch. apollo provides datasources to connect to external REST APIs like the one you have.
so install the npm package "apollo-datasource-rest" and add it to your instance of apollo server like so
const server = new ApolloServer({
typeDefs,
resolvers,
dataSources: ()=>({
jokeinstance : new Jokenorris
})
})
then create the datasource class for Jokenorris and import appropriately or do everything in one src file as you did.
import pkg from "apollo-datasource-rest";
const { RESTDataSource } = pkg;
export class Jokenorris extends RESTDataSource {
constructor() {
super();
this.baseURL = "https://api.chucknorris.io/jokes";
}
async getAllCategories() {
const res = await this.get("categories");
return res;
}
async getRandomJoke({ category }) {
const response = await this.get("random", { category: category });
return response;
}
}
then your resolveer can look like so, you can ignore the exports and imports if you chunked everything in one file
export const resolvers = {
Query: {
allJokeCategories: (_, __, { dataSources }) =>
dataSources.jokeinstance.getAllCategories(),
randomJoke: (_, {category}, {dataSources})=>
dataSources.jokeinstance.getRandomJoke({category:category})
},
};
I'm building a custom endpoint on Strapi. For this endpoint, I need to have the raw body content. Is it possible to obtain it from the ctx variable?
stripe : async(ctx) => {
// Handle the event
const sig = ctx.request.headers['stripe-signature']
let event = null
try {
// ctx.request.body needs to be the original raw body
event = stripe.webhooks.constructEvent(ctx.request.body,sig, endpointSecret)
}catch (e) {
ctx.badRequest(null,e)
return
}
Create a middleware (/config/middleware.js) and update it to the following
module.exports = {
settings: {
cors: {
enabled: true,
},
parser: {
enabled: true,
multipart: true,
includeUnparsed: true,
},
},
};
In the controller (/api/<model>/controllers/<model>.js):
const unparsed = require("koa-body/unparsed.js");
const unparsedBody = ctx.request.body[unparsed];
The official koa-bodyparser package actually does this out of the box. See: https://github.com/koajs/bodyparser#raw-body
Here is a small example:
import Koa from 'koa';
import KoaRouter from '#koa/router';
import koaBodyParser from 'koa-bodyparser';
const app = new Koa();
const router = new KoaRouter();
const stripeCheckout = (ctx, next) => {
const sig = ctx.request.header['stripe-signature'];
let event;
if (!process.env.STRIPE_ENDPOINT_SECRET) {
throw new Error('Missing Stripe endpoint secret.');
}
try {
event = stripe.webhooks.constructEvent(
ctx.request.rawBody,
sig,
endpointSecret: process.env.STRIPE_ENDPOINT_SECRET
);
} catch (err) {
logger('error', err);
ctx.status = 400;
ctx.body = `Webhook Error: ${err.message}`;
return next();
}
// ... do something with the event
if (event.type === 'checkout.session.completed') {
const session = event.data.object;
// ... do something with the checkout session
}
// return a response to acknowledge receipt of the event
ctx.status = 200;
ctx.body = { received: true };
return next();
};
// POST
router.post('/stripe-checkout', stripeCheckout);
app.use(koaBodyParser());
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(port, () => {
logger('log', `โ
Done! Server is listening on http://localhost:${port}`);
});
I'm not sure to understand your needs.
ctx.request.body contains the original body of your request.
After that if you want to send event as response body you can do this like that.
ctx.body = event;
And a warning in your code. You wrote define a const for event and you assign event withe the result of your strapi webhook. You have to define a let variable.
It actually works by switching on "includingUnparsed" in the request environment configuration (config/environments/development/request.json -> parser.includedUnparsed: true).
You can access the unparsed body using the koa-body built-in feature for that:
Some applications require crytopgraphic verification of request bodies, for example webhooks from slack or stripe. The unparsed body can be accessed if includeUnparsed is true in koa-body's options. When enabled, import the symbol for accessing the request body from unparsed = require('koa-body/unparsed.js'), or define your own accessor using unparsed = Symbol.for('unparsedBody'). Then the unparsed body is available using ctx.request.body[unparsed].
koa-body docs
Recently they have deprecated the subscriptionManager. I would like to know how to setup resolvers, define subscribe and execute function.
You will need to upgrade to Apollo 2.0. I have recently done a write-up on how to use Apollo 2.0 since the official docs have not yet been updated.
In short, you have to use apollo-link now on the client and execute and subscribe from the graphql package now get passed directly to SubscriptionServer instead.
You will first need the right packages with the right versions:
npm install --save apollo-client#beta apollo-cache-inmemory#beta apollo-link#0.7.0 apollo-link-http#0.7.0 apollo-link-ws#0.5.0 graphql-subscriptions subscriptions-transport-ws apollo-server-express express graphql graphql-tools body-parser
If you're running Meteor, you might also need:
meteor add apollo swydo:blaze-apollo swydo:graphql webapp
Now for the code, the following was made in Meteor, but it can easily adapt to other server types, like Express. You can also download a working example app.
On the client:
import { ApolloClient } from 'apollo-client';
import { ApolloLink } from 'apollo-link';
import { HttpLink } from 'apollo-link-http';
import WebSocketLink from 'apollo-link-ws';
import Cache from 'apollo-cache-inmemory';
import { getOperationAST } from 'graphql';
const httpUri = 'http://localhost:3000/graphql';
const wsUri = 'ws://localhost:3000/subscriptions';
const link = ApolloLink.split(
operation => {
const operationAST = getOperationAST(operation.query, operation.operationName);
return !!operationAST && operationAST.operation === 'subscription';
},
new WebSocketLink({
uri: wsUri,
options: {
reconnect: true, //auto-reconnect
// // carry login state (should use secure websockets (wss) when using this)
// connectionParams: {
// authToken: localStorage.getItem("Meteor.loginToken")
// }
}
}),
new HttpLink({ uri: httpUri })
);
const cache = new Cache(window.__APOLLO_STATE);
const client = new ApolloClient({
link,
cache
});
On the server:
import { WebApp } from 'meteor/webapp'; // Meteor-specific
import { execute, subscribe } from 'graphql';
import { SubscriptionServer } from 'subscriptions-transport-ws';
import { createApolloServer, addCurrentUserToContext } from 'meteor/apollo'; // specific to Meteor, but you can always check out the Express implementation
import { makeExecutableSchema } from 'graphql-tools';
import resolvers from './resolvers'; // your custom resolvers
import typeDefs from './schema.graphql'; // your custom schema
// make schema executable
const schema = makeExecutableSchema({
typeDefs,
resolvers
});
// any additional context you use for your resolvers, if any
const context = {};
// start a graphql server with Express handling a possible Meteor current user
// if you're not using Meteor, check out https://github.com/apollographql/apollo-server for instructions on how to create a server in pure Node
createApolloServer({
schema,
context
}, {
// // enable access to GraphQL API cross-domain (requires NPM "cors" package)
// configServer: expressServer => expressServer.use(cors())
});
// create subscription server
// non-Meteor implementation here: https://github.com/apollographql/subscriptions-transport-ws
new SubscriptionServer({
schema,
execute,
subscribe,
// // on connect subscription lifecycle event
// onConnect: async (connectionParams, webSocket) => {
// // if a meteor login token is passed to the connection params from the client,
// // add the current user to the subscription context
// const subscriptionContext = connectionParams.authToken
// ? await addCurrentUserToContext(context, connectionParams.authToken)
// : context;
// return subscriptionContext;
// }
}, {
server: WebApp.httpServer,
path: '/subscriptions'
});
resolvers.js
import { withFilter } from 'graphql-subscriptions'; // will narrow down the changes subscriptions listen to
import { PubSub } from 'graphql-subscriptions';
import { People } from '../imports/api/collections'; // Meteor-specific for doing database queries
const pubsub = new PubSub();
const resolvers = {
Query: {
person(obj, args, context) {
const person = People.findOne(args.id);
if (person) {
// Mongo stores id as _id, but our GraphQL API calls for id, so make it conform to the API
person.id = person._id;
delete person._id;
}
return person;
}
},
Mutation: {
updatePerson(obj, args, context) {
// You'll probably want to validate the args first in production, and possibly check user credentials using context
People.update({ _id: args.id }, { $set: { name: args.name, eyeColor: args.eyeColor, occupation: args.occupation } });
pubsub.publish("personUpdated", { personUpdated: args }); // trigger a change to all subscriptions to this person
// Note: You must publish the object with the subscription name nested in the object!
// See: https://github.com/apollographql/graphql-subscriptions/issues/51
return args;
}
},
Subscription: {
personUpdated: {
// See: https://github.com/apollographql/graphql-subscriptions#channels-mapping
// Take a look at "Channels Mapping" for handling multiple create, update, delete events
// Also, check out "PubSub Implementations" for using Redis instead of PubSub
// PubSub is not recommended for production because it won't work if you have multiple servers
// withFilter makes it so you can only listen to changes to this person instead of all people
subscribe: withFilter(() => pubsub.asyncIterator('personUpdated'), (payload, args) => {
return (payload.personUpdated.id===args.id);
})
}
}
};
export default resolvers;
schema.graphql
enum EyeColor {
brown
blue
green
hazel
}
type Person {
id: ID
name: String
eyeColor: EyeColor
occupation: String
}
type Query {
person(id: ID!): Person
}
type Mutation {
updatePerson(id: ID!, name: String!, eyeColor: EyeColor!, occupation: String!): Person
}
type Subscription {
personUpdated(id: ID!): Person
}
schema {
query: Query
mutation: Mutation
subscription: Subscription
}
A full write-up about this can be found in this Medium post: How to get Apollo 2.0 working with GraphQL + subscriptions.
An example app demonstrating how to use Apollo 2.0 with a GraphQL server + subscriptions can be found here: meteor-apollo2
How can i go about testing the publishing/subscriptions of my graphql schema, without using apollo?
Is it possible to just do it with subscriptions-transport-ws ?
My SubscriptionServer is setup with default settings, i'm able to see the publish happen by just calling a mutation query with graphql.
I've setup a websocket connection using my express server
const WebSocket = require('ws');
const myServer = require('../../bin/www');
const wsServer = new WebSocket.Server({
server: myServer
});
const client = new SubscriptionClient(`ws://localhost:3000/subscriptions`, {}, WebSocket);
client.subscribe(
{
query: `subscription roomSubscription(placeId: "someid") {
user {
id,
name
}
}`,
},
(error, result) => {
console.log({error, result});
}
);
const query = `
mutation {
roomAddOrUpdate(placeId: "some id") {
user {
name
}
}
}`;
const result = await graphql(schema, query, {}, req);
nothing seems to console out in client.subscribe function, not sure if i'm going at this the right way. You can see my setup here https://github.com/farzd/firstsight/blob/master/bin/www