I am new to firebase and would like to implement the ranking for the players stored in the rank table from firebase real-time db, see screenshot below:
I have also setup the rules for the index:
{
"rules": {
".read": "auth != null",
".write": "auth != null",
"TH": {
"rank": {
".indexOn" : ["balance", "w_rate","match"]
}
}
}
}
in my firebase function, I have a function as a post request to get the rank:
exports.getRank = functions.https.onRequest(async (req,res) => {
const user_id = req.query.id;
console.log(`user_id ${user_id} `);
const query = database().ref('TH/rank')
.orderByChild('balance')
.limitToLast(30)
query.once('value',function(snapshot) {
console.log('in side the usersRef');
console.log(`snapshot:${JSON.stringify(snapshot.val())}`);
let current_user_rank = 0;
if (user_id !== null) {
console.log(`user id ${user_id} isn't null`);
database().ref('TH/rank/').orderByChild('balance').once('value',function(all_snapshot){
let index = 0;
console.log(`user id ${user_id} all snapshot.`);
if (all_snapshot.child(`${user_id}`).exists()) {
console.log(`user id ${user_id} exists`);
const current_user_data = all_snapshot.child(`${user_id}`).val();
all_snapshot.forEach( child => {
index += 1;
console.log(child.key, child.val());
if(child.key === user_id) {
current_user_rank = all_snapshot.numChildren() - index;
}
});
res.json({
user: { id: user_id,
rank: current_user_rank,
data: current_user_data
},
rank: snapshot.val()
});
} else {
res.json({
user: { id: user_id,
rank: current_user_rank,
data: null
},
rank: snapshot.val()
});
}
}).catch();
} else {
res.json({
user: { id: user_id,
rank: current_user_rank,
data: null
},
rank: snapshot.val()
});
}
}).catch();
});
However, the result isn't correct, it seems the orderByChild doesn't work at all. Can someone help on this?
Thanks.
There are several problems with your Cloud Function:
You use async when declaring the callback but you never use await in the Cloud Function, in order to get the result of the asynchronous once() method.
Instead of using the callback "flavor" of the once() method (i.e. ref.once('value',function(snapshot) {..}), use the promise "flavor" (i.e., with await: await ref.once('value');).
The result is that you don't correctly manage the Cloud Function life cycle. For more details on how to do that correctly, I would suggest you watch the 3 videos about "JavaScript Promises" from the Firebase video series as well as read the following doc.
So the following should do the trick. (Note that I've just adapted it to correctly manage the life cycle, I've not tested the business logic).
exports.getRank = functions.https.onRequest(async (req, res) => {
try {
const user_id = req.query.id;
console.log(`user_id ${user_id} `);
let current_user_rank = 0;
if (user_id !== null) {
console.log(`user id ${user_id} isn't null`);
const baseQuery = database().ref('TH/rank/').orderByChild('balance');
const query = baseQuery.limitToLast(30);
const snapshot = await query.once('value');
console.log(`snapshot:${JSON.stringify(snapshot.val())}`);
const all_snapshot = await baseQuery.once('value');
let index = 0;
console.log(`user id ${user_id} all snapshot.`);
if (all_snapshot.child(`${user_id}`).exists()) {
console.log(`user id ${user_id} exists`);
const current_user_data = all_snapshot.child(`${user_id}`).val();
all_snapshot.forEach(child => {
index += 1;
console.log(child.key, child.val());
if (child.key === user_id) {
current_user_rank = all_snapshot.numChildren() - index;
}
});
res.json({
user: {
id: user_id,
rank: current_user_rank,
data: current_user_data
},
rank: snapshot.val()
});
} else {
res.json({
user: {
id: user_id,
rank: current_user_rank,
data: null
},
rank: snapshot.val()
});
}
} else {
res.json({
user: {
id: user_id,
rank: current_user_rank,
data: null
},
rank: snapshot.val()
});
}
} catch (error) {
console.log(error);
res.status(500).send(error);
}
});
Update following your comment:
You need to use forEach() in order to get the children correctly ordered, and not snapshot.val(). snapshot.val() displays the children according to their key, exactly like they are ordered in the DB. The following adaptation of the code in your comment works correctly:
exports.getSortedRank = functions.https.onRequest(async (req, res) => {
try {
const obj = {};
const baseQuery = admin.database().ref('TH/rank/').orderByChild('balance');
const query = baseQuery.limitToLast(10);
const snapshot = await query.once('value');
snapshot.forEach(childSnapshot => {
console.log(childSnapshot.val());
obj[childSnapshot.key] = childSnapshot.val()
});
res.json({ rank: obj });
} catch (error) { console.log(error); res.status(500).send(error); }
});
Related
I'm trying to update value in mongoDB and it's not changing the value. I checked the values and they are ok and I checked the errors and everything looks good. what am I missing?
Mongo atlas
picture of my mongo values
type: Number,
default: 0
code :
// symbol = the symbol of the coin | ammount = the ammount of coin
// in this case : symbol = USDT | ammount = 3
const setCoin = async (symbol, ammount) => {
setError(false);
try {
setError(false);
const res = await axios.patch("http://localhost:3001/api/users/setCoin", {
email: user.email,
symbol, ammount
});
res.data && window.location.replace("/home");
console.log(error)
} catch (err) {
setError(true);
}
};
server side
//setCoin
router.route("/setCoin")
.patch(async (req, res,) => {
try {
const symbol = req.body.symbol;
const user = await User.findOneAndUpdate({ "email": req.body.email }, { $set: { symbol: req.body.ammount } }, { upsert: true });
console.log("succes to set coin");
} catch (err) {
res.status(500).json(User.symbol);
console.log(err)
}
});
So as I dont know how you defined your User model can you try the following:
const user = await User.findOneAndUpdate({ "email": req.body.email }, { $set: { [symbol]: req.body.ammount } }, { upsert: true });
My usecase of socket is not for chats, it's specifically to tell the front-end what BullMQ queue events happened, with a progress bar and telling when a job is done or failed.
Currently when I'm emitting events, it's going for all users, I tried to use the socket.to(socket.id).emit({ myEvent: example }) but didn't work at all.
I'm storing session on Redis.
Socket
this.redisClient = new Redis(`${process.env.REDIS_URL}`);
this.sessionStore = new RedisSessionStore(this.redisClient);
this.pubClient = this.redisClient;
this.subClient = this.pubClient.duplicate();
this.instance.adapter(createAdapter(this.pubClient, this.subClient));
} catch (err) {
console.log("Error on Socket Controller: ", err.message);
}
this.instance.use(async (socket, next) => {
const sessionID = socket.handshake.auth.sessionID;
if (sessionID) {
const session = await this.sessionStore.findSession(sessionID);
if (session) {
socket.sessionID = sessionID;
socket.userID = session.userID;
socket.username = session.username;
return next();
}
}
const username = socket.handshake.auth.username;
if (!username) {
return next(new Error("invalid username"));
}
socket.sessionID = uuidv4();
socket.userID = uuidv4();
socket.username = username;
console.log(socket.sessionID, socket.userID, socket.username);
next();
});
this.instance.on("connection", async (socket) => {
// Assign socket to the class
this.socket = this.socket == null ? socket : this.socket;
let connectedUsersCount =
Object.keys(this.instance.sockets.sockets).length + 1;
let oneUserLeft = connectedUsersCount - 1;
console.log(`New client connected`, connectedUsersCount);
try {
this.sessionStore.saveSession(this.socket.sessionID, {
userID: this.socket.userID,
username: this.socket.username,
connected: true
});
// emit session details
this.socket.emit("session", {
sessionID: this.socket.sessionID,
userID: this.socket.userID
});
// join the "userID" room
this.socket.join(this.socket.userID);
const users = [];
const sessions = await this.sessionStore.findAllSessions();
sessions.forEach((session) => {
users.push({
userID: session.userID,
username: session.username,
connected: session.connected
});
});
this.socket.emit("users", users);
// notify existing users
this.socket.broadcast.emit("user connected", {
userID: this.socket.userID,
username: this.socket.username,
connected: true,
messages: []
});
integrationQueueEvents.on("progress", async (job: any) => {
try {
console.log("Job Progressing", job);
const payload = {
status: true,
data: job.data,
jobId: job.jobId,
to: this.socket.userID
};
console.log("integration progress payload: ", payload);
this.socket.emit("integrationProgress", payload);
} catch (error) {
console.log(error);
}
// this.socket.to(this.socket.id).emit("integrationProgress", payload);
});
Session Store
findSession(id) {
return this.redisClient
.hmget(`session:${id}`, "userID", "username", "connected")
.then(mapSession);
}
saveSession(id, { userID, username, connected }) {
this.redisClient
.multi()
.hset(
`session:${id}`,
"userID",
userID,
"username",
username,
"connected",
connected
)
.expire(`session:${id}`, SESSION_TTL)
.exec();
}
async findAllSessions() {
const keys = new Set();
let nextIndex = 0;
do {
const [nextIndexAsStr, results] = await this.redisClient.scan(
nextIndex,
"MATCH",
"session:*",
"COUNT",
"100"
);
nextIndex = parseInt(nextIndexAsStr, 10);
results.forEach((s) => keys.add(s));
} while (nextIndex !== 0);
const commands = [];
keys.forEach((key) => {
commands.push(["hmget", key, "userID", "username", "connected"]);
});
return await this.redisClient
.multi(commands)
.exec()
.then((results) => {
return results
.map(([err, session]) => (err ? undefined : mapSession(session)))
.filter((v) => !!v);
});
}
I have a method that performs an update in Mongo but I do not have access to "res" and "res" since it is a method that I will use on more than one occasion.
My problem is when responding to the update as I try with a return but it doesn't work as the request never completes. Do you know how I can answer then?
This method calls the method that will be reused several times:
let item = (req, res = response ) => {
const { id } = req.params;
const { status, user, ...data } = req.body;
data.user = req.uid;
data.expenditure = null;
let item = {
concept: data.concept,
revenue: data.revenue,
'createdBy': {
uid: req.uid,
username: req.user.username,
},
description: data.description
}
createNewItem( id, item );
}
And this is the method to reuse several times that does not have "req" or "res":
const createNewItem = async ( id, item ) => {
try {
let pettycash = await PettyCash.findByIdAndUpdate( id,
{
$push: {
'items': {
$each: [item],
}
}
},{ new: true }
);
return { err: null, status: 200, pettycash };
} catch (err) {
return { err: err.toString(), status: 500, data: null };
}
}
Although the process is correct return { err: null, status: 200, pettycash }; doesn't finish the request so POSTMAN never finishes and you keep waiting for some response.
Thanks.
Okay so two options.
One pass res to function as argument which you don't want and is not a good idea because it violates single responsibility rule for a function.
You add await so that controller waits for the function execution to complete. To do this make your controller async. This is generally a good idea. You let controller do its work meanwhile your createNewItem only have single responsibility of creating an item.
let item = async (req, res = response ) => {
const { id } = req.params;
const { status, user, ...data } = req.body;
data.user = req.uid;
data.expenditure = null;
let item = {
concept: data.concept,
revenue: data.revenue,
'createdBy': {
uid: req.uid,
username: req.user.username,
},
description: data.description
}
await createNewItem( id, item );
// send response back
}
I have one question about a problem that I'm not able to fix. I try to update push a string passed via Query in my mongoose collection.
My collection are like this:
{
"_id": {
"$oid": "6199288597e42bf84d017f9e"
},
"name": "Lisa",
"surname": "Bianchi",
"ID_school": "afbH598U3",
"classes": [
{
"class": "5A",
"activities": {
"in_progress": [],
"finisched": []
},
"_id": {
"$oid": "6199288597e42bf84d017f9f"
}
},
{
"class": "1A",
"activities": {
"in_progress": [],
"finisched": []
},
"_id": {
"$oid": "6199288597e42bf84d017fa0"
}
}
],
"email": "insegnante#a.com",
"__v": 0
}
and I try to push a string in in_progress array that match, for example, with class:"5A" using this way:
import db from "../models/index.js";
const Teacher = db.teacher
const updateActivity = (req, res) => {
const query = { _id: req.query.id};
const update = {$push:{'classes.$[group].activities.in_progress': req.query.data } };
const options = {arrayFilters: { 'group.class': req.query.class }};
Teacher.findOneAndUpdate(query, update, options).exec((err, data) => {
if (err) {
res.status(400).send({ message: err });
return;
} else {
res.status(200).send(data);
}
})
}
const API = {
updateActivity
}
export default API
The query works fine, but nothing was pushed. I tested whit Insomnia passing in the Query field
id = 6199288597e42bf84d017f9e;
class:'5A';
data:"pushed"
Any suggestion? Thanks!
try this way by passing classes.class in query and also change push to $push:{'classes.$.activities.in_progress': req.query.data }
const updateActivity = (req, res) => {
const query = { _id: req.query.id ,'classes.class': req.query.class};
const update = {$push:{'classes.$.activities.in_progress': req.query.data } };
Teacher.updateOne(query,update).exec((err, data) => {
if (err) {
res.status(400).send({ message: err });
return;
} else {
res.status(200).send(data);
}
})
}
There are two ways of doing this:
Option 1: arrayFilters - more flexible Docu
The option you are using.
You have a syntax error - arrayFilters should be an array of documents.
const updateActivity = (req, res) => {
const query = { _id: req.query.id };
const update = {
$push:{ 'classes.$[group].activities.in_progress': req.query.data }
};
// This MUST be an array of filter documents!
const options = { arrayFilters: [{ 'group.class': req.query.class }] };
Teacher
.findOneAndUpdate(query, update, options)
.exec((err, data) => {
if (err) {
res.status(400).send({ message: err });
return;
} else {
res.status(200).send(data);
}
});
}
Option 2: Via Query (as answered by #Saurabh Mistry)
Repeating his answer for completeness
By specifying a query that targets a particular element in an array within result documents.
const updateActivity = (req, res) => {
const query = {
_id: req.query.id,
'classes.class': req.query.data,
};
const update = {
$push:{ 'classes.$.activities.in_progress': req.query.data }
};
Teacher
.findOneAndUpdate(query, update, options)
.exec((err, data) => {
if (err) {
res.status(400).send({ message: err });
return;
} else {
res.status(200).send(data);
}
});
}
I got a data from MySQL and push it into new Array but it when I log it. It doesn't have any data. I use for loop to get each data from DB and I don't know how to push RowDataPacket into new Array.
Or is there any way to combine two SQL into one SQL line?
router.get(`/find-users/:queryString`, function(req, res, next) {
let queryString = req.params.queryString;
db.query(
`SELECT distinct userId from project WHERE keyword like "%${queryString}%"`,
function(error, data) {
if (error) {
console.log(error);
}
// console.log(data);
let userArray = [];
for (let i = 0; i < data.length; i++) {
db.query(
`SELECT * FROM user WHERE loginId='${data[i].userId}'`,
function(error, userData) {
if (error) {
console.log(error);
} else {
console.log("-----------------------");
console.log(userData[0]);
// userArray[i] = userData;
userArray.push(userData[0]);
}
}
);
}
console.log(`-------`);
console.log(userArray);
console.log(`-------`);
}
);
});
I have to make array like this.
[ RowDataPacket {
loginId: '박동찬',
displayId: '107688875506148574770',
name: '박동찬',
bio: 'NO BIO',
RowDataPacket {
loginId: 'jaagupkymmel',
displayId: '1156051',
name: 'Jaagup Kümmel',
bio: 'NO BIO' }
]
But it only returns like this
Result
-------
[]
-------
const {promisify} = require('util')
router.get(`/find-users/:queryString`, async function (req, res, next) {
const query = promisify(db.query).bind(db);
let queryString = req.params.queryString;
const data = await query(`SELECT distinct userId from project WHERE keyword like "%${queryString}%"`)
if (!data) {
console.log("error");
}
// console.log(data);
let userArray = [];
for (let i = 0; i < data.length; i++) {
const userData = await query(`SELECT * FROM user WHERE loginId='${data[i].userId}'`)
if (!userData) {
console.log("error");
} else {
console.log("-----------------------");
console.log(userData[0]);
// userArray[i] = userData;
userArray.push(userData[0]);
}
}
console.log(`-------`);
console.log(userArray);
console.log(`-------`);
});
use this instead
function(error, userData, fields)
and you get an array like shown here https://www.w3schools.com/nodejs/nodejs_mysql_select.asp
And please read up on sql injection and node.js Preventing SQL injection in Node.js
in addition to above answer,
[{
RowDataPacket: {
loginId: '박동찬',
displayId: '107688875506148574770',
name: '박동찬',
bio: 'NO BIO',
RowDataPacket: {
loginId: 'jaagupkymmel',
displayId: '1156051',
name: 'Jaagup Kümmel',
bio: 'NO BIO' }
}
}]
the json should be key value pair other it wont work.