Facing throttle (Http 429) error when calling Graph API in the loop - sharepoint

I am developing a portal for meeting room booking similar to the one available on Power Apps. I want to call graph API to get the availability of each rooms in the tenet at the filtered time provided by the user. But it only provides result for 3-4 rooms and HTTP 429 error for the rest of them.
I have even tried it using debounce but it only gives 1 result. Below is my code snippet
private async _getRooms(item: RoomListInfo): Promise<void> {
this.setState({ rooms: [] });
await this.props.context.msGraphClientFactory
.getClient("3")
.then((client: MSGraphClientV3): void => {
// Get user information from the Microsoft Graph
client
.api(
"places/" + item.emailAddress + "/microsoft.graph.roomlist/rooms"
)
.version("v1.0")
.get((err, res: any) => {
// handle the response
if (err) {
console.log("Error: ", err);
return;
}
// Map the JSON response to the output array
res.value.map((item: any) => {
this._allRooms.push({
displayName: item.displayName,
emailAddress: item.emailAddress,
capacity: item.capacity,
id: item.id,
availability: this._getAvailability(item),
});
});
// Update the component state accordingly to the result
this.setState({
rooms: this._allRooms,
});
});
});
}
private _getAvailability = debounce((item) => {
const start = this.state.selectedDate;
const end = this.state.selectedDate;
start.setUTCHours(this.state.selectedStart.getUTCHours());
start.setUTCMinutes(this.state.selectedStart.getUTCMinutes());
end.setUTCHours(this.state.selectedEnd.getUTCHours());
end.setUTCMinutes(this.state.selectedEnd.getUTCMinutes());
console.log(this.state.selectedStart, this.state.selectedEnd, start, end);
const apiMail = {
Schedules: [item.emailAddress],
StartTime: {
dateTime: this.formattedDateForAvailability(this.state.selectedStart),
timeZone: "Central Standard Time",
},
EndTime: {
dateTime: this.formattedDateForAvailability(this.state.selectedEnd),
timeZone: "Central Standard Time",
},
availabilityViewInterval: "30",
};
this.props.context.msGraphClientFactory
.getClient("3")
.then((client: MSGraphClientV3): void => {
client
.api("me/calendar/getschedule")
.version("v1.0")
.post(apiMail)
.then((res) => {
console.log(res);
res.value.map((x: any) => {
console.log("Availability: ", x);
if (x.availabilityView !== "0") {
console.log("Busy found: ", item.emailAddress);
return false;
}
});
});
});
return true;
}, 500);
How to get result for all the rooms without causing 429 error?

You will need to create on the code a back-off rule to avoid getting throttled.
The limits for the service should be on the page of the API.

It looks like you're using the exact same date/time for both the START and END filter on your call.
const start = this.state.selectedDate;
const end = this.state.selectedDate;
As a result, I believe Graph is trying to return your entire calendar schedule which results in too many API calls on the back end thus producing the 429 throttling errors you're seeing.
Try using a small date range like a single day for example.

Related

Sequelize not retrieving all data after insert

I have noticed that my backend is not retrieving the expected data after an insert.
In my React application, I have one function which inserts data into the database and after getting a response, a new request is sent to update the current component state with the newly fetched data.
All my functions are using await/async and in the backend, all transactions are correctly used and committed in order.
My client is calling the following endpoints:
-POST: api/ticket ( INSERT AN ITEM)
-GET: api/ticket (GET ALL ITEMS)
Here is what the backend is showing which looks correct to me, the problem is that in the 'SELECT' statement, the inserted item is not retrieved.
The transactions are started from two different routes but I don't see why it should be an issue.
In addition, I tried to change the AddItem function to output the same findAll statement which is called when using the GET method and the data returned are correct.
So why if I separate these two flows I do not get all the items? I always need to refresh the page to get the added item.
START TRANSACTION;
Executing (a9d14d5c-c0ac-4821-9b88-293b086debaa): INSERT INTO `messages` (`id`,`message`,`createdAt`,`updatedAt`,`ticketId`,`userId`) VALUES (DEFAULT,?,?,?,?,?);
Executing (a9d14d5c-c0ac-4821-9b88-293b086debaa): COMMIT;
Executing (9ee9ddaa-294e-41d1-9e03-9f02a2737030): START TRANSACTION;
Executing (9ee9ddaa-294e-41d1-9e03-9f02a2737030): SELECT `ticket`.`id`, `ticket`.`subject`, `ticket`.`status`, `ticket`.`createdAt`, `ticket`.`updatedAt`, `ticket`.`deletedAt`, `ticket`.`userId`, `messages`.`id` AS `messages.id`, `messages`.`message` AS `messages.message`, `messages`.`sender` AS `messages.sender`, `messages`.`createdAt` AS `messages.createdAt`, `messages`.`updatedAt` AS `messages.updatedAt`, `messages`.`deletedAt` AS `messages.deletedAt`, `messages`.`ticketId` AS `messages.ticketId`, `messages`.`userId` AS `messages.userId`, `messages->user`.`id` AS `messages.user.id`, `messages->user`.`firstname` AS `messages.user.firstname`, `messages->user`.`surname` AS `messages.user.surname`, `messages->user`.`email` AS `messages.user.email`, `messages->user`.`password` AS `messages.user.password`, `messages->user`.`stripeId` AS `messages.user.stripeId`, `messages->user`.`token` AS `messages.user.token`, `messages->user`.`birthDate` AS `messages.user.birthDate`, `messages->user`.`status` AS `messages.user.status`, `messages->user`.`confirmationCode` AS `messages.user.confirmationCode`, `messages->user`.`createdAt` AS `messages.user.createdAt`, `messages->user`.`updatedAt` AS `messages.user.updatedAt`, `messages->user`.`deletedAt` AS `messages.user.deletedAt` FROM `tickets` AS `ticket` LEFT OUTER JOIN `messages` AS `messages` ON `ticket`.`id` = `messages`.`ticketId` AND (`messages`.`deletedAt` IS NULL) LEFT OUTER JOIN `users` AS `messages->user` ON `messages`.`userId` = `messages->user`.`id` AND (`messages->user`.`deletedAt` IS NULL) WHERE (`ticket`.`deletedAt` IS NULL);
Executing (9ee9ddaa-294e-41d1-9e03-9f02a2737030): COMMIT;
-- POST '/api/ticket
exports.addMessage = async (req, res) => {
try {
const result = await sequelize.transaction(async (t) => {
var ticketId = req.body.ticketId;
const userId = req.body.userId;
const message = req.body.message;
const subject = req.body.subject;
// Validate input - If new ticket, a subject must be provided
if (!ticketId && !subject) {
return res
.status(400)
.send({ message: "New ticket must have a subject" });
}
// Validate input - If ticket exists, userId and message must be provided
if (!userId && !message && ticketId) {
return res
.status(400)
.send({ message: "UserID and message are required" });
}
// Create ticket is no ticketID was provided
if (!ticketId) {
const [ticket, created] = await Ticket.findOrCreate({
where: {
subject: subject,
userId: userId,
},
transaction: t,
});
ticketId = ticket.id;
}
// Create a new message object
const messageObject = await db.message.create(
{
message: message,
userId: userId,
ticketId: ticketId,
},
{ transaction: t }
);
// Output message object
return res.send(messageObject);
});
} catch (err) {
console.log(err);
return res.status(500).send({
message:
err.message || "Some error occurred while creating the ticket message.",
});
}
};
-- GET: api/ticket
exports.findAll = async (req, res) => {
try {
const result = await sequelize.transaction(async (t) => {
const tickets = await db.ticket.findAll(
{
include: [{ model: db.message, include: [db.user] }],
},
{ transaction: t }
);
tickets.forEach((ticket) => {
console.log(JSON.stringify(ticket.messages.length));
});
return res.send(tickets);
});
} catch (err) {
console.log(err);
res.status(500).send({
message: err.message || "Some error occurred while retrieving Tickets.",
});
}
};
You sent a response to a client before the transaction actually was committed. You just need to move res.send(messageObject); outside the transaction call.
You can try to look what's going on in the current version of your code if you add several console.log with messages to see what the actual order of actions is (I mean a couple of messages in POST (the last statement inside transaction and after transaction before res.send) and at least one at the beginning of GET).
Actually if the transaction was rolled back you'd send an uncommited and already removed object/record that I suppose is not your goal.

Wait until previous query is complete before running the next one

The code below forms part of a websocket that connects to a third party API and receives messages quite frequently from it, currently there are about 3 messages that all appear in very quick succession and I was wondering how I could make sequelize wait until executing one query before the next
this.client.ws.on(WebSocketEvent.ON_MESSAGE, async (message) => {
if (message.type == 'received') {
await db.Order.create({
symbol: message.product_id,
orderId: message.order_id,
price: message.funds,
side: message.side,
orderedAt: message.time,
sold: false
}).catch(err => {
console.log(err);
process.exit();
});
} else if (message.type == 'match') {
await db.Order.update({
volume: message.size
}, {
where: {
orderId: message.taker_order_id
}
}).catch(err => {
console.log(err);
});
} else if (message.type == 'done') {
console.log(message);
await db.Order.update({
completeAt: message.time
}, {
where: {
orderId: message.order_id
}
}).catch(err => {
console.log(err);
});
}
});
Basically what should happen is a message should come through with the type received and that creates a row in the Order table, a split second later a match message should come through and theoretically it should wait for the previous query to run before updating the previously created order, although this is not the case and the model is not updated.
I've thought about simply adding a delay to the code blocks below the match and done message types but would there be any other way to implement what I am looking for?

Is my Paypal checkout flow Angular-Node secure?

I'm developing a paypal checkout using the 'basic Smart Payment Buttons integration' and integrating it with server Node installing the 'checkout-server-sdk'.
I followed the documentations:
https://developer.paypal.com/docs/checkout/reference/server-integration/set-up-transaction/
https://github.com/paypal/Checkout-NodeJS-SDK
where they suggest to:
'createOrder' starting from the client and calling the server
generating on the server an orderID and return it to the client
'onApprove' send to the server the orderID and approve it on the server
return back to the client the response
I don't think it is a good flow.
Someone could:
start the payment
so the app create the order on the server taking the shoppingcart from db and elaborate a totalPrice of 100euros.
generete the orderID and send it back to the client
instead of approve this order, a 'bad user' could, in some way, send to the server another orderID that could correspond to a lower price (2euros)
so he could approve the payment of 2 euros
So I don't understand why we need to make the checkout jumping more times from client to server.
Or maybe am i doing something wrong on my checkoutflow ?
unfortunately I feel the Paypal documentation so unclear.
checkout.component.html
<!-- * here there is a form where i get shipment info, invoice info and so on ->
<!-- * PAYPAL SMART BUTTONS -->
<div>
<div #paypal></div>
</div>
checkout.component.ts
onFormSubmit() {
this.isFormSubmitted = true;
// set paypal settings and show the paypal buttons
this.paypalSetting(this.shippmentInfo, this.invoiceRequired, this.invoice, this.addressInvoice);
}
async paypalSetting(shipment, invoiceRequired, invoice, addressInvoice) {
await paypal
.Buttons({
style: {
size: 'responsive',
label: 'pay',
},
experience: {
input_fields: {
no_shipping: 1,
},
},
createOrder: async (data, actions) => {
console.log('CREATE ORDER -->');
var paypalOrderId;
//generate new order
await this.apiService.newOrder().toPromise().then(
(res) => {
console.log('ON CREATE: SUCCESSFULLY CREATED')
paypalOrderId = res.order.paypalOrderId;
// ????? someone here could change 'paypalOrderId' with another value !!!!
//I also would like to return the 'paypalOrderId' only here !!
},
(err) => {
console.log('ON CREATE: ERROR: ' + err);
// how should i manage this error ? i should skip the flow to onError but HOW ?
}
);
return paypalOrderId;
},
onApprove: async (data, actions) => {
console.log('APPROVE ORDER -->');
var paypalOrderId = data.orderID;
console.log('ON APPROVE: save the order on server/DB')
await this.apiService.saveOrder(shipment, invoiceRequired, invoice, addressInvoice, paypalOrderId).toPromise().then(
(res) => {
console.log('ON APPROVE: ORDER APPROVED')
this.isPaid = true;
//if isPaid i can show a 'success page'
},
(err) => {
console.log('ON APPROVE: ERROR: ' + err);
this.isPaid = false;
}
);
},
onError: (err) => {
console.log('ON ERROR: ' + err);
},
})
.render(this.paypalElement.nativeElement);
}
Node api.js
//* paypal
const paypal = require('#paypal/checkout-server-sdk');
const payPalClient = require('../paypalManager');
router.post('/newOrder', tokenManager.verifyAccessToken, async function (req, res, next) {
const idUser = req.userId;
// I get the shoppingcart of the user 'idUser'
// i calculate the total price
var totalPrice;
//* Call PayPal to set up a transaction
let order;
const request = new paypal.orders.OrdersCreateRequest();
request.prefer("return=representation");
request.requestBody({
intent: 'CAPTURE',
purchase_units: [{
description: 'payment ecc..', /
amount: {
currency_code: 'EUR',
value: totalPrice
}
}],
application_context: {
brand_name: "brand",
shipping_preference: 'NO_SHIPPING',
},
});
let response = await payPalClient.client().execute(request);
order = response;
const paypalOrderId = order.result.id;
// return a successful response to the client with the order ID
return res.json({
status: 200,
order: {
paypalOrderId: paypalOrderId,
},
message: "Paypal order sucessfully created",
});
});
router.post('/saveOrder', tokenManager.verifyAccessToken, async function (req, res, next) {
const idUser = req.userId;
var paypalOrderId = req.body.paypalOrderId;
try {
connection.beginTransaction(async () => {
try {
// here i insert all the checkout infos in DB
// confirm the queries executions
connection.commit(async function (err) {
if (err) {
//return connection.rollback(function () {
connection.rollback(function () {
return next(createError.Unauthorized("Sql query error: " + err)); //! or error.message
});
}
//* here i send the Emails to confirm the checkout
//* capture/approve the order
console.log('CAPTURING THE ORDER')
var request = new paypal.orders.OrdersCaptureRequest(paypalOrderId);
request.requestBody({});
// Call API with your client and get a response for your call
let response = await payPalClient.client().execute(request);
//*response
return res.json({
status: 200,
message: "Paypal sucessfully approved",
});
});// end commit
} catch (error) {
connection.rollback(function () {
return next(createError.Unauthorized("Sql query error " + error)); //! or error.message
});
}
});// end transaction
} catch (error) {
return next(error);
}
});
Node paypalManager.js
'use strict';
/**
* PayPal Node JS SDK dependency
*/
const checkoutNodeJssdk = require('#paypal/checkout-server-sdk');
/**
* Returns PayPal HTTP client instance with environment that has access
* credentials context. Use this instance to invoke PayPal APIs, provided the
* credentials have access.
*/
function client() {
return new checkoutNodeJssdk.core.PayPalHttpClient(environment());
}
/**
* Set up and return PayPal JavaScript SDK environment with PayPal access credentials.
* This sample uses SandboxEnvironment. In production, use LiveEnvironment.
*/
function environment() {
let clientId = process.env.PAYPAL_CLIENT_ID;
let clientSecret = process.env.PAYPAL_CLIENT_SECRET;
return new checkoutNodeJssdk.core.SandboxEnvironment(
clientId, clientSecret
);
}
module.exports = {
client: client,
prettyPrint: prettyPrint
};
The reason you are "jumping" between the client and the server, is the approval by the payer has to happen on the client. The payer cannot give their approval on your server, they are not sitting on your server. They are using a client browser.
Regarding:
a 'bad user' could, in some way, send to the server another orderID that could correspond to a lower price (2euros)
If this happens, your server should reject the undesired transaction, and not proceed with it. That's the point of having a server. Nothing happens unless your server OKs it.

Stripe - Update default card

I am trying to allow the user to update their default payment method after they add it. I am getting this in Firebase Functions: Error: No such source: card_1EhmibFZW9pBNLO2aveVfEm6.
This leads me to believe that I need to pass default_source a src_XXX... id rather than a card_XXX... id. Anyone have an idea on this?
Firebase Function:
// Update Stripe default card based on user choice
exports.updateDefaultSource = functions.firestore
.document("users/{userId}")
.onUpdate(async (change, context) => {
const newValue = change.after.data();
const previousValue = change.before.data();
console.log("previousValue.default_source: "+previousValue.default_source)
console.log("newValue.default_source: "+newValue.default_source)
if (
previousValue.default_source &&
newValue.default_source !== previousValue.default_source
) {
// this triggers on every update to profile (more overhead), can we reduce this?
try {
console.log("newValue.default_source: "+newValue.default_source)
const response = await stripe.customers.update(
previousValue.customer_id,
{ default_source: newValue.default_source },
(err, customer) => {
console.log(err);
}
);
return console.log("Response from Stripe update: " + response);
} catch (error) {
console.log(error);
await change.ref.set(
{ error: userFacingMessage(error) },
{ merge: true }
);
return reportError(error, { user: context.params.userId });
}
}
});
Firebase Function logs after I add the second Card to account:
Looks like this error solved itself, not 100% sure on how, but my guess is it had to do with Redux and/or Redux Persist not having everything loaded into the store.
My main question was answered by #hmunoz on whether or not the default_source accepted the card_123 type, which it does.

How to make a block of code work synchronously

Here is my code :
server.get(url_prefix + '/user/:user_id/photos', function(req, res, next) {
if (!req.headers['x-session-id']) {
res.send({
status: {
error: 1,
message: "Session ID not present in request header"
}
})
} else {
User.findOne({
session_id: req.headers['x-session-id']
}, function(err, user) {
if (user) {
var user_id = req.params.user_id
Album.find({userId : user_id})
.populate('images')
.exec(function (err, albums) {
if (albums) {
albums.forEach(function(album, j) {
var album_images = album.images
album_images.forEach(function(image, i) {
Like.findOne({imageID : image._id, userIDs:user._id}, function(err,like){
if(like){
albums[j].images[i].userLike = true;
}
})
})
})
return res.send({
status: {
error: 0,
message: "Successful"
},
data: {
albums: albums
}
})
} else
return notify_error(res, "No Results", 1, 404)
})
}
else {
res.send({
status: {
error: 1,
message: "Invalid Session ID"
}
})
}
})
}
})
I am trying to add a extra value (albums[j].images[i].userLike = true;) to my images array, which is inside album array.
The problem is return res.send({ send the data before we get response from the foreach
How can I make it work, so that return should happen only after foreach has completed all the iteration
You will have to wait with invoking res.send until you fetched all the likes for all the images in each of the albums. E.g.
var pendingImageLikes = album_images.length;
album_images.forEach(function(image, i) {
Like.findOne({imageID : image._id, userIDs:user._id}, function(err,like){
if (like) {
albums[j].images[i].userLike = true;
}
if (!--pendingImageLikes) {
// we fetched all likes
res.send(
// ...
);
}
});
You might need to special case for album_images.length === 0.
Also, this does not take into account that you have multiple albums with multiple images each. You would have to delay res.send there in a very similar way to make this actually work. You might want to consider using a flow control library like first (or any other of your preference, just search for "flow control library") to make this a bit easier.
Also, you might want to consider not relying on semicolon insertion and manually type your semicolons. It prevents ambiguous expressions and makes the code easier to read.
Since you need your code to wait until all of the find operations have completed, I'd suggest you consider using the async package, and specifically something like each (reference). It makes using async loops cleaner, especially when dealing with MongoDB documents and queries. There are lots of nice features, including the ability to sequentially perform a series of functions or waterfall (when you want to perform a series, but pass the results from step to step).
> npm install async
Add to your module:
var async = require("async");
Your code would look something like this:
albums.forEach(function(album, j) {
async.each(album.images, function(album, done) {
Like.findOne({imageID: image._id, userIDs:user._id}, function(err, like){
if(!err && like){
albums[j].images[i].userLike = true;
}
done(err); // callback that this one has finished
})
})
}, function (err) { // called when all iterations have called done()
if (!err) {
return res.send({
status: {
error: 0,
message: "Successful"
},
data: {
albums: albums
}
});
}
return notify_error(res, "No Results", 1, 404);
});
});

Resources