How can I catch errors in my firebase function when setting a document fails? - google-cloud-firestore

I have a firebase cloud function to create a user document with user data whenever a user registers. How would I return an error when the set() fails? Since this is not an http request (an I don't want to use an http request in this case) I have no response. So how would I catch errors?
export const onUserCreated = functions.region('europe-west1').auth.user().onCreate(async user => {
const privateUserData = {
phoneNumber: user.phoneNumber
}
const publicUserData = {
name: 'Nameless'
}
try
{
await firestore.doc('users').collection('private').doc('data').set(privateUserData);
}catch(error)
{
//What do I put here?
}
try
{
await firestore.doc('users').collection('public').doc('data').set(publicUserData);
}catch(error)
{
//What do I put here?
}
});

You can't "return" an error, since the client doesn't even "know" about this function running, there is nobody to respond to.
You can make a registration collection, and in your function make a document there for the current user (using the uid as the document id). In that document, you can put any information you'd like your user to know (status, errors, etc).
So your clients would have to add a listener to this document to learn about their registration.
In your particular code, I think the error is in doc('users'). I guess you meant doc('users/'+user.uid).

Your catch -block will receive errors that occur on your set -call:
try {
await firestore.doc('users').collection('public').doc('data').set(publicUserData);
} catch (error) {
// here you have the error info.
}

Related

Create an account and a document in Firestore at the same time?

I'm using Flutter and Firebase for my app and the following is the code for my register function:
Future registerWithEmailAndPassword(String email, String name, String password) async {
try{
// Creates user account with Firebase Auth:
UserCredential result = await _auth.createUserWithEmailAndPassword(email: email, password: password);
User user = result.user!;
// Creates a new document in Firestore with the uid:
await DatabaseService(uid: user.uid).createUserData(
name: name,
email: email,
);
return _userObjectFromUser(user);
} on FirebaseAuthException catch(e) {
return e;
}
}
It works well. However, I keep wondering if this is the best way to do this... What if the connection gets interrupted after creating the account but before creating the documents in Firestore? What if the creation of the document fails for some reason? Then the user would be in a weird situation where they have an account but no data saved in the database, meaning the app would probably load forever.
So, I wonder: is there a way to create something similar to a batch write that would somehow create an account at the same time as the documents are created?
I guess you shouldn't be concerned about this since the two methods will run on each other, they're in a really small chance of this happening, either both will succeed or both will fail together, however, I can recommend for those cases to listen to the authStateChanges() stream and take an action based on it, combined with using the isNew like this :
// At first, we were not sure that the document exists
bool areWeSureThatTheuserHaveDocument = false;
// we listen to auth changes of new user
FirebaseAuth.instance.authStateChanges().listen((user) {
// we want this method to get triggered only when the user authenticates, we don't want it to get executed when the user signs out
if(user != null && !areWeSureThatTheuserHaveDocument) {
// here we create the document
await DatabaseService(uid: user.uid).createUserData(
name: name,
email: email,
);
// now if the document does exists, it will return true, for future checks on this method it will not be executed
areWeSureThatTheuserHaveDocument = await doesUserDocumentExists(user.uid);
}
});
// this is the check document existence
Future<bool> doesUserDocumentExists(String id) async {
final collection = await FirebaseFirestore.instance.collection("users").get();
return collection.docs.map((doc) => doc.id).contains(id);
}
Actually, if you're willing to implement this code or something similar to it, you might want to know that by this you can make sure 100% of the user has a document in the database, but it will cost you one additional read to check that existence od document.
Since you tagged with google-cloud-functions, doing the create-user-and-write-profile-document would reduce the chances of having the type of interruption that you talk about.
But my approach is typically to either write the profile document each time the onAuthState changed listener for a user gets a value, or to check for the existence of a document at that time and create it if needed.

Mongoose save method seems to not be working

I am trying to create a REST API. I am pretty new to the back end and am just practicing on my own for the time being. For my code, I know it's bad practice to store the plain text password but again, this is completely for practice and will never go live. I will also add encryption at a later point for practice.
My issue is I am not sure why my API does not work. I see where it fails, it fails in the catch block when I try to save a user but I do not get any error to tell me what is wrong exactly, besides the once I force. I have another part on this website that follows almost the exact same logic and it works perfectly but for this one it does not. I have no idea how to solve my issue but after googling I still cannot figure it out. It looks perfectly fine too me, but as mentioned I am pretty new to the backend.
This is my controller function:
const signup = async (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return next(new HttpError('Invalid inputs passed, please check your data', 422));
}
const { name, email, password, places } = req.body;
let existingUser;
try {
existingUser = await User.findOne({email: email}) // finds one document matching our criteria we set
} catch(err) {
const error = new HttpError('Signing up failed, please try again later', 500);
return next(error);
}
if (existingUser) {
const error = new HttpError('User exists already, please login instead', 422);
return next(error);
}
const createdUser = new User({
name,
email,
image: 'https://images.pexels.com/photos/220453/pexels-photo-220453.jpeg?auto=compress&cs=tinysrgb&dpr=3&h=750&w=1260',
password,
places
});
try {
await createdUser.save();
} catch(err) {
const error = new HttpError(
'Signing up failed', 500
);
return next(error);
}
res.status(201).json({user: createdUser.toObject({ getters:true })});
};
I use Postman to send the request to my API endpoint with all of the correct information. Based on what I recieve back it is failing in the try catch block of await createdUser.save()
For anyone who finds this from google this was my solution:
First I suggest you add this into your save method to try and diagnose the problem
await createdUser.save(function(err){
if(err){
console.log(err);
return;
}
});
This help me greatly as it gave me more information on how to solve it.It turns out my problem was because I misspelled a field in my Schema. So extremely simple solution!

How to handle non explicit errors inside sails.js helpers?

I am trying to figure out how the Error handling in Sails.js works. Unfortunatley the code examples in the docs do not cover this use case.
The problem is I keep getting this error:
UsageError: `.intercept()` handler returned `undefined`, but this should never happen.
Regardless, here is a summary of the original underlying error:
Now all I am trying to do is call a helper and if it fails, then I want to catch the error (any), log it and run some code. If I wouldn't be using Sails but normal promises I would have handled it like this:
await helper().catch((err) => { // run some code }
In Sails I should be able to use .intercept() instead of .catch()
My code looks like this:
// ExportController.js
const csv = await sails.helpers.files.convertToCsv(data)
.intercept((err) => {
sails.log.error(err)
req.addFlash('error_messages', 'Error parsing data to csv!')
return res.redirect(`/`);
})
// convert-to-csv.js
if (!Array.isArray(inputs.data)) {
throw new Error('invalid inputs.data type: ' + typeof inputs.data)
};
Now how can I avoid getting this error?
The code examples show only cases where errors that are explicitly added to the exits object are handled, but not for general error handling.
In the docs it says that if the filter argument is
not provided, ALL errors will be intercepted.
Or is that only true for db queries? Because the .intercept() doc section is in that subcategory.
You could use “throw ‘errorCode’;” for example:
Set the exits:
exits {
errorWithCsvFile: {
responseType: 'badRequest'
}
}
const csv = await sails.helpers.files.convertToCsv(data)
.intercept(‘somethingWrongCode’, ‘errorWithCsvFile’)
... // Other handles
.intercept(err => new Error(err))
Alternative:
try {
...
const csv = await sails.helpers.files.convertToCsv(data)
.intercept((err) => {
sails.log.error(err)
req.addFlash('error_messages', 'Error parsing data to csv!')
throw 'badRequest';
})
...
} catch (err) {
sails.log.err(err);
return res.redirect(`/`);
}

How to Catch Error When Data is not Sent on Angularfire when adding data to firebase?

Im using angularfire to save data into my firebase. Here is a quick code.
$scope.users.$add({
Name:$scope.username,
Age:$scope.newage,
Contact: $scope.newcontact,
});
alert('Saved to firebase');
I am successful in sending these data to my firebase however how can I catch an error if these data are not saved successfully? Any ideas?
EDIT
So after implementing then() function.
$scope.users.$add({
Name:'Frank',
Age:'20',
Contact: $scope.newcontact,
}).then(function(ref) {
alert('Saved.');
}).catch(function(error) {
console.error(error); //or
console.log(error);
alert('Not Saved.');
});
When connected to the internet. The then() function is fine. It waits for those data to be saved in firebase before showing the alert.
What I want is for it to tell me that data is not saved. catch error function is not firing when i am turning off my internet connection and submitting those data.
When you call $add() it returns a promise. To detect when the data was saved, implement then(). To detect when saving failed, implement catch():
var list = $firebaseArray(ref);
list.$add({ foo: "bar" }).then(function(ref) {
var id = ref.key;
console.log("added record with id " + id);
list.$indexFor(id); // returns location in the array
}).catch(function(error) {
console.error(error);
});
See the documentation for add().
Update
To detect when the data cannot be saved due to not having a network connection is a very different problem when it comes to the Firebase Database. Not being able to save in this case is not an error, but merely a temporary condition. This condition doesn't apply just to this set() operation, but to all read/write operation. For this reason, you should handle it more globally, by detecting connection state:
var connectedRef = firebase.database().ref(".info/connected");
connectedRef.on("value", function(snap) {
if (snap.val() === true) {
alert("connected");
} else {
alert("not connected");
}
});
By listening to .info/connected your code can know that the user is not connected to the Firebase Database and handle it according to your app's needs.

Apigee push notification - one

Apigee's push notification is documented here.
http://apigee.com/docs/api-baas/content/introducing-push-notifications
I tried this with the js sdk that Apigee provides here http://apigee.com/docs/app-services/content/installing-apigee-sdk-javascript. It looks like only the client can generate a notification to itself?
But I have a scenario where I would like to push notifications to multiple clients from a nodejs job that runs once every hour. Something like this, but from the nodejs sdk not from the js sdk.
var devicePath = "devices;ql=*/notifications";
How do I do this?
As remus points out above, you can do this with the usergrid module (https://www.npmjs.com/package/usergrid).
You are basically trying to construct an API call that looks like this (sending a message by referencing a device):
https://api.usergrid.com/myorg/myapp/devices/deviceUUID/notifications?access_token= access_token_goes_here '{"payloads":{"androidDev":"Hello World!!"}}'
Or like this (sending a message by referencing a user who is connected to a device)
https://api.usergrid.com/myorg/myapp/users/fred/notifications?access_token=access_token_goes_here '{"payloads":{"androidDev":"Hello World!!"}}'
You can do this with code that looks something like this:
var options = {
method:'POST',
endpoint:'devices/deviceUUID/notifications',
body:{ 'payloads':{'androidDev':'Hello World!!'} }
};
client.request(options, function (err, data) {
if (err) {
//error - POST failed
} else {
//data will contain raw results from API call
//success - POST worked
}
});
or
var options = {
method:'POST',
endpoint:'users/fred/notifications',
body:{ 'payloads':{'androidDev':'Hello World!!'} }
};
client.request(options, function (err, data) {
if (err) {
//error - POST failed
} else {
//data will contain raw results from API call
//success - POST worked
}
});
Note: the second call, that posts to the users/username/notifications endpoint assumes that you have already made a connection between the user and their device (e.g. POST /users/fred/devices/deviceUUID).