For some reason that I cannot seem to wrap my brain around, whenever I send an Object over the network, its parameters change from when I send the Object over the network from the server to when I read it back on the client.
Here's the scenario:
I'm testing an API to delete your user account from the database. You send over your authToken and username parameters in a specially formatted Object, the server queries the database, and if found, your user account is deleted. When everything works perfectly, your account, the server sends this string back to you:
{ header: { type: 'success' }, body: {} }
That's known as a plain SuccessEnvelope. However, when I read the returned string from the server, I get this:
{ header: { type: 'error' }, body: {} }
Now, it seems that the type parameter is being changed somewhere, but I have no idea where! Below you'll find the relevant code which handles all of these requests:
server.js
server = net.createServer(function(socket) {
socket.on("data", function(data) {
try {
// Accept Envelope from client
var input = new Envelope(data.toString());
if (input.verify()) { // Make sure client Envelope has correct data
switch (input.header.type) { // Route commands based on Envelope "type"
case "user":
userHandler.parseEnvelope(input, function(err, res) {
if (err) {
socket.write(errors.parseError(err).toString());
} else {
socket.write(res.toString());
}
});
break;
default:
socket.write(new Envelope().toString());
break;
}
} else {
socket.write(errors.parseError(
new errors.MissingHeaderDataError()).toString());
}
} catch (err) {
socket.write(errors.parseError(err).toString());
}
});
});
user-handler.js
// removing some of the unnecessary stuff
case "delete":
// Make sure user provided all necessary information.
if (!header.token || !body.username) {
return callback(new errors.MissingEnvelopeDataError("Missing the 'token' and/or
'username' parameter(s) in header and/or body, respectively"));
} else {
// Delete the user's account. So sad to see them go... :'(
User.findOne({"token": header.token, "username": body.username}, "+token +_id",
function(err, user) {
if (err) {
return callback(err);
} else {
user.remove(function(err) {
if (err) {
return callback(err);
} else {
// Everything went according to plan. Return a SuccessEnvelope to the user.
callback(new SuccessEnvelope());
}
});
}
});
}
break;
Can anyone see what I did wrong? Any help would be fantastic. Thanks in advance!
EDIT: It's probably also a good idea to include my test cases...
deleteUser = {
"header": {
"type": "user",
"method": "delete",
"token": ""
},
"body": {
"username": ""
}
}
describe("Delete user", function() {
it("should return a MissingEnvelopeDataError when deleting a user without a token or
username", function(done) {
connection.write(JSON.stringify(deleteUser));
connection.on("data", function(res) {
res = JSON.parse(res.toString());
res.header.should.have.property("type", "error");
res.header.should.have.property("errorType", "MissingEnvelopeDataError");
done();
});
});
it("should return a SuccessEnvelope when deleting a user", function(done) {
deleteUser.header.token = user.token;
deleteUser.body.username = user.username;
connection.write(JSON.stringify(deleteUser));
connection.on("data", function(res) {
res = JSON.parse(res.toString());
console.dir(res);
res.header.should.have.property("type", "success");
// res.body.should.be.empty
(function() {
if (Object.keys(res.body).length == 0) {
return true;
} else {
return false;
}
})();
done();
});
});
});
The user variable you see here is just a simple object, which stores all of the user info.
As usual, I'm an idiot and don't follow my own API.
The callback function that's being passed takes two parameters: err and response. I was forgetting to pass null as my error, so the code was doing exactly what it should have been doing. DOH! Thanks for the responses, everyone!
Related
I'm creating a rest api for CRUD operations using Sequelize and MySql. I'm using a controller to run an update on a PATCH request to update fields of a product. It technically works, but I feel like there is a more elegant way to handle this.
Sequelize's update method will return an array of objects depending on the results. Array[0] is the number of rows affected by the update (should just be one in my case, as I'm updating by id). Array[1] will return an object with details about the update as well as all the old values and new values. Here's how I'm handling that currently:
//products.controller.js
//Update a single product using id (PUT/PATCH)
const patch = (req, res) => {
const id = req.params.id;
Product.update(req.body, { where: { id }, individualHooks: true })
.then((rowsAffected) => {
//Item not found
if (Object.entries(rowsAffected[1]).length === 0) {
res.status(404).send({
success: false,
status: 404, //Not found
message: `Product with id ${id} not found. Update failed.`,
});
return;
}
//if rowsAffected[0] === 1 then success
if (rowsAffected[0] === 1) { //row changed
res.status(200).send({
success: true,
status: 200,
message: `Product updated.`,
id: id,
payload: req.body,
});
} else {
// if rowsAffected[0] !== 1 then it failed.
res.status(200).send({
success: false,
status: 200, //Not Modified
message: `No fields have changed. Product not updated.`,
});
}
})
.catch((err) => {
res.status(500).send({
success: false,
status: 500,
message:
err.message || "Something went wrong while updating the product.",
});
});
}
As you can see, first I'm checking to see if the the update function returns the product details (meaning it successfully found it in the database). If not then sending 404. Then I check the affected rows. If 1 then success, if 0 then nothing changed. Finally I'm catching any server errors.
I feel like there is a better way rather than having to break down the update function's return (like Object.entries(rowsAffected[1]).length === 0)
This is ok if this is the only way you can check the effects of the update. What I can suggest is putting an abstraction above it.
First thing that checking (rowsAffected[0] === 1) does not make much sense, since the update is idempotent and you end up with the same resource state no matter what the actual values are. If you insist, then I would not pair success: false with a 200 ok status, because failure is failure and it requires an error message and 4xx or 5xx status. So either delete it or convert it into a proper error. Hard to find such a status code, but maybe using 409 conflict is ok in these cases https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 though I would just remove this part of the code. I keep it for the sake of the example.
As of the success and status properties in the body, they don't make much sense either, because they travel in the header, and it is evident from the HTTP standard that 2xx means success, 4xx and 5xx means error. So I would remove those too.
If you don't want to support detailed error codes and exception types and parameters, then just send the error messages and the body can be even a string instead of an object.
Sending the err.message to the consumers is a bad idea by unexpected errors. You don't know what you send out. You need to log them and send something general instead. Communicating errors is always a higher abstraction level stuff, many times. As of the Product with id ${id} not found. Update failed. here adding the id is not necessary, because the request contains it.
So atm. the code looks like this:
const patch = (req, res) => {
const id = req.params.id;
Product.update(req.body, { where: { id }, individualHooks: true })
.then((rowsAffected) => {
if (Object.entries(rowsAffected[1]).length === 0) {
res.status(404).send({message: `Product not found. Update failed.`});
return;
}
//if rowsAffected[0] === 1 then success
if (rowsAffected[0] === 1) { //row changed
res.status(200).send({
message: `Product updated.`,
id: id,
payload: req.body,
});
} else {
res.status(409).send({message: "No fields have changed. Product not updated."});
}
})
.catch((err) => {
res.status(500).send({message: "Something went wrong while updating the product."});
});
}
We can go further by mapping status codes to status messages and extracting the possibly repeating parts of the story into separate functions.
const patch = (req, res) => {
const id = req.params.id;
const statusMessages = {
200: "Product updated."
404: "Product not found. Update failed."
409: "No fields have changed. Product not updated.",
500: "Something went wrong while updating the product."
};
Product.update(req.body, { where: { id }, individualHooks: true })
.then(updateStatusVerification)
.then(successHandler(res, statusMessages, () => {
return {
id: id,
payload: req.body,
};
}))
.catch(apiErrorHandler(res, statusMessages));
}
function successHandler(res, statusMessages, callback){
return function (){
let body = callback();
body.message = statusMessages[200];
res.status(200).send(body);
};
}
function apiErrorHandler(res, statusMessages){
return function (err){
let statusCode = 500;
if (err instanceof NotFoundError)
statusCode = 404;
else if (err instanceof NotUpdatedError)
statusCode = 409;
res.status(statusCode).send({
message: statusMessages[statusCode]
});
};
}
function updateStatusVerification(rowsAffected){
return new Promise((resolve, reject) => {
if (Object.entries(rowsAffected[1]).length === 0)
reject(new NotFoundError);
else if (rowsAffected[0] !== 1)
reject(new NotUpdatedError);
else
resolve();
});
}
class ApiError extends Error {}
class NotFoundError extends ApiError {}
class NotUpdatedError extends ApiError {}
We can move the status messages to the documentation. So you will end up with something like this and some utility functions:
const patch = (req, res) => {
const id = req.params.id;
statusMessages = docs.product.update.statusMessages;
Product.update(req.body, { where: { id }, individualHooks: true })
.then(updateStatusVerification)
.then(successHandler(res, statusMessages, () => {
return {
id: id,
payload: req.body,
};
}))
.catch(apiErrorHandler(res, statusMessages));
}
We can go even further if this is a frequent pattern:
const patch = (req, res) => {
const id = req.params.id;
handleUpdate(
Product.update(req.body, { where: { id }, individualHooks: true }),
() => {id: id, payload: req.body},
docs.product.update.statusMessages
);
}
function handleUpdate(dbUpdatePromise, successCallback, statusMessages){
dbUpdatePromise.then(updateStatusVerification)
.then(successHandler(res, statusMessages, successCallback))
.catch(apiErrorHandler(res, statusMessages));
}
So it can be as abstract as you like, it really depends on your needs and what the current usage allows. You can decide how many and what kind of layers you need based on actual use cases and repetitions.
New to meteor and stripes API I am trying to apply this coupon code using Meteor and stripe.This is for a one time payment with a coupon. However the handleCharge method fires before the process payment method. And I want the Stripe.coupons.retrieve to return a result first before the payment is processed.
Server Method
Meteor.methods({
processPayment( charge, coupon ) {
Stripe.coupons.retrieve(
coupon,
function(err, result) {
if( result ) {
charge.amount = parseInt(charge.amount) - parseInt( charge.amount * coupon.percent_off );
}
}
);
let handleCharge = Meteor.wrapAsync( Stripe.charges.create, Stripe.charges ),
payment = handleCharge( charge );
return payment;
}
});
I've also tried to return a result before the coupon is passed into the processPayment. Then when i try to console.log the result it is always undefined.
checkForCoupon( couponCode ) {
let result = false;
Stripe.coupons.retrieve(
couponCode,
function(err, coupon) {
if( err ) {
result = false;
} else {
result = true;
}
}
);
return result;
}
Meteor.call( 'checkForCoupon', coupon, ( error, response ) => {
if ( error ) {
console.log( error );
} else {
console.log( "Success");
}
});
Any help would be greatly appreciated.
Ok here's a thing, the argument for coupon key in stripe api takes a string which looks like you already have because you are passing that in coupons.retrieve api, so what you'll get from this api is coupon object which is no use to you. So usually in Stripe we already have coupon id before creating subscription which gets passed in Stripe API for discount.
But as you said you are having problem in getting response before running another method, so here I can suggest you to use Async.runSync of meteor.
And another thing is that you can't use coupon in charge.create api, its for subscriptions. So here's how my approach would be with subscription:
Here I'm retreiving coupons object from coupon_id and and then hitting subscriptions API.
On Client:
var data = {
source: "source",
plan: "plan"
}
Meteor.call('processPayment', data, function(err, res) {
if(err)
return;
console.log("Subscription added with coupon");
});
On Server:
Meteor.methods({
var coupon;
'processPayment': function(data) {
check( data, {
source: String,
plan: String
});
Async.runSync(function(done) {
var coupon = Stripe.coupons.retrieve("coupon_id", function(err, coupon) {
if(err) {
return done(null); //done is callback
}
return done(null, coupon);
});
});
//this code will only run when coupon async block is completed
if(coupon !== null) {
data.coupon = coupon.id;
}
var subscription = Async.runSync(function(done) {
Stripe.subscriptions.create(data, function(err, subscription) {
if(err) {
console.log(err);
return done(null, err);
}
return done(null, subscription);
}
);
});
return subscription;
}
});
Hope this helps you and feel free to ask anything in comments, I'll be more than happy to answer.
I have an App using Parse.com as a backend and an external site that acts as my payment gateway. Upon receiving the customer/subscription webhook data from Stripe I wish to lookup the users email so I can then run a Cloud Code function and change their user status to 'paid'
My webhook receiver is:
Parse.Cloud.define("update_user", function(request, response) {
var data = request.params["data"]
var customer = data.object.customer;
response.success'Working' + request);
});
And I am able to get an email back from stripe from the customer ID using:
Parse.Cloud.define("pay", function(request, response) {
Stripe.initialize(STRIPE_SECRET_KEY);
console.log(JSON.stringify(request.params));
Stripe.Customers.retrieve(
customerId, {
success:function(results) {
console.log(results["email"]);
// alert(results["email"]);
response.success(results);
},
error:function(error) {
response.error("Error:" +error);
}
}
);
});
I need help turning this into a complete function that is run on receipt of every webhook from Stripe. I am also struggling with options for fallback if this does not work for whatever reason.
EDIT
Taking parts of the first answer and I now have:
Parse.Cloud.define("update_user", function(request, response) {
Stripe.initialize(STRIPE_SECRET_KEY);
var data = request.params["data"]
var customerId = data.object.customer;
get_stripe_customer(customerId, 100).then(function(stripeResponse) {
response.success(stripeResponse);
}, function(error) {
response.error(error);
});
});
function get_stripe_customer (customerId) {
Stripe.initialize(STRIPE_SECRET_KEY);
return Stripe.Customers.retrieve(
customerId, {
success:function(results) {
console.log(results["email"]);
},
error:function(error) {
}
}
);
};
My knowledge is really falling down on the Promise side of things and also the callback (success:, error, request response) etc further reading would be appreciated.
This is now working
Out of interest I did this:
Parse.Cloud.define("update_user", function(request, response) {
var data = request.params["data"]
var customerId = data.object.customer;
get_stripe_customer(customerId, 100).then(function(stripeResponse) {
return set_user_status(username, stripeResponse);
}).then(function(username) {
response.success(username);
}, function(error) {
response.error(error);
});
});
function get_stripe_customer (customerId) {
Stripe.initialize(STRIPE_SECRET_KEY);
return Stripe.Customers.retrieve(
customerId, {
success:function(results) {
// console.log(results["email"]);
},
error:function(error) {
}
}
);
};
function set_user_status(stripeResponse) {
Parse.Cloud.useMasterKey();
var emailquery = new Parse.Query(Parse.User);
emailquery.equalTo("username", stripeResponse['email']); // find all the women
return emailquery.first({
success: function(results) {
alert('running set_user_status success');
var user = results;
user.set("tier", "paid");
user.save();
},
error:function(error) {
console.log('error finding user');
}
});
};
open to improvements...
EDIT - I (#danh) cleaned it up a bit. A few notes:
used promises throughout. much easier to read and handle errors
get_stripe_customer requires only one param (that 100 was my idea to charge $100)
set_user_status appears to need only user email as param, which apparently is in the stripeResponse
set_user_status returns a promise to save the user. that will be fulfilled with the user object, not the username
be sure you're clear on how to identify the user. stripe apparently provides email address, but in your user query (in set_user_status) you compare email to "username". some systems set username == email. make sure yours does or change that query.
Parse.Cloud.define("update_user", function(request, response) {
var data = request.params["data"]
var customerId = data.object.customer;
get_stripe_customer(customerId).then(function(stripeResponse) {
var email = stripeResponse.email;
return set_user_status(email);
}).then(function(user) {
response.success(user);
}, function(error) {
response.error(error);
});
});
function get_stripe_customer(customerId) {
Stripe.initialize(STRIPE_SECRET_KEY);
return Stripe.Customers.retrieve(customerId).then(function(results) {
// console.log(results["email"]);
return results;
});
};
function set_user_status(email) {
Parse.Cloud.useMasterKey();
var emailquery = new Parse.Query(Parse.User);
emailquery.equalTo("username", email); // find all the women
return emailquery.first().then(function(user) {
user.set("tier", "paid");
return user.save();
}, function(error) {
console.log('error finding user ' + error.message);
return error;
});
}
Did a quick skim of the docs pertaining to stripe, and it looks like the steps are: (1) make a stripe REST-api call from your client side to get a token, (2) pass that token to a cloud function, (3) call stripe from the parse cloud to finish paying. I understand that you'd like to include a (4) fourth step wherein the transaction is recorded in the data for the paying user.
From the client (assuming a JS client):
var token = // we've retrieved this from Stripe's REST api
Parse.Cloud.run("pay", { stripeToken: token }).then(function(result) {
// success
}, function(error) {
// error
});
On the server:
Parse.Cloud.define("pay", function(request, response) {
var user = request.user;
var stripeToken = request.params.stripeToken;
payStripeWithToken(stripeToken, 100).then(function(stripeResponse) {
return updateUserWithStripeResult(user, stripeResponse);
}).then(function(user) {
response.success(user);
}, function(error) {
response.error(error);
});
});
Now we need only to build promise-returning functions called payStripeWithToken and updateUserWithStripeResult.
// return a promise to pay stripe per their api
function payStripeWithToken(stripeToken, dollarAmt) {
Stripe.initialize(STRIPE_SECRET_KEY); // didn't see this in the docs, borrowed from your code
return Stripe.Charges.create({
amount: dollarAmt * 10, // expressed in cents
currency: "usd",
card: stripeToken //the token id should be sent from the client
});
// caller does the success/error handling
}
// return a promise to update user with stripeResponse
function updateUserWithStripeResult(user, stripeResponse) {
var transactionId = // dig this out of the stripeResponse if you need it
user.set("paid", true);
user.set("transactionId", transactionId);
return user.save();
}
I'm using Sails.js to develop a REST API server.
For ease of use and for abstraction sake I would like to throw exceptions inside of my controllers, e.g.:
// api/controllers/TempController.js
module.exports = {
index: function(request, response) {
throw new NotFoundException('Specific user is not found.');
throw new AccessDeniedException('You have no permissions to access this resource.');
throw new SomeOtherException('Something went wrong.');
}
};
How do I catch those exceptions automatically (on a global level) and transform them into a valid JSON response? e.g.:
{
"success": false,
"exception": {
"type": "NotFoundException",
"message": "Specific user is not found."
}
}
Is it the best possible approach to use built-in serverError response in order to handle such exceptions? Or is it better to create some custom middleware? If so, could you provide a simple example?
The unhandled exceptions are passed to the default response in the api/responses/serverError.js as a first argument data.
Here's an example of how such exceptions can be handled:
var Exception = require('../exceptions/Exception.js');
module.exports = function serverError (data, options) {
var request = this.req;
var response = this.res;
var sails = request._sails;
// Logging error to the console.
if (data !== undefined) {
sails.log.error('Sending 500 ("Server Error") response: \n', String(data));
} else {
sails.log.error('Sending empty 500 ("Server Error") response');
}
response.status(500);
if (data instanceof Exception) {
return response.json({
success: false,
exception: {
type: data.constructor.name,
message: data.message
}
});
} else {
return response.json(data);
}
};
When exception is thrown in the controller:
// api/controllers/TempController.js
var NotFoundException = require('../exceptions/NotFoundException.js');
module.exports = {
index: function(request, response) {
throw new NotFoundException('Specific user is not found.');
}
};
This will output the following JSON:
{
"success": false,
"exception": {
"type": "NotFoundException",
"message": "Specific user is not found."
}
}
I have an Angular service that takes in a roleId and userId and assigns the user to that role and make a pointer in User to that role.
app.service('CRUD', function () {
this.addUserToRole = function (roleId, userId) {
// first we have to find the role we're adding to
var query = new Parse.Query(Parse.Role);
return query.get(roleId, {
success: function (role) {
// then add the user to it
var Database = Parse.Object.extend("User");
var query = new Parse.Query(Database);
console.log(role);
return query.get(userId, {
success: function (user) {
console.log(user);
role.getUsers().add(user);
role.save();
// now we need to tell the user that he has this role
console.log(user);
user.attributes.role.add(role);
user.save();
return user;
},
error: function (err) {
return err;
}
});
},
error: function (err) {
console.log(err);
}
});
}
});
I'm getting {"code":206,"error":"Parse::UserCannotBeAlteredWithoutSessionError"} on user.save();
After some research, I arrived at this website. He uses this code snippet as a JS SDK example:
Parse.Cloud.run('modifyUser', { username: 'userA' }, {
success: function(status) {
// the user was updated successfully
},
error: function(error) {
// error
}
});
and mentions something about a useMasterKey() function.
I'm still unsure how to fix this error.
Add
Parse.Cloud.useMasterKey();
at the beginning of your function.
Set it up as a background job. That is the code snip you found I think and a simpler far more secure means of fondling users and roles
https://parse.com/docs/cloud_code_guide#jobs