Doing a basic inquiry chatbot app on dialogflow using firebase as our database and for some reason, the code works like when it wants to.
Majority of the time, it doesn't work, it keeps providing this error "Not available"
But it actually did work twice with no changes to the code, which leads me to think that it is a database issue, if so, is there any way to solve it?
The code and the database will be below.
The code is currently being hard coded into "Animation" but it still does not work.
function getCourseMotto(agent){
if (action === 'CD_CMotto') {
var FullTimecourse = agent.parameters.Course;
//For course
//const product = request.body.queryResult.parameters.motto.trim();
const ref = db.ref(`CourseCategory/Full-Time/Animation/CourseMotto`);
//const ref = db.ref('Course Category/Full-Time/Animation');
return
ref.once('value').then((snapshot) => {
const product = snapshot.val();
if (product === null) {
response.json({
fulfillmentText: `No data found.`
});
return ;
}
else{
response.json({
fulfillmentText: `The course motto ${FullTimecourse} is ${product}`,
source: action
});
return ;
}
}).catch((err) => {
response.json({
fulfillmentText: `I don't know what is it error`
});
return;
});
we have also noticed that when the app connects to the database within 11ms, it works, but it usually takes much longer than that, which is probably the reason why it does not.
Related
I have been playing around with ExpressJS I normally use FastAPI. I can't seem to generate an error using Supabase.
I have this endpoint
app.delete('/api/delete-book/:id', cors(corsOptions), async (req, res) => {
const {data, error} = await supabase
.from('books-express')
.delete()
.match({id: req.params.id})
if (error) {
res.status(400).send({message: `ERROR! ${error.message}`})
}
if (data)
res.send({
message: `Book ID ${req.params.id} has been deleted from the database`,
})
})
This works when it comes to deleting a book via an ID. However if I enter an invalid ID I get the data if block firing.
There is no book with an ID of 222 in the database, I would expect the error to fire but its just null
Any ideas here?
This is expected behaviour; not matching any rows is not considered an error condition in postgres.
If you'd like to check if any rows were deleted, you can use something akin to (on supabase-js 2.x):
const { data, error } = await supabase.from('books-express')
.delete()
.match({id: req.params.id})
.select() // not needed on 1.x libs
if (error || data.length === 0) {
res.status(400).send({...})
}
I have a cloud function that runs a simple firestore query by doc ID. The function logs show a ~7 second delay around running the query.
Here's the function code:
exports.cancelUnpaidOrder = functions.https.onCall(async (orderId, context) => {
console.log("in cancelUnpaidOrder");
const uid = context.auth.uid;
console.log("awaiting get order doc");
const orderSnap = await admin
.firestore()
.collection("order")
.doc(orderId)
.get();
console.log("getting order doc data");
const order = orderSnap.data();
console.log("asserting");
assert.ok(order.userId == uid && !order.paid);
console.log("awaiting update order doc");
await admin.firestore().collection("order").doc(orderId).update({
canceled: true,
cancelMsg: "canceled by user before pay",
open: false,
});
console.log("finished cancelUnpaidOrder");
});
Here are the logs, notice the very long query time between 9:52:29 and 9:52:36:
9:52:37.003 Function execution took 9292 ms, finished with status code: 200
9:52:37.001 finished cancelUnpaidOrder
9:52:36.487 awaiting update order doc
9:52:36.487 asserting
9:52:36.486 getting order doc data
9:52:29.558 awaiting get order doc
9:52:29.558 in cancelUnpaidOrder
9:52:29.558 Callable request verification passed
9:52:27.712 Function execution started
Function zone: us-central1.
Firestore zone: nam5 (us-central)
Thanks.
EDIT:
Firestore document size is 6.57K
Note that, like a Cloud Functions instance, the Admin SDK, experiences a cold-start. While your code may be just a simple query, the first API request also triggers an exchange of authentication tokens between the service account client and the authentication server before your query is executed. For any given functions instance, this performance hit should only happen once, unless that particular instance manages to stay alive for long enough (usually an hour) where it will reauthenticate. If multiple instances are fired up to soak up demand, they will each have this hit to performance just once.
Minimal data transfer
You can help your function's performance by using a field mask on the returned data. For the Admin SDK, this is done using select(). Using a field mask for your document drops its size down from 6.57KB to just 51B.
exports.cancelUnpaidOrder = functions.https.onCall(async (orderId, context) => {
console.log("in cancelUnpaidOrder");
const uid = context.auth.uid;
const orderRef = admin.firestore() // <- new: store reference in variable
.collection("order")
.doc(orderId);
console.log("awaiting get order doc");
console.time("getDoc");
const orderSnap = await orderRef
.select('userId', 'paid') // <- new: only fetch userId and paid, ignore other fields
.get();
console.timeEnd("getDoc"); // <-- new: calculate timings locally
console.log("getting order doc data");
const order = orderSnap.data();
console.log("asserting");
assert.ok(order.userId === uid && !order.paid); // <- new: use === for user ID checks!
console.log("awaiting update order doc");
console.time("setDoc");
await orderRef.update({
canceled: true,
cancelMsg: "canceled by user before pay",
open: false,
});
console.timeEnd("setDoc"); // <-- new: calculate timings locally
console.log("finished cancelUnpaidOrder");
});
Note: canceled should probably be corrected to cancelled.
Handling errors
Rather than use assert.ok, you should use a helper function that does the same job, but throws a HttpsError instead so that your front end can receive a meaningful error instead of one with a error code of "internal". In a similar fashion, converting the Firestore calls to throw a HttpsError as well may also be desirable.
These helper functions would look like:
// Typescript: function assertOk(condition: unknown, message?: string): asserts condition {
function assertOk(condition, message = undefined) {
if (!condition) {
throw new functions.https.HttpsError(
"failed-precondition",
message || "failed-precondition"
);
}
}
function throwAsHttpsError(error, message = undefined) {
let err, errCode = (!!error && typeof error === "object" && error.code) || "internal";
try {
// attempt to use original error's code
err = new functions.https.HttpsError(
errCode,
message || "INTERNAL"
);
} catch {
// unexpected error code, use fallback code of "internal"
err = new functions.https.HttpsError(
"internal",
message || errCode || "INTERNAL"
);
}
throw err;
}
Applying them gives:
exports.cancelUnpaidOrder = functions.https.onCall(async (orderId, context) => {
console.log("in cancelUnpaidOrder");
const uid = context.auth.uid;
const orderRef = admin.firestore()
.collection("order")
.doc(orderId);
console.log("awaiting get order doc");
console.time("getDoc");
const orderSnap = await orderRef
.select('userId', 'paid')
.get()
.catch(e => throwAsHttpsError(e, "failed to read order"));
console.timeEnd("getDoc");
console.log("getting order doc data");
const order = orderSnap.data();
console.log("asserting");
assertOk(order.userId === uid, "User mismatch");
assertOk(!order.paid, "Order already paid");
console.log("awaiting update order doc");
console.time("setDoc");
await orderRef
.update({
canceled: true,
cancelMsg: "canceled by user before pay",
open: false,
})
.catch(e => throwAsHttpsError(e, "failed to cancel order"));
console.timeEnd("setDoc");
console.log("finished cancelUnpaidOrder");
});
Testing timings
When testing Cloud Function timings, you should make sure to test calling the function twice. The first time is to test cold-start performance of spinning up a new Cloud Function instance. The second time is to attempt to reuse the same instance used by the first call once it has gone idle. Either call may or may not experience a cold-start.
const cancelUnpaidOrder = firebase.functions().httpsCallable('cancelUnpaidOrder');
function testCall(orderId) {
const tag = "Call for Order #" + orderId;
console.time(tag);
return cancelUnpaidOrder(orderId1)
.then(() => {
console.timeEnd(tag);
console.log(tag + ": success");
}, (err) => {
console.timeEnd(tag);
console.error(tag + ": error -> ", err);
});
}
const orderId1 = /* first test ID to be cancelled */;
const orderId2 = /* second test ID to be cancelled */;
// attempt to invoke a cold-start
testCall(orderId1);
// attempt to catch the cooled down instance, timings may need adjustment
setTimeout(() => testCall(orderId2), 15000);
Hi I'm trying to read a users document stored on Firestore using Firebase Functions. Each user has a unique document with extra data that cannot be stored on Firebase Auth. The document name is the user UID.
But I can't access the doc when I'm trying to read it on my callable function.
Code to create doc when user is created:
exports.createdacc = functions.auth.user().onCreate(user => {
console.log('User created', user.phoneNumber);
return admin.firestore().collection('users').doc(user.uid).set({
number: user.phoneNumber,
verified: false,
});
});
Callable function to read that doc so I can make some decisions
exports.checkVerification = functions.https.onCall((data, context) => {
if (!context.auth){
throw new functions.https.HttpsError('unauthenticated');
}
console.log('user is ', context.auth.uid);
const user = admin.firestore().collection('users').doc(context.auth.uid);
user.get().then(doc => {
//temp code -- Not working
console.log('data read');
if (doc.get().verified){
console.log('verified');
} else {
console.log('not verified');
}
return "success";
}).catch(error => {
throw new functions.https.HttpsError('internal');
});
});
Why cant I read the doc? Nothing inside there executes.
Try to use data() at callback of user.get()
user.get().then(doc => {
//you get user doc value by using data()
const userData = doc.data();
// then you can use all properties from userData
const verified = userData.verified;
});
You don't return the promise returned by user.get().then(...);: your Cloud Function may be cleaned up before the asynchronous work is complete and the response sent back to the front-end.
Note that doing doc.get().verified is incorrect: as you will see in the doc, you need to pass the field path of a specific field to this method. So either you do doc.get("verified") or you can do doc.data().verified;.
Therefore the following should work:
exports.checkVerification = functions.https.onCall((data, context) => {
if (!context.auth) {
throw new functions.https.HttpsError('unauthenticated');
}
console.log('user is ', context.auth.uid);
const user = admin.firestore().collection('users').doc(context.auth.uid);
return user.get().then(doc => {
console.log('data read');
if (doc.get("verified") {
console.log('verified');
} else {
console.log('not verified');
}
return "success";
}).catch(error => {
throw new functions.https.HttpsError('internal');
});
});
In addition, note that you may throw an error if the user document does not exist and return a specific error to the front-end, i.e. not the generic internal one (maybe not-found, see the list of possible codes).
I have seen, on occasion, that information coming in to the function via context and data are actually JSON, and not strictly a standard Javascript object. In a similar issue of matching (in my case, a customClaim on the context.auth.token), I had to do something like:
JSON.parse(JSON.stringify(context.auth.token.customCLaim))
They behave like an object (i.e. I can call/assign context.auth.token.customClaim), but results from a console.log are different.
console.log(context.auth.token.customCLaim);
//prints {"userID": "1234567890"}
console.log(JSON.parse(JSON.stringify(context.auth.token.customClaim)));
//prints {userID: "1234567890"}
Subtle, but it tripped me up in a few authentication cases.
I use Node.js and back4app.com
I try to update the user object. Therefore I have read a lot and found this promissing documentation:
let progressId = "xyz";
let userId = "12354"; //aka objectId
const User = new Parse.User();
const query = new Parse.Query(User);
// Finds the user by its ID
query.get(userId).then((user) => {
// Updates the data we want
user.set('progressId', progressId);
// Saves the user with the updated data
user.save()
.then((response) => {
console.log('Updated user', response);
})
.catch((error) => {
console.error('Error while updating user', error);
});
});
But there also is a warning. It states:
The Parse.User class is secured by default, you are not able to invoke save method unless the Parse.User was obtained using an authenticated method, like logIn, signUp or current
How would this look like in code?
My solution
Well, I got it to work. While I figured it out, I have found some small show stoppers. I list it for anyone it may concern.
Thanks #RamosCharles I added the Master Key in Parse._initialize. Only with that .save(null, {useMasterKey: true}) works. Take notice, without null it also won't work.
That's my working code:
let progressId = "xyz";
const User = Parse.Object.extend('User'); //instead of const User = new Parse.User();
const query = new Parse.Query(User);
query.equalTo("objectId", '123xyz');
query.get(userId).then((userObj) => {
// Updates the data we want
userObj.set('progressId', progressId);
// Saves the user with the updated data
userObj.save(null, {useMasterKey: true}).then((response) => {
console.log('Updated user', response);
}).catch((error) => {
console.error('Error while updating user', error);
});
});
Now I'm wondering
why my working code is different from documentation?
how secure is my code? And what is to do to get it more secure?
Yes, their API Reference is very helpful! On this section, there's a "try on JSFiddle" button, have you already seen that?
To update a user object, you must use the Master Key. On the frontend, it's not recommended, and it's better to create a cloud code function and call it on your frontend. However, for test purposes, you can keep using the API Reference, but on JSFiddle, you need to do some changes, here is their sample code, but with the adjustments:
Parse.serverURL = 'https://parseapi.back4app.com';
Parse._initialize('<your-appID-here>', '<your-JSKey-here>', '<Your-MasterKey-here>');
const MyCustomClass = Parse.Object.extend('User');
const query = new Parse.Query(MyCustomClass);
query.equalTo("objectId", "<object-ID-here>");
query.find({useMasterKey: true}).then((results) => {
if (typeof document !== 'undefined') document.write(`ParseObjects found: ${JSON.stringify(results)}`);
console.log('ParseObjects found:', results);
}, (error) => {
if (typeof document !== 'undefined') document.write(`Error while fetching ParseObjects: ${JSON.stringify(error)}`);
console.error('Error while fetching ParseObjects', error);
});
You'll need to insert the "_" before the "initialize" in your "Parse._initialize" and insert the Master Key in your query as I did on the query.find.
I am fetching data from a node from Firebase Database and then after doing some calculations I am updating it.
I am using Firebase SDK in node js to perform this task.
Here is the code:
app.get("/setBookedSpots", function (request, response) {
console.log("booked spots called");
var verifierEmail = request.query.verifierEmail;
var toBeBookedSpot = request.query.bookSpot;
console.log(toBeBookedSpot);
admin.auth().getUserByEmail(verifierEmail)
.then(function (userRecord) {
console.log("Successfully fetched user data:", userRecord.toJSON());
var verifierId = userRecord.uid;
var refToUserInformation = db.ref("user/" + verifierId);
refToUserInformation.child("Society_Name").on("value", function (snapshot) {
console.log(snapshot.val());
var SocietyName = snapshot.val();
refToSocietyBookingStatus = db.ref("Societies/" + SocietyName + "/Society_Parking_Details/Society_Parking_Status");
refToSocietyBookingStatus.child("Booked_Spots").on("value", function (snapshot) {
var Booked_Spots = snapshot.val();
console.log(Booked_Spots);
console.log("to be booked spot", toBeBookedSpot);
Booked_Spots = Booked_Spots.toString() + "," + toBeBookedSpot;
console.log("after booked spot", Booked_Spots);
refToSocietyBookingStatus.update({
"Booked_Spots": Booked_Spots
})
response.send({ "msg": "success" });
})
})
});
});
I am getting the booked spots and the updating them based on the request parameter.
When I hit it, it goes in infinite callback. What I mean to say is it keeps on appending recursively as there is no loop which makes it to crash.
I tried using a different variable for the database reference so that it might not be calling the parent event again but still the same problem.
Here is the service call url:
http://localhost:8080/setBookedSpots?verifierEmail=kemarikun#gmail.com&bookSpot=A7
you may use instead of response.send({ "msg": "success" }); to change this one response.send(JSON.stringify({ msg: 'success'}));
After trying a lot, I came to know that the on("value") event was being executed as I was updating the same node from which I just retrived value and thus went in recursion and kept on calling as it went on updating.
Here is the solution to that: replaced on("value") with once("value")
Making it execute just once.