Count grandchild nodes in a cloud function - node.js

I have the following data structure:
/users
/{user_uid}
/lists
/{list_uid}
Using a cloud function, i'd like to be able to have a /list_count reference on the root of my database, to be able to easily track list count without having to do a fat client-side call to count them.
at the moment I have this implementation, which I find a bit ugly:
exports.countlists = functions.database.ref('/users/{uuid}/lists').onWrite(event => {
const ref = event.data.ref.parent.parent.parent.child('list_count');
return ref.transaction(current => {
if (event.data.exists() && !event.data.previous.exists()) {
return (current || 0) + 1;
}
else if (!event.data.exists() && event.data.previous.exists()) {
return (current || 0) - 1;
}
});
});
The issue being that I get an error in the firebase console:
Error serializing return value: TypeError: Converting circular structure to JSON

as Inlined said here:
The problem you're running into is that the promised value that ref.transaction returns isn't serializeable as JSON. The easiest way to fix this (before we fix it in the Firebase SDK) is to transform the value to something like null.
I think to fix your problem, do this:
exports.countlists = functions.database.ref('/users/{uuid}/lists').onWrite(event => {
let root = admin.database().ref(`users/${event.params.uuid}/lists/list_count`)
return root.transaction(function(current){
if (event.data.exists() && !event.data.previous.exists()) {
return (current || 0) + 1;
}
else{
return (current || 0) - 1;
}
}.then(() => null));
});

Related

discord.js Cannot read property add of undefined when trying to give role

for (let l = 0; l < client.config.topMemberCount; l++) {
if (topMemberList[l] === null) continue;
if (String(topMemberList[l]) === '261292565247252480') continue;
let roleMember = message.guild.members.fetch(topMemberList[l]);
console.log(roleMember);
let roleToGive = message.guild.roles.fetch('791984048851124234');
roleMember.roles.add(roleToGive);
}
Alright, so:
client.config.topMemberCount can be read, the value is 5
topMemberList is an Array which contains the IDs of 5 Users in String format (for example: `223212312432123')
When I run this, it returns TypeError: Cannot read property 'add' of undefined, which is weird, since if i output roleMember and roleToGive to console, it clearly seems to output a Snowflake of the role or member that is targeted.
if (topMemberList[l] === null) continue; and if (String(topMemberList[l]) === '261292565247252480') continue; are just some measures to not give the role to myself or a null-object, should the array contain null at some point.
What am I doing wrong? :(
Edit: discord.js is on version 12.5.1
The method .fetch() is asynchronous and returns a Promise, not a GuildMember or Role object. This is because it will take DJS a bit of time to fetch these values, and you need to only use the fetched member/role once it truly has been fetched (currently you are attempting to use these values before they have truly been fetched, which is why the error is telling you that roleMember.roles is undefined). Look at any example for .fetch() on the discord.js docs to see for yourself.
The solution would be either to use async/await, or to use .then(). I will demonstrate the latter below, because your function declaration is not provided (needless to say, you would just need to add async before function and await before your .fetch() lines).
for (let l = 0; l < client.config.topMemberCount; l++) {
if (topMemberList[l] === null) continue;
if (String(topMemberList[l]) === '261292565247252480') continue;
message.guild.members.fetch(topMemberList[l]).then(roleMember => {
message.guild.roles.fetch('791984048851124234').then(roleToGive => {
roleMember.roles.add(roleToGive);
}).catch(err => console.log("Role could not be found; Error: " + err.stack));
}).catch(err => console.log("Member could not be found; Error: " + err.stack));
}
I also added .catch() statements, which should be able to show you any additional errors in this part of your code (in the event that the above fix is not enough to get your code working).

Update only if key exist firebase cloud functions

I have a function that counts all the members that are going to do a event.
I have the registered_people key to count how many users are on this event. This key is updated +1 or -1 when someone adds itself to the /registrations/approved link.
This works very well. See the method below.
exports.reservation = functions.database.ref('/agenda/activitys/{year}/{month}/{day}/{time}/{event}/registrations/approved/{key}').onWrite((event) => {
var collectionRef = event.data.adminRef.parent.parent;
var countRef = collectionRef.parent.child('registered_people');
console.log("Fired of reservation watcher");
return countRef.transaction(function(current) {
if (event.data.exists() && !event.data.previous.exists()) {
return (current || 0) + 1;
}
else if (!event.data.exists() && event.data.previous.exists()) {
return (current || 0) - 1;
}
});
});
But my problem is when the admin deletes an event. Url /agenda/activitys/{year}/{month}/{day}/{time}/{event} gets deleted, and the method defined above gets triggered and writes data again to the url. How can I prevent that this method writes anything when an admin deletes the event?
And this code is not working:
if (event.data.previous.exists()) {
return;
}
Because when a user wants to sign out from an event the registered_people must be updated. With the code defined above the delete does not work anymore. So I need to check if the event is deleted.
First, you are running and old version of functions & admin, update to be sure your firebase-functions & firebase-admin are updated:
In your functions folder run:
npm install firebase-functions#latest --save
npm install firebase-admin#latest --save
Then your code should look like this:
exports.reservation = functions.database.ref('/agenda/activitys/{year}/{month}/{day}/{time}/{event}/registrations/approved/{key}').onWrite((change, context) => {
var collectionRef = change.after.ref.parent.parent;
var countRef = collectionRef.parent.child('registered_people');
let increment;
if (change.after.exists() && !change.before.exists()) {
increment = 1;
} else if (!change.after.exists() && change.before.exists()) {
return null;
} else {
return null;
}
return countRef.transaction((current) => {
return (current || 0) + increment;
}).then(() => {
return console.log('Counter updated.');
});
});

In a Firebase Database Cloud Function, how can you add/replace an item in a list

My database structure is:
- Scores
- r5rwuerpepdsoazdf (user UID)
- scoreString: "5,12"
- j6fdasdfsdfs08fdd
- scoreString: "1,4,7"
and I have a cloud function that updates the Score for a particular user UID when a value is written in another node (ScoreStacks):
exports.updateScoreString =
functions.database.ref('/ScoreStacks/{uid}/{levelKey}/Score')
.onWrite((dataSnapshot, context) => {
const score = dataSnapshot.after.val();
const scoreStringRef = dataSnapshot.after.ref.root.child('Scores/' + context.params.uid + '/scoreString');
return scoreStringRef.transaction(scoreString => {
if (!scoreString) // transactions return null first time
{
return scoreString;
}
return scoreStringWithAddedScore(scoreString, score);
});
});
This is all working fine apart from if the UID in the Scores node does not yet exist. Then nothing happens.
I guess that is fair enough because scoreStringRef is refering to a path that doesn't exist.
So how can I ensure a new entry is made in the Scores node if the UID does not exist yet? It all has to be done atomically.
It's hard to be sure without seeing scoreStringWithAddedScore, but I suspect you want this:
return scoreStringRef.transaction(scoreString => {
if (!scoreString) // transactions return null first time
{
scoreString = "";
}
return scoreStringWithAddedScore(scoreString, score);
});
A more concise (but somewhat harder to parse if you're new to this) version:
return scoreStringRef.transaction(scoreString => {
return scoreStringWithAddedScore(scoreString || "", score);
});

Blockly while loop argument

I have created a little maze with a robot and I use Blockly to generate code to try to solve it. I can move the robot using Javascript commands which are Blockly blocks. So far so good.
I am currently breaking my head over arguments of if-statements and while loops. Mainly, I have tried two things:
Blockly maze
create a variable, 'not_goal_reached' which says whether or not the robot has reached the goal position (cross). Code:
function not_done() {
var goal_location = get_goal_position()
var goal_x = goal_location[0];
var goal_y = goal_location[1];
console.log('in not done');
//console.log(player.x!= goal_x || player.y != goal_y)
return (player.x!= goal_x || player.y != goal_y);
};
Blockly.Blocks['not_goal_reached'] = {
init: function() {
this.appendDummyInput()
.appendField("not at goal")
this.setOutput(true, "Boolean");
this.setColour(230);
this.setTooltip('');
this.setHelpUrl('');
}
};
Blockly.JavaScript['not_goal_reached'] = function(block) {
var code = 'not_done()';
// TODO: Change ORDER_NONE to the correct strength.
//console.log(code)
return [code, Blockly.JavaScript.ORDER_ATOMIC];
};
However, when using this block in an If or While statement. I always get a Javascript error that does not help me to find the solution:
TypeError: Cannot read property 'toBoolean' of undefined
at Interpreter.stepConditionalExpression (acorn_interpreter.js:148)
at Interpreter.step (acorn_interpreter.js:45)
at nextStep (index.html:79)
I use the Acorn js interpreter:
window.LoopTrap = 2000;
//Blockly.JavaScript.INFINITE_LOOP_TRAP = 'if(--window.LoopTrap == 0) throw "Infinite loop.";\n';
var code = Blockly.JavaScript.workspaceToCode(workspace);
console.log(code);
var myInterpreter = new Interpreter(code, initInterpreter);
//Blockly.JavaScript.INFINITE_LOOP_TRAP = null
var counter = 0;
function nextStep() {
try {
if (myInterpreter.step()) {
counter+=1;
console.log(counter);
if (counter < window.LoopTrap) {
window.setTimeout(nextStep, 30);
}
else {
throw "Infinite Loop!"
}
}
}
catch (e) {
//alert(e);
console.log(e)
}
}
nextStep();
Problem: javascript error I can not solve :(
I created my own While block that does not require input. This While block checks internally whether or not the robot has reached the goal and then processes the DO statements:
Blockly.Blocks['repeat_forever'] = {
init: function() {
this.appendDummyInput()
.appendField("While not at goal");
this.appendStatementInput("DO")
.appendField("Do");
this.setPreviousStatement(true);
this.setColour(230);
this.setTooltip('');
this.setHelpUrl('');
}
};
Blockly.JavaScript['repeat_forever'] = function(block) {
var branch = Blockly.JavaScript.statementToCode(block, 'DO');
// TODO: Assemble JavaScript into code variable.
//if (Blockly.JavaScript.INFINITE_LOOP_TRAP) {
// branch = Blockly.JavaScript.INFINITE_LOOP_TRAP.replace(/%1/g,
// '\'block_id_' + block.id + '\'') + branch;
// console.log(branch);
//}
var code = 'while (' + not_done() + ') {' + branch + '}';
console.log(code)
return [code, Blockly.JavaScript.ORDER_ATOMIC];
};
This works, BUT, here I have the problem that my internal function 'not_done' is only evaluated once (at code generation) to while(true) (since the first time the robot is of course not at the goal location yet). This block correctly applies the DO codes but does not halt (since while (true)). If I add quotes around 'not_done()' the function is evaluated once apparently, but then I receive the same Javascript error as above (Cannot read property 'toBoolean' of undefined)
Am I missing something here? Thanks a lot for your time!
Greetings
K
It seems that you setTimeout which cannot be reached while the while loop runs.

How do I stop a table script from processing?

I am creating an insert script that does some business logic.
Basically, I want to check to see if a value in the inserted item exists in a table. But, it seems like if I find a problem Request.Send() doesn't stop execution and get an error.
I think there is an async issue here. I'm not 100% sure how to solve.
Is there a way to stop execution of the script?
if (item.memberType === 'Family' && item.primaryFamilyMember) {
table
.where({
memberNumber: item.primaryFamilyMember,
memberType: 'Family',
primaryFamilyMember: null })
.read({
success: function(results) {
if (results.length == 0) {
request.respond(statusCodes.BAD_REQUEST,
'Invalid Primary Family Member specified.');
console.error('Invalid Primary Family Member specified:' + item.primaryFamilyMember);
validInsert = false;
} else {
item.memberType = results[0].memberType;
item.memberLevel = results[0].memberLevel;
item.dateOfExpiry = results[0].dateOfExpiry;
}
}
});
}
if (validInsert) {
var today = new Date();
var prefix = today.getFullYear().toString().substr(2,2) + ('0' + (today.getMonth() + 1)).slice(-2);
table.includeTotalCount().where(function(prefix){
return this.memberNumber.substring(0, 4) === prefix;
}, prefix)
.take(0).read({
success: function (results) {
if (isNaN(results.totalCount)) {
results.totalCount = 0;
}
item.memberNumber = prefix + ('00' + (results.totalCount + 1)).slice(-3);
request.execute();
}
});
}
Yes, validInsert is declared at the top of the insert function.
I assume what's happening is the if(validInsert) runs before the read callback. But if so, i'm not sure why I'm getting "Error: Execute cannot be called after respond has been called." That implies the callback is running first.
Also, the record is being inserted when it shouldn't be even though the 400 error is sent back to the client.
This is an express app right? Should I just call response.end() after the error occurs?
Yes, there are definitely asyn issues in that code. To solve get rid of your validInsert flag and simply move the if (validInsert) section into the success callback (or make it a function called from the success callback). For example:
success: function(results) {
if (results.length == 0) {
request.respond(statusCodes.BAD_REQUEST,
'Invalid Primary Family Member specified.');
console.error('Invalid Primary Family Member specified:' + item.primaryFamilyMember);
} else {
item.memberType = results[0].memberType;
item.memberLevel = results[0].memberLevel;
item.dateOfExpiry = results[0].dateOfExpiry;
var today = new Date();
var prefix = today.getFullYear().toString().substr(2,2) + ('0' + (today.getMonth() + 1)).slice(-2);
...
//respond successfully
}
}

Resources