Is it possible to get the raw body on a custom made endpoint on strapi? - strapi

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

Related

Getting list with the Elrond's esdt tokens (and balances) from an address (a wallet)

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);

DIalogflow-Fullfilment GraphQL

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 ๐Ÿ˜

ms-rest-nodeauth (azure-sdk-for-js) : Error: credentials argument needs to implement signRequest method

Trying to follow the samples from https://github.com/Azure/ms-rest-nodeauth
When passing authresponse to a client to generate a client to ping resources, I end up getting:
Error: credentials argument needs to implement signRequest method
I am trying to read through the documents to see if I need to sign the token's I am getting back from the SDK/Azure AD, but the documentation for the new SDK doesnt show anything
Figured it out, have to call .credentials on the authresponse
Adding the code, using #azure/arm-billing, in case the full code file is helpful.
// auth.json
// Create auth file with Azure CLI:`az ad sp create-for-rbac --sdk-auth > auth.json`
{
"clientId": "",
"clientSecret": "",
"subscriptionId": "",
"tenantId": "",
"activeDirectoryEndpointUrl": "https://login.microsoftonline.com",
"resourceManagerEndpointUrl": "https://management.azure.com/",
"activeDirectoryGraphResourceId": "https://graph.windows.net/",
"sqlManagementEndpointUrl": "https://management.core.windows.net:8443/",
"galleryEndpointUrl": "https://gallery.azure.com/",
"managementEndpointUrl": "https://management.core.windows.net/"
}
// index.js
const msRest = require("#azure/ms-rest-js");
const msRestAzure = require("#azure/ms-rest-azure-js");
const msRestNodeAuth = require("#azure/ms-rest-nodeauth");
const armBilling = require("#azure/arm-billing");
const path = require('path');
// list billing information
const lists = async (client) => {
try {
let lists = [];
const enrollmentAccounts = await client.enrollmentAccounts.list();
lists.push(enrollmentAccounts);
const billingPeriods = await client.billingPeriods.list();
lists.push(billingPeriods);
const invoices = await client.invoices.list();
lists.push(invoices);
return lists;
} catch (err) {
console.log(err);
throw (err);
}
}
// sample auth file created from Azure CLI - removed PII
const authenticationFile = path.join(__dirname, "./auth.json");
const options = {
filePath: authenticationFile
};
// get subscriptionId from auth file
const subscriptionIdFromAuthFile = require('./auth.json').subscriptionId;
// authenticate and getting billing information
msRestNodeAuth.loginWithAuthFileWithAuthResponse(options).then(async (response) => {
console.log("authenticated");
// --- CHANGE response parameter to -> response.credentials
const client = new armBilling.BillingManagementClient(response.credentials, subscriptionIdFromAuthFile);
console.log("client created");
const results = await lists(client);
console.log(`The result is:${JSON.stringify(results)}`);
}).catch((err) => {
console.error(err);
});

Apollo GraphQL server; setting context to handle requests triggered by a fired subscription

I understand how to set the context object when creating a GraphQL server e.g.
const app = express();
app.use(GRAPHQL_URL, graphqlExpress({
schema,
context: {
foo: 'bar'
},
}));
so that the context object is passed to my resolvers when handling an incoming request.
However I'm not seeing this context object when the resolvers are triggered by a subscription (i.e. a client subscribes to a GraphQL subscription, and defines the shape of the data to be sent to them when the subscription fires); in that case the context appears to be an empty Object.
Is there way to ensure that my context object is set correctly when resolvers are called following a PubSub.publish() call?
I guess you are using the package subscription-transport-ws. In that case it is possible to add a context value in different execution steps.
See API. Two possible scenarios
If you have some kind of authentication. You could add a viewer in the context at the onConnect execution step. This is done at the first connection to the websocket and wont change until the connection is closed and opened again. See example.
If you want to add a context more dynamically you can add a kind of middleware before the execute step.It could look like this:
const middleware = (args) => new Promise((resolve, reject) => {
const [schema, document, root, context, variables, operation] = args;
context.foo = "bar"; // add something to context
resolve(args);
})
subscriptionServer = SubscriptionServer.create({
schema: executable.schema,
subscribe,
execute: (...args) => middleware(args).then(args => {
return execute(...args);
})
}, {
server: websocketServer,
path: "/graphql",
}, );
Here is my solution:
You can pass the context and do the authentication for graphql subscription(WebSocket )like this:
const server = new ApolloServer({
typeDefs,
resolvers,
context: contextFunction,
introspection: true,
subscriptions: {
onConnect: (
connectionParams: IWebSocketConnectionParams,
webSocket: WebSocket,
connectionContext: ConnectionContext,
) => {
console.log('websocket connect');
console.log('connectionParams: ', connectionParams);
if (connectionParams.token) {
const token: string = validateToken(connectionParams.token);
const userConnector = new UserConnector<IMemoryDB>(memoryDB);
let user: IUser | undefined;
try {
const userType: UserType = UserType[token];
user = userConnector.findUserByUserType(userType);
} catch (error) {
throw error;
}
const context: ISubscriptionContext = {
// pubsub: postgresPubSub,
pubsub,
subscribeUser: user,
userConnector,
locationConnector: new LocationConnector<IMemoryDB>(memoryDB),
};
return context;
}
throw new Error('Missing auth token!');
},
onDisconnect: (webSocket: WebSocket, connectionContext: ConnectionContext) => {
console.log('websocket disconnect');
},
},
});
You can pass the context argument of resolver using pubsub.publish method in your resolver like this:
addTemplate: (
__,
{ templateInput },
{ templateConnector, userConnector, requestingUser }: IAppContext,
): Omit<ICommonResponse, 'payload'> | undefined => {
if (userConnector.isAuthrized(requestingUser)) {
const commonResponse: ICommonResponse = templateConnector.add(templateInput);
if (commonResponse.payload) {
const payload = {
data: commonResponse.payload,
context: {
requestingUser,
},
};
templateConnector.publish(payload);
}
return _.omit(commonResponse, 'payload');
}
},
Now, we can get the http request context and subscription(websocket) context in
your Subscription resolver subscribe method like this:
Subscription: {
templateAdded: {
resolve: (
payload: ISubscriptionPayload<ITemplate, Pick<IAppContext, 'requestingUser'>>,
args: any,
subscriptionContext: ISubscriptionContext,
info: any,
): ITemplate => {
return payload.data;
},
subscribe: withFilter(templateIterator, templateFilter),
},
},
async function templateFilter(
payload?: ISubscriptionPayload<ITemplate, Pick<IAppContext, 'requestingUser'>>,
args?: any,
subscriptionContext?: ISubscriptionContext,
info?: any,
): Promise<boolean> {
console.count('templateFilter');
const NOTIFY: boolean = true;
const DONT_NOTIFY: boolean = false;
if (!payload || !subscriptionContext) {
return DONT_NOTIFY;
}
const { userConnector, locationConnector } = subscriptionContext;
const { data: template, context } = payload;
if (!subscriptionContext.subscribeUser || !context.requestingUser) {
return DONT_NOTIFY;
}
let results: IUser[];
try {
results = await Promise.all([
userConnector.findByEmail(subscriptionContext.subscribeUser.email),
userConnector.findByEmail(context.requestingUser.email),
]);
} catch (error) {
console.error(error);
return DONT_NOTIFY;
}
//...
return true;
}
As you can see, now we get the subscribe users(who establish the WebSocket connection with graphql webserver) and HTTP request user(who send the mutation to graphql webserver) from subscriptionContext and HTTP request context.
Then you can do the rest works if the return value of templateFilter function is truthy, then WebSocket will push message to subscribe user with payload.data, otherwise, it won't.
This templateFilter function will be executed multiple times depending on the count of subscribing users which means it's iterable. Now you get each subscribe user in this function and does your business logic to decide if push WebSocket message to the subscribe users(client-side) or not.
See github example repo
Articles:
GraphQL Subscription part 1
GraphQL Subscription part 2
If you're using Apollo v3, and graphql-ws, here's a docs-inspired way to achieve context resolution:
const wsContext = async (ctx, msg, args) => {
const token = ctx.connectionParams.authorization;
const currentUser = await findUser(token);
if(!currentUser) throw Error("wrong user token");
return { currentUser, foo: 'bar' };
};
useServer(
{
schema,
context: wsContext,
}
wsServer,
);
You could use it like so in your Apollo React client:
import { GraphQLWsLink } from '#apollo/client/link/subscriptions';
import { createClient } from 'graphql-ws';
const wsLink = new GraphQLWsLink(createClient({
url: 'ws://localhost:4000/subscriptions',
connectionParams: {
authorization: user.authToken,
},
}));

Angular 2 HTTP Progress bar

Is there currently a way within Angular 2 to retrieve the progress (i.e. percentage done) of an ajax call, using the angular2/http module?
I use the following code to make my HTTP calls:
let body = JSON.stringify(params);
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
this.http.post(url, body, options)
.timeout(10000, new Error('Timeout exceeded during login'))
.toPromise()
.then((res) => {
...
}).catch((err) => {
...
});
The goal is to write a synchronisation system. The post will return a lot of data, and I want to give the user an indication on how long the syncing will take.
Currently (from v. 4.3.0, when using new HttpClient from #ngular/common/http) Angular provides listening to progress out of the box. You just need to create HTTPRequest object as below:
import { HttpRequest } from '#angular/common/http';
...
const req = new HttpRequest('POST', '/upload/file', file, {
reportProgress: true,
});
And when you subscribe to to request you will get subscription called on every progress event:
http.request(req).subscribe(event => {
// Via this API, you get access to the raw event stream.
// Look for upload progress events.
if (event.type === HttpEventType.UploadProgress) {
// This is an upload progress event. Compute and show the % done:
const percentDone = Math.round(100 * event.loaded / event.total);
console.log(`File is ${percentDone}% uploaded.`);
} else if (event instanceof HttpResponse) {
console.log('File is completely uploaded!');
}
});
More info here.
You could leverage the onprogress event provided by XHR (see this plunkr: http://plnkr.co/edit/8MDO2GsCGiOJd2y2XbQk?p=preview).
This allows to get hints about the progress of the download. This isn't supported out of the box by Angular2 but you can plug it by extended the BrowserXhr class:
#Injectable()
export class CustomBrowserXhr extends BrowserXhr {
constructor(private service:ProgressService) {}
build(): any {
let xhr = super.build();
xhr.onprogress = (event) => {
service.progressEventObservable.next(event);
};
return <any>(xhr);
}
}
and override the BrowserXhr provider with the extended:
bootstrap(AppComponent, [
HTTP_PROVIDERS,
provide(BrowserXhr, { useClass: CustomBrowserXhr })
]);
See this question for more details:
Angular2 / RxJS - updating variable after getting data from Http observable
When you make http cals in angular2, it returns an Observable of type Response, this response is created inside class called XHRConnection where all the magic happens.
The XHRConnection builds the response by listening to XMLHttpRequest's load event, this means it will return one response at the end of the request.
Now to be able to alter this behavior we need to make our connection class listen to the progress event.
So we need to create custom Connection class, to handle the response as we see fit.
I did it this way,
Take note that my php API returns multi response in a single request and this responses are plain strings.
my_backend.ts
import {Injectable} from "angular2/core";
import {Observable} from "rxjs/Observable";
import {Observer} from "rxjs/Observer";
import {Connection,ConnectionBackend} from "angular2/src/http/interfaces";
import {ReadyState, RequestMethod, ResponseType} from "angular2/src/http/enums";
import {ResponseOptions} from "angular2/src/http/base_response_options";
import {Request} from "angular2/src/http/static_request";
import {Response} from "angular2/src/http/static_response";
import {BrowserXhr} from "angular2/src/http/backends/browser_xhr";
import {Headers} from 'angular2/src/http/headers';
import {isPresent} from 'angular2/src/facade/lang';
import {getResponseURL, isSuccess} from "angular2/src/http/http_utils"
export class MyConnection implements Connection {
readyState: ReadyState;
request: Request;
response: Observable<Response>;
constructor(req: Request, browserXHR: BrowserXhr, baseResponseOptions?: ResponseOptions) {
this.request = req;
this.response = new Observable<Response>((responseObserver: Observer<Response>) => {
let _xhr: XMLHttpRequest = browserXHR.build();
_xhr.open(RequestMethod[req.method].toUpperCase(), req.url);
// save the responses in array
var buffer :string[] = [];
// load event handler
let onLoad = () => {
let body = isPresent(_xhr.response) ? _xhr.response : _xhr.responseText;
//_xhr.respons 1 = "Loading data!"
//_xhr.respons 2 = "Loading data!Ready To Receive Orders."
// we need to fix this proble
// check if the current response text contains the previous then subtract
// NOTE: I think there is better approach to solve this problem.
buffer.push(body);
if(buffer.length>1){
body = buffer[buffer.length-1].replace(buffer[buffer.length-2],'');
}
let headers = Headers.fromResponseHeaderString(_xhr.getAllResponseHeaders());
let url = getResponseURL(_xhr);
let status: number = _xhr.status === 1223 ? 204 : _xhr.status;
let state:number = _xhr.readyState;
if (status === 0) {
status = body ? 200 : 0;
}
var responseOptions = new ResponseOptions({ body, status, headers, url });
if (isPresent(baseResponseOptions)) {
responseOptions = baseResponseOptions.merge(responseOptions);
}
let response = new Response(responseOptions);
//check for the state if not 4 then don't complete the observer
if(state !== 4){
//this will return stream of responses
responseObserver.next(response);
return;
}
else{
responseObserver.complete();
return;
}
responseObserver.error(response);
};
// error event handler
let onError = (err: any) => {
var responseOptions = new ResponseOptions({ body: err, type: ResponseType.Error });
if (isPresent(baseResponseOptions)) {
responseOptions = baseResponseOptions.merge(responseOptions);
}
responseObserver.error(new Response(responseOptions));
};
if (isPresent(req.headers)) {
req.headers.forEach((values, name) => _xhr.setRequestHeader(name, values.join(',')));
}
_xhr.addEventListener('progress', onLoad);
_xhr.addEventListener('load', onLoad);
_xhr.addEventListener('error', onError);
_xhr.send(this.request.text());
return () => {
_xhr.removeEventListener('progress', onLoad);
_xhr.removeEventListener('load', onLoad);
_xhr.removeEventListener('error', onError);
_xhr.abort();
};
});
}
}
#Injectable()
export class MyBackend implements ConnectionBackend {
constructor(private _browserXHR: BrowserXhr, private _baseResponseOptions: ResponseOptions) {}
createConnection(request: Request):MyConnection {
return new MyConnection(request, this._browserXHR, this._baseResponseOptions);
}
}
And in the app.component.ts
import {Component, provide} from 'angular2/core';
import {HTTP_PROVIDERS,XHRBackend} from 'angular2/http';
import {MyBackend} from './my_backend';
#Component({
selector: 'my-app',
providers: [
HTTP_PROVIDERS,
MyBackend,
provide(XHRBackend, {useExisting:MyBackend})
]
.
.
.
Now calling http.get will return a steam of responses.
#Bartek Chichoki's answer is correct but it was not working for my case.
Adding observe: 'events' did the trick for me
const req = new HttpRequest('POST', '/upload/file', file, {
reportProgress: true,
observe: 'events'
});
Hope it helps
I strongly recomend using this
https://www.npmjs.com/package/angular-progress-http
otherwise messing around with xhr will make you miss sessions cookies and other stuffs
besides it'll be more portable and way easier to implement

Resources