discord.js - Leaderboard / top command showing wrong order - node.js

I have been having a lot of trouble recently with a leader board command showing users in the wrong order.
I am using the DiscordJS master branch and Sequelize v6
Here is an image of what I am describing:
Leaderboard command file:
const { MessageEmbed } = require("discord.js");
exports.run = async (client, message, args) => {
const { Users } = require("../functions/database");
let users = await Users.findAll({ limit: 10, order: [['count', 'DESC']] });
for (const user of users) {
await client.users.fetch(user.userid);
}
let embed = new MessageEmbed()
.setAuthor("Middleman Leaderboard", `${client.user.displayAvatarURL({ dynamic: true })}`)
.setDescription(users.map((user, position) => `**${position + 1}**. ${(client.users.cache.get(user.userid).username)}: ${user.count} Points`))
.setThumbnail('https://i.ibb.co/ByRSBmB/hp.png')
.setColor("#d70069");
await message.reply(embed)
}
Sequelize database model:
const Users = sequelize.define('users', {
userid: {
type: Sequelize.INTEGER,
},
count: Sequelize.INTEGER,
});
exports.Users = Users;
Please note this is greatly a proof of concept and not for production.
Any help is appreciated!

This is a solution from discordjs.guide:
users.sort((a, b) => b.balance - a.balance)
.filter(user => client.users.cache.has(user.user_id))
.first(10)
.map((user, position) => `(${position + 1}) ${(client.users.cache.get(user.user_id).tag)}: ${user.balance}💰`)
.join('\n'),

Related

Saving array of children of a Mongoose Schema and then adding returned IDs to parent

i'm trying to loop over an array of objects, saving them to MongoDB and then add the returned ObjectIds to a parent Schema which then is also saved. I'm at a loss here.
Everything gets saved correctly but the Recipe (parent) apparently is saved before I get the returned ObjectIds of the Tags (children). I feel like I've used the async and await keywords a bit to often.
Can someone help? Code simplified, but I can post more if needed.
Parent Schema:
const recipe = new mongoose.Schema(
{
name: String,
ingredients: [
{
type: mongoose.SchemaTypes.ObjectId,
ref: "Ingredient",
},
],
}
);
Child Schema:
const ingredientSchema = new mongoose.Schema({
value: String,
label: String,
});
Payload:
{
name: "Rezept",
ingredients: [
{
label: "zutat",
value: "Zutat",
},
{
label: "schokolade",
value: "Schokolade",
},
],
};
My router:
recipesRouter.post("/", async (req, res) => {
const { body } = req;
const saveIngredients = async () => {
let ingredientIDs = [];
body.ingredients.map(async (ingredient) => {
const i = new Ingredient({
value: ingredient.value,
label: ingredient.label,
});
const savedIngredient = await i.save();
ingredientIDs.push(savedIngredient._id);
});
return ingredientIDs;
};
const recipe = new Recipe({
name: body.name,
ingredients: (await saveIngredients()) || [],
});
const savedRecipe = await recipe.save();
res.status(201).json(savedRecipe);
});
Returned recipe:
savedRecipe: {
name: 'asd',
ingredients: [],
_id: new ObjectId("62782b45a431e6efb7b8b1a7"),
}
As I said, both ingredients individually and the recipe is saved to the MongoDB after this but not the ingredient IDs in the recipe. The returned recipe has an empty array in ingredients. I guess the recipe is saved too soon before MongoDB can return ObjectIds for the ingredients.
Thanks for any help.
First of all, your post method is an async, so everything inside it is wrapped in a resolved promise automatically.
Do you really need to make your saveIngredients as an async? IMHO, it's better to let the saveIngredients not be in another async.
And then we can remove the empty list, and just wait for the saveIngredients() finish first.
const recipe = new Recipe({
name: body.name,
ingredients: await saveIngredients(),
});
Your guess is correct, the Recipe was saved first because all the conditions are fulfilled because it doesn't need to wait for the saveIngredients since you provided a [] as the default value. And your saveIngredients is run in parallel.
I got it smh. Turns out async in a .map or .foreach doesn't go well. I turned it into a simple for loop. It's still bloated/lot of steps imo but it works!
recipesRouter.post("/", async (req, res) => {
const { body } = req;
const saveIngredients = async () => {
let ingredientIDs = [];
for (let i = 0; i < body.ingredients.length; i++) {
const el = body.ingredients[i];
const ing = new Ingredient({
value: el.value,
label: el.label,
});
const savedIngredient = await ing.save();
ingredientIDs.push(savedIngredient._id);
}
return ingredientIDs;
};
const ingredientIDs = await saveIngredients();
const recipe = new Recipe({
name: body.name,
ingredients: ingredientIDs,
});
const savedRecipe = await recipe.save();
res.status(201).json(savedRecipe);
});

Discord.js | "user_id" has an "undefined" value

Following the discord.js guide for creating a currency system. I'm getting this error: WHERE parameter "user_id" has invalid "undefined" value
I can run my !balance command fine and I'm getting currency per message. This is for the !inventory command. My code:
index.js
client.on('messageCreate', async message => {
if (message.author.bot) return;
if (!message.content.startsWith(prefix)) return;
const commandBody = message.content.slice(prefix.length);
const args = commandBody.split(' ');
const command = args.shift().toLowerCase();
if (command === 'balance') {
const target = message.author;
return message.reply(`${target.tag} has ${currency.getBalance(target.id)}🍰!`);
}
if (command == "inventory") {
const target = message.author;
const user = await Users.findOne({where: {user_id: target.id}});
const items = await user.getItems();
if (!items.length) return message.reply(`${target.tag} has nothing!`);
return message.reply(`${target.tag} currently has ${items.map(i => `${i.amount} ${i.item.name}`).join(' , ')}`);
}
});
Users.js
module.exports = (sequelize, DataTypes) => {
return sequelize.define('users', {
user_id: {
type: DataTypes.STRING,
primaryKey: true,
},
balance: {
type: DataTypes.INTEGER,
defaultValue: 0,
allowNull: false,
},
}, {
timestamps: false,
});
};
I made sure to run the dbInit.js file.
I'm a bit lost on what to try, honestly. This is my first time working with Discord.js; I expected to see the user inventory, but it seems like the user_id is undefined and I don't know how to fix that.
The problem is not the WHERE-clause in the main code, but the WHERE-clause within the code of getItems().
I managed to find a solution here:
https://github.com/discordjs/guide/discussions/1085
It relates to the final code published on github by the creators of the guide. I don't know why it's different than in the guide, but it is:
https://github.com/discordjs/guide/blob/main/code-samples/sequelize/currency/14/dbObjects.js

Not able to get the value of a custom attribute in hyperledger-fabric using cid.getAttributeValue function

Other than the 3 attributes hf.EnrollmentId, hf.type and hf.Affiliation, I've created a custom attribute named email and added it as attrs:[{name: 'email', value: rahul18#gmail.com, ecert: true}] and it was successfully added to the attribute list.
In my chaincode, i'm able to get the enrollmentId by using the following command : cid.GetAttributeValue(ctx.GetStub(), "hf.EnrollmentID") but i'm not able to get the email using the same method cid.GetAttributeValue(ctx.GetStub(), "email")
Any help would be appreciated regarding why the first one is working and the second isn't
Does getAttributeValue not support custom made attributes?
Here is an example that may be helpful. A previous stackoverflow contribution helped me with a similar situation. I don't have the link for it right now, but thanks anyway.
First of all, you state that you have added attributes successfully. Here is some code as an example which I had placed in the code file for registering users.
//create user attr array
let registerAttrs = [];
let registerAttribute = {
name: "recycler",
value: config.recycler,
ecert: true,
};
registerAttrs.push(registerAttribute);
const secret = await ca.register({
affiliation: config.affiliation,
enrollmentID: config.recycler,
role: "client",
attrs: registerAttrs,
},
adminUser
);
The contract code is able to find the value of "recycler" using the following code. Of particular importance is the getCurrentUserId() function.
async getCurrentUserId(ctx) {
let id = [];
id.push(ctx.clientIdentity.getID());
var begin = id[0].indexOf("/CN=");
var end = id[0].lastIndexOf("::/C=");
let userid = id[0].substring(begin + 4, end);
return userid;}
async getCurrentUserType(ctx) {
let userid = await this.getCurrentUserId(ctx);
// check user id; if admin, return type = admin;
// else return value set for attribute "type" in certificate;
if (userid == "admin") {
return userid;
}
return ctx.clientIdentity.getAttributeValue(userid);}
The user type returned from the getCurrentUserType function is subsequently examined further up in the contract code, as shown in the following example.
async readTheAsset(ctx, id) {
let userType = await this.getCurrentUserType(ctx);
const buffer = await ctx.stub.getState(id);
const asset = JSON.parse(buffer.toString());
asset.userType = userType;
asset.userID = ctx.clientIdentity.getID();
if (asset.userType === "recycler") {
throw new Error(`The record cannot be read by ${asset.userType} `);
}
return asset;}
I feel sure that this code should solve your issue, as there is a lot of similarity.
const updateObj = {
enrollmentID : userName,
type:'client',
affiliation:'' ,
attrs: [{name: 'email', value: email, ecert: true}, {name: 'orgId', value: orgId, ecert: true}, {name: 'userId', value: userName, ecert: true}] ,
}
const response = await identityService.update(userName, updateObj ,adminUser)
const clientUser = await provider.getUserContext(userIdentity, userName);
const reenrollment = await caClient.reenroll(clientUser,
[{
name: 'email',
optional: false
},
{
name: 'orgId',
optional: false
},
{
name: 'userId',
optional: false
}
]);

Creating a relationship between models with additional info in Mongo, Express, Node

somewhat new to Node and been struggling with this model relationship and not finding an answer here.
I have four models I'm trying to create relationships between:
User
Review
Topics
Courses
When a User leaves a Review on a Course in a certain Topic, I want to track a "topic score" on the User model.
So if a User Reviewed a programming Course, they should get +10 to their programming Topic score. Then I should be able to query User.scores.programming to get their Programming score.
The Reviews are being created fine, it's just the Topic scoring part where I'm running into issues.
Here's how my User schema are set up, just the relevant part:
const userSchema = new mongoose.Schema({
...
scores: [{
topic: {
type: mongoose.Schema.ObjectId,
ref: 'Topic'
},
score: {
type: Number,
default: 0
}
}]
});
And here's the code I have so far right now for trying to increment the score:
const updateUserScores = async (userId, course) => {
const user = await User.findOne({_id: userId}).populate('scores');
const userScores = user.scores;
let topics = await Course.findOne({_id: course}).populate('tags');
topics = topics.tags.map(x => x._id);
// I know it works for here to get the array of topics that they need to be scored on
// Then we need to go through each topic ID, see if they have a score for it...
// If they do, add 10 to that score. If not, add it and set it to 10
for (topic in topics) {
const operator = userScores.includes(topic) ? true : false;
if (!operator) {
// Add it to the set, this is not currently working right
const userScoring = await User
.findByIdAndUpdate(userId,
{ $addToSet: { scores: [topic, 10] }},
{ new: true}
)
} else {
// Get the score value, add 10 to it
}
}
}
I know I probably have a few different things wrong here, and I've been very stuck on making progress. Any pointers or examples I can look at would be extremely helpful!
Alright so after lots of messing around I eventually figured it out.
User model stayed the same:
scores: [{
topic: {
type: mongoose.Schema.ObjectId,
ref: 'Tag'
},
score: {
type: Number,
default: 0
}
}]
Then for the code to increment their score on each topic when they leave a review:
const updateUserScores = async (userId, course) => {
const user = await User.findOne({_id: userId}).populate({
path : 'scores',
populate: [
{ path: 'topic' }
]
});
let userScores = user.scores;
userScores = userScores.map(x => x.topic._id);
let topics = await Course.findOne({_id: course}).populate('tags');
topics = topics.tags.map(x => x._id);
for (t of topics) {
const operator = userScores.includes(t) ? true : false;
if (!operator) {
const userScoring = await User
.findByIdAndUpdate(userId,
{ $addToSet: { scores: {topic: t, score: 10}}},
{ new: true}
);
} else {
const currentScore = await user.scores.find(o => o.topic._id == `${t}`);
const userScoring = await User
.findByIdAndUpdate(userId,
{ $pull: { scores: {_id: currentScore._id}}},
{ new: true }
)
const userReScoring = await User
.findByIdAndUpdate(userId,
{ $addToSet: { scores: {topic: t, score: (currentScore.score + 10)}}},
{ new: true }
)
}
}
}
Ugly and not super elegant, but it gets the job done.

Dealing with models and model methods using Node + Knex + Postgres

I was hoping to get some help. I just started using Postgres with my Node applications and am curious to find out how to go about dealing with models and model methods. What is the best practice when working with Node and Postgres in regards to models and methods? I was looking around and all I could find is something called Objection, but is it absolutely necessary I take that route?
Ideally I would like to have a model.js file for each component but I have not seen them used when dealing with Postgres + Node.
Any help is greatly appreciated. Thanks guys, hope you all had a great Thanksgiving!
Assuming that viewer can understand basic javascript modules and that the codes are mostly self explanatory
This is my model.js file
module.exports = ({
knex = require('./connection'),
name = '',
tableName = '',
selectableProps = [],
timeout = 1000
}) => {
const query = knex.from(tableName)
const create = props => {
delete props.id
return knex.insert(props)
.returning(selectableProps)
.into(tableName)
.timeout(timeout)
}
const findAll = () => {
return knex.select(selectableProps)
.from(tableName)
.timeout(timeout)
}
const find = filters => {
return knex.select(selectableProps)
.from(tableName)
.where(filters)
.timeout(timeout)
}
const update = (id, props) => {
delete props.id
return knex.update(props)
.from(tableName)
.where({
id
})
.returning(selectableProps)
.timeout(timeout)
}
const destroy = id => {
return knex.del()
.from(tableName)
.where({
id
})
.timeout(timeout)
}
return {
query,
name,
tableName,
selectableProps,
timeout,
create,
findAll,
find,
update,
destroy
}
}
This is my controller.js file
const model = require('./model');
const user = model({
name: "users",
tableName: "tbl_users",
});
const getAllUsers = async (req, res, next)=>{
let result = await user.findAll();
res.send(result);
}
module.exports = { getAllUsers }
And Lastly a the connection.js file
const knex = require('knex')({
client: 'pg',
connection: {
host: 'YOUR_HOST_ADDR',
user: 'YOUR_USERNAME',
password: 'YOUR_PASS',
database: 'YOUR_DB_NAME'
},
pool: {
min: 0,
max: 7
}
});
module.exports = knex;

Resources