Is this Transaction valid? - javascript

I am wondering if this transaction is even valid and actually ensuring quantity is being the most up to date.
async function deductQuantity(orders: [Order]) : Promise<boolean> {
try {
let document = await admin.firestore().collection("MenuItems")
orders.forEach(async (order)=> {
let itemDoc = (await document.where(`item.catalogType`, "==", order.catalogType).where(`item.id`, "==", order.item.id))
let get = await itemDoc.get()
get.forEach(async a=> {
const pp = document.doc(a.id)
await admin.firestore().runTransaction(async (t)=> {
const mostRecentDoc = await t.get(pp)
const data = await mostRecentDoc.data()
if (data == undefined){
return
}
const newQuantity = data.item.quantity - order.quantity
await t.update(pp, {[`item.quantity`] : newQuantity})
})
})
})
return true
} catch (error) {
console.log("dum: " + error)
return false
}
}
the part where I do let get = await itemDoc.get(), and get.ForEach, is kind of unnecessary because I know, that it will only return one document that matches the query field, but I need to forEach it in order to get the child component\s. Anyways, is it a valid transaction?

Related

Code works fine when value is hardcoded, but fails when value is dynamic

I'm working on a chrome extension that grabs data from Rate My Professor's GraphQL API and then puts that rating on my universities courses portal. Here's my code:
background.js
const {GraphQLClient, gql} = require('graphql-request');
console.log("background.js loaded");
const searchTeacherQuery = gql`
query NewSearchTeachersQuery($text: String!, $schoolID: ID!)
{
newSearch {
teachers(query: {text: $text, schoolID: $schoolID}) {
edges {
cursor
node {
id
firstName
lastName
school {
name
id
}
}
}
}
}
}
`;
const getTeacherQuery = gql`
query TeacherRatingsPageQuery(
$id: ID!
) {
node(id: $id) {
... on Teacher {
id
firstName
lastName
school {
name
id
city
state
}
avgDifficulty
avgRating
department
numRatings
legacyId
wouldTakeAgainPercent
}
id
}
}
`;
const AUTH_TOKEN = 'dGVzdDp0ZXN0';
const client = new GraphQLClient('https://www.ratemyprofessors.com/graphql', {
headers: {
authorization: `Basic ${AUTH_TOKEN}`
}
});
const searchTeacher = async (professorName, schoolID) => {
console.log("searchTeacher called");
console.log(professorName);
console.log(typeof professorName);
console.log(schoolID);
const response = await client.request(searchTeacherQuery, {
text: professorName,
schoolID
});
if (response.newSearch.teachers === null) {
return [];
}
return response.newSearch.teachers.edges.map((edge) => edge.node);
};
const getTeacher = async (id) => {
const response = await client.request(getTeacherQuery, {id});
return response.node;
};
async function getAvgRating(professorName) {
console.log('1: ', professorName);
const teachers = await searchTeacher(professorName, 'U2Nob29sLTE0OTU=');
console.log(teachers);
const teacherID = teachers[0].id;
const teacher = await getTeacher(teacherID);
const avgRating = teacher.avgRating;
console.log(teacher);
console.log(avgRating);
return avgRating;
}
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
console.log('received message from content script:', request);
console.log('test:', request.professorName);
getAvgRating(request.professorName).then(response => {
sendResponse(response);
});
return true;
});
and here's content.js:
const professorLinks = document.querySelectorAll('td[width="15%"] a');
professorLinks.forEach(link => {
const professorName = link.textContent;
console.log(professorName);
chrome.runtime.sendMessage({ professorName }, (response) => {
console.log(response);
if (response.error) {
link.insertAdjacentHTML('afterend', `<div>Error: ${response.error}</div>`);
} else {
link.insertAdjacentHTML('afterend', `<div>${response}/5</div>`);
}
});
});
Now, if I set professorName to a fixed value, like this:
async function getAvgRating(professorName) {
const teachers = await searchTeacher('Hossein Kassiri', 'U2Nob29sLTE0OTU=');
console.log(teachers);
const teacherID = teachers[0].id;
const teacher = await getTeacher(teacherID);
const avgRating = teacher.avgRating;
console.log(teacher);
console.log(avgRating);
return avgRating;
}
the code works as intended, with the expected output:
but if searchTeacher is called with professorName instead of a fixed value like this:
async function getAvgRating(professorName) {
const teachers = await searchTeacher(professorName, 'U2Nob29sLTE0OTU=');
console.log(teachers);
const teacherID = teachers[0].id;
const teacher = await getTeacher(teacherID);
const avgRating = teacher.avgRating;
console.log(teacher);
console.log(avgRating);
return avgRating;
}
it returns an empty object:
dynamic graphql request vs hardcoded graphql request
i'm not sure if i'm missing something trivial, as the values being passed to searchTeacher appear to be exactly the same, but it only works when hardcoded. please let me know if i'm missing something, thank you.
After much troubleshooting, I found the issue. #Damzaky was correct, using the localeCompare method I was able to determine that the string from the HTML and my hardcoded string were not the same, was localeCompare would return -1. I cleansed the string from the HTML using this line of code:
const normalizedName = name.normalize('NFKD');
and passed that to searchTeacher, and now the behaviour is as expected. Thanks to everyone that helped.

How to read a lot of documents (1M+) from a collection in Cloud Firestore?

The code below fails with error 9 FAILED_PRECONDITION: The requested snapshot version is too old.
const ref = db.collection('Collection');
const snapshot = await ref.get();
snapshot.forEach((doc,index) => {
...use data
})
Getting all documents from one collection in Firestore
EDIT:
getData();
async function getData(doc) {
let snapshot = await global.db.collection('Collection').orderBy().startAfter(doc).limit(5000).get();
const last = snapshot.docs[snapshot.docs.length - 1];
snapshot.forEach((doc,index) => {
//...use data
})
if (snapshot.docs.length < 5000) {
return;
}
else {
getData(last)
}
}
EDIT 2 (works, a bit slow, reads sequentially, 5000 docs at a time):
let snapshot = null;
let totalIndex = 0;
await getData();
async function getData(doc) {
if (!doc) {
const first = await global.db.collection("collection").doc("docID");
snapshot = await global.db.collection('collection').orderBy(admin.firestore.FieldPath.documentId()).startAt(first).limit(5000).get();
}
else {
snapshot = await global.db.collection('Prompts').orderBy(admin.firestore.FieldPath.documentId()).startAfter(doc).limit(5000).get();
}
const last = snapshot.docs[snapshot.docs.length - 1];
snapshot.forEach((doc,index) => {
console.log(totalIndex++);
//...use data
})
if (snapshot.docs.length < 5000) {
return;
}
else {
getData(last)
}
}
Since you're initially calling getData() without any arguments, that leads to doc being undefined in that function body. And calling startAfter(undefined) is not valid.
What you'll want to do is optionally adding that startAfter with something like:
async function getData(doc) {
let query = global.db.collection('Collection').orderBy();
if (doc) {
query = query.startAfter(doc);
}
let snapshot = await query.limit(5000).get();
...

Firebase Cloud Functions Async

I am making a function for firebase cloud functions, I want a function to be called every time a new document is created in "posts". I want this function to perform the tasks that I put inside the "onCeatePost" function.
The problem I have is that I'm not sure if this is the correct way to structure such a function.
In several firebase examples I have seen that it is always called return _; or return null; at the end of a task, but I don't know how to structure the function so that all the tasks are carried out, could someone help me to restructure my function or tell me what is wrong please.
There are several if statements in the function, if the created publication does not comply with them, I would like it to skip them but continue with the other tasks that I put inside the function.
I don't know if it's too much to ask, but I'm new to this language and I haven't been able to find the answer I'm looking for. Thank you!
exports.onPostCreate = functions.firestore.document("/posts/{postId}").onCreate(async (snap) => {
const post = snap.data();
if (post) {
try {
const topic = post.topic;
const contentForFeed = post.contentForFeed;
const uid = post.uid;
const previous = post.prev;
await db.collection("users").doc(uid).update({"stats.posts": admin.firestore.FieldValue.increment(1)});
if (topic) {
await db.collection("topics").doc(topic.id).collection("user-authors").doc(uid).set({"date": snap.createTime});
}
if (contentForFeed == true) {
const userPath = db.collection("users").doc(uid);
await userPath.update({"stats.lastUpdate": snap.createTime});
}
if (previous) {
const previousId = previous.id;
const previousUid = previous.uid;
const refPrev = db.collection("posts").doc(previousId);
await db.runTransaction(async (t) => {
const doc = await t.get(refPrev);
const priority = doc.data().stats.date;
const newDate = new admin.firestore.Timestamp(priority.seconds + 120, priority.nanoseconds);
await db.collection("posts").doc(previousId).update({"newDate": newDate});
});
if (previousUid != uid) {
const path = db.collection("users").doc(uid).collection("user-posts");
const dataToSet = {"timestamp": snap.createTime, "uid": uid, "postId": onReplyToPostId};
await path(dataToSet);
}
}
} catch (err) {
functions.logger.log(err);
}
} else {
return null;
}
});
You'll find below the adapted code (untested) with 4 corrections.
Here are explanations for the two most important ones:
(Correction 2) In a transaction you need to use the transaction's update() method and not the "standard one"
(Correction 4) When all the asynchronous work is complete you need to return a value or a Promise. See this documntation page for more details.
exports.onPostCreate = functions.firestore
.document('/posts/{postId}')
.onCreate(async (snap) => {
const post = snap.data();
if (post) {
try {
const topic = post.topic;
const contentForFeed = post.contentForFeed;
const uid = post.uid;
const previous = post.prev;
await db
.collection('users')
.doc(uid)
.update({
'stats.posts': admin.firestore.FieldValue.increment(1),
});
if (topic) {
await db
.collection('topics')
.doc(topic.id)
.collection('user-authors')
.doc(uid)
.set({ date: snap.createTime });
}
if (contentForFeed == true) {
const userPath = db.collection('users').doc(uid);
await userPath.update({ 'stats.lastUpdate': snap.createTime });
}
let previousUid; // <= Correction 1
if (previous) {
const previousId = previous.id;
previousUid = previous.uid; // <= Correction 1
const refPrev = db.collection('posts').doc(previousId);
await db.runTransaction(async (t) => {
const doc = await t.get(refPrev);
const priority = doc.data().stats.date;
const newDate = new admin.firestore.Timestamp(
priority.seconds + 120,
priority.nanoseconds
);
t.update(refPrev, { newDate: newDate }); // <= Correction 2
});
if (previousUid != uid) {
const path = db
.collection('users')
.doc(uid)
.collection('user-posts');
const dataToSet = {
timestamp: snap.createTime,
uid: uid,
postId: onReplyToPostId,
};
await path.add(dataToSet); // <= Correction 3
}
}
return null; // <= Correction 4
} catch (err) {
functions.logger.log(err);
}
} else {
return null;
}
});

Nodejs & TypeORM Transaction Confusion

I have confusion and I couldn't resolve it from the TypeORM documents.
Here is my function that is used as request handler and it is using transaction taken from getManager()
static makeTransaction = async (req: Request, res: Response) => {
try {
// check if the account ids are same
const { senderAccountId, receiverAccountId, amount } = req.body;
let senderAccount = await findEntityById(getRepository(Account), senderAccountId);
let receiverAccount = await findEntityById(getRepository(Account), receiverAccountId);
senderAccount.balance -= amount;
receiverAccount.balance += amount;
await validateOrReject(senderAccount);
await validateOrReject(receiverAccount);
const tempEntity = MyEntity.create({
amount,
receiverAccount: receiverAccount,
senderAccount: senderAccount,
});
await validateOrReject(tempEntity);
const accounts = await getManager().transaction(async transactionManager => {
await transactionManager.save(senderAccount);
await transactionManager.save(receiverAccount);
return transactionManager.save(tempEntity);
});
return res.status(200).json({
error: null,
data: true,
});
} catch (e) {
return res.status(400).json({ error: "Transaction wasn't successfull" });
}
};
So when I put a catch function for the last saving (only it fails and first 2 of them runs correctly), it gives "Query runner already released. Cannot run queries anymore" error.
When I change the flow like this, it works and I really wonder the reason because in documents there are saving operations for different entity types and this is what I'm trying to do too.
static makeTransaction = async (req: Request, res: Response) => {
try {
// check if the account ids are same
const { senderAccountId, receiverAccountId, amount } = req.body;
let senderAccount = await findEntityById(getRepository(Account), senderAccountId);
let receiverAccount = await findEntityById(getRepository(Account), receiverAccountId);
senderAccount.balance -= amount;
receiverAccount.balance += amount;
await validateOrReject(senderAccount);
await validateOrReject(receiverAccount);
const tempEntity = MyEntity.create({
amount,
receiverAccount,
senderAccount,
});
await validateOrReject(tempEntity);
const transactionRes = await getManager().transaction(async transactionManager => {
return transactionManager.save([tempEntity, senderAccount, receiverAccount]);
});
return res.status(200).json({
error: null,
data: transactionRes[0],
});
} catch (e) {
console.log(e);
return res.status(400).json({ error: "Transaction wasn't successfull" });
}
};
I want to learn the reason for that and any kind of idea or help is appreciated, thank you in advance.

async method always returning undefined

I can't solve that problem so I'm asking that here:
This is the async function, and as you can see is returning an array. But it returns an undefined value.
async function scrape(pageURL) {
var dealArray = [];
try {
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
await page.goto(pageURL);
await page.waitForSelector('div.s-item-container');
const dealsElements = await page.$$('div.s-item-container');
for(deal of dealsElements) {
let dealTitleElement = await deal.$('div.s-item-container a.s-access-detail-page');
let dealTitleValue = await (await dealTitleElement.getProperty('title')).jsonValue();
let dealPriceElement= await deal.$('div.s-item-container span.a-color-price');
let dealPriceValue = await (await dealPriceElement.getProperty('textContent')).jsonValue();
let dealReviewsElement = await deal.$('div.s-item-container .a-icon-star');
let dealLinkValue = await (await dealTitleElement.getProperty('href')).jsonValue() + '&tag=dragonstv-21';
let dealReviewsClass = await (await dealReviewsElement.getProperty('className')).jsonValue();
let dealReviewsValue;
if(dealReviewsClass) {
let starValue = dealReviewsClass.substring(26);
if(starValue.indexOf('-') === -1) {
dealReviewsValue = starValue;
} else {
let stars = starValue.replace('-', '.');
dealReviewsValue = stars;
}
}
dealArray.push({
"title": dealTitleValue,
"price": dealPriceValue,
"reviews": dealReviewsValue + "/5.0",
"link": dealLinkValue,
"store": "Amazon",
});
}
return Promise.resolve(dealArray);
} catch(e) {
console.error('Error: ' + e);
}
}
And here is how I'm calling it:
scrape('working link').then((data) => {
console.log(data) // result: undefined
}
It works only if I declare the variable out of the function and the function doesn't return anything but only changes the array content.
As written, your function must return an array (empty or otherwise). If it's returning undefined, then you're generating an exception and should see one in the console, via your catch statement. If you're not seeing it, you might try removing the try/catch and see what exception bubbles up.
I've actually figured the problem out. It was returning a string so I had to use JSON.parse(request) so I have an object on which I can work on.

Categories

Resources