How to save a Nested Object in MongoDB - mongodb

const checkAuth = require("../../../util/checkAuth");
const { Post } = require("../../../models/post");
const { Like } = require("../../../models/like");
const upvoteComment = async (_, { username, postID, commentID }, context) => {
checkAuth(context);
/* TODO Securety concern one Person can upvote the same post via post request */
const like = new Like({
username: username,
createdAt: new Date(),
});
const post = await Post.findOne({ _id: postID });
const findComment = (post) => {
for (let i = 0; post.comments.length > i; i++) {
if (post.comments[i]._id == commentID) {
return i;
}`enter code here`
}
};
const index = findComment(post);
console.log(index, "index");
console.log(post.comments[0]);
post.comments[index].likes.push(like);
post.save();
return like;
};
module.exports = { upvoteComment };
I am trying to save a Like which I have modelled into a Comment on a Post. First I fetch the Post from my Mongo DB database, then I loop through the comments to find the comment with the id of the comment that was liked and I push the new like object into that comments likes array but when I save the like will not be saved... Does this have something to do with nesting?
So i changed the Code to this
const checkAuth = require("../../../util/checkAuth");
const { Post } = require("../../../models/post");
const { Like } = require("../../../models/like");
const upvoteComment = async (_, { username, postID, commentID }, context) => {
checkAuth(context);
/* TODO Securety concern one Person can upvote the same post via post request */
const like = new Like({
username: username,
createdAt: new Date(),
});
const post = await Post.findOne({ _id: postID });
console.log(post.comments);
const findComment = (post) => {
for (let i = 0; post.comments.length > i; i++) {
if (post.comments[i]._id == commentID) {
return i;
}
}
return -1;
};
const index = findComment(post);
/* console.log(index, "index");
console.log(post.comments[index]);
*/
if (index !== -1) {
post.comments[index].likes.push(username);
post.comments[index].likes.push(like);
post.title="this title will be saved"
const res = await post.save();
console.log("likes on the server: ", res.comments[index].likes);
}
const savedPost = await Post.findOne({ _id: postID });
console.log("likes on Database: ", savedPost.comments[index].likes);
return like;
};
module.exports = { upvoteComment };
and the console will return this:
likes on the server: [
'username',
{
_id: 6081838453f4aa1ff4db6b5e,
username: 'username',
createdAt: 2021-04-22T14:09:08.817Z
}
]
likes on Database: []
This does indeed change the title of the post so I have concluded that mongoDB sees the post.comment[i].likes field as uneditable even though i have declared it as an Array... The type of item doesnt matter for this either as i have tested it in this example.

Try this once hope it works for you.
const upvoteComment = async (_, { username, postID, commentID }, context) => {
checkAuth(context);
/* TODO Securety concern one Person can upvote the same post via post request */
const like = new Like({
username: username,
createdAt: new Date(),
});
const post = await Post.findOne({ _id: postID });
const findComment = (post) => {
for (let i = 0; post.comments.length > i; i++) {
if (post.comments[i]._id == commentID) {
return i;
}
}
return -1;
};
const index = findComment(post);
console.log(index, "index");
console.log(post.comments[0]);
if(index !== -1)
post.comments[index].likes.push(like);
await post.save();
return like;
};

Try using
post.comments[i].likes.push({
username: "johndoe",
createdAt: new Date(),
});
instead of using new Like({...})
By the way, you should use findIndex for conciseness and clarity:
...
const i = post.comments.findIndex(comment => comment._id === commentID);
post.comments[i].likes.push(like);
await post.save();
...

So the Problem lied within my Schema definiotion and was a simple Typo I apologize to everyone that tried to wrap their head around this...

Related

Bot doesnt want to take id from db

I'm making a command where u can setup a bot status channel. Everything so far worked fine, db even writes down id of guild and channel but when I want to use it in ready.js is says its not defined.
Little help would be handy and grateful
Code:
https://pastebin.com/GnxtxFwG - main command
https://pastebin.com/hhy90czw - schema
https://pastebin.com/jVDGu43T - error
Error file:
const { EmbedBuilder } = require('discord.js');
const { channelId } = require('../../schemas/status');
module.exports = {
name: "ready",
once: "true",
async execute(client) {
console.log(channelId)
console.log('Bot is up and ready to work!')
const uptime = new EmbedBuilder()
.setColor('ffd9c0')
.addFields(
{ name: 'Capy is back and online!', value: 'Bot was probably offline due to a bug or maintenance.'},
{ name: '\u200b', value: 'If Capy isnt working how is he supposed to be contact on of our Developers or contact <#574849327650963469> directly.'},
)
const channel = await client.channels.cache.get(channelId);
channel.send({ embeds: [uptime] });
setInterval(client.pickPresence, 10 * 1000);
},
};
Client on function
const { EmbedBuilder } = require('discord.js');
const { Statuses } = require('../../schemas/status');
const uptime = require('../../commands/tools/status')
module.exports = {
name: "ready",
once: "true",
async execute(client) {
console.log(Statuses.channelId)
console.log('Bot is up and ready to work!')
const uptimeEmbed = new EmbedBuilder()
.setColor('ffd9c0')
.addFields(
{ name: 'Capy is back and online!', value: 'Bot was probably offline due to a bug or maintenance.'},
{ name: '\u200b', value: 'If Capy isnt working how is he supposed to be contact on of our Developers or contact <#574849327650963469> directly.'},
)
const channel = await client.channels.cache.get(Statuses.channelId);
Statuses.findById({ channelId: message.guild.id }, async (err, data) => {
if (data) {
channel.send({ embeds: [uptimeEmbed] });
} else {
new Statuses ({
guildId: interaction.guild.id,
chnanelId: uptime ? uptime : "None"
}).save()
console.log("Status channel wasnt set on this server yet!")
}
});
setInterval(client.pickPresence, 10 * 1000);
},
};
Schema
const { Schema, model } = require('mongoose');
const statusSchema = new Schema({
guildId: String,
channelId: String,
});
module.exports = model("Status", statusSchema, "Statuses");
Main command
const {
SlashCommandBuilder,
ChatInputCommandInteraction,
EmbedBuilder,
} = require("discord.js");
const Statuses = require('../../schemas/status');
module.exports = {
data: new SlashCommandBuilder()
.setName("status")
.setDescription("Select the language of the bot")
.addChannelOption(uptime => {
return uptime
.setName("channel")
.setDescription("Channel you want to send the FAQ embed in")
.setRequired(true)
}),
/**
*
* #param {ChatInputCommandInteraction} interaction
* #param {Client} client
*/
async execute(interaction, client) {
const { options } = interaction;
const uptime = options.getChannel("channel");
const user = interaction.user;
const status = new EmbedBuilder()
.setColor("#bb6464")
.setDescription(`Status message was set to ${uptime}!`)
.setFooter({ text: `Request from ${interaction.user.username}`, iconURL: user.displayAvatarURL()})
.setTimestamp();
const status2 = new EmbedBuilder()
.setColor("#bb6464")
.setDescription(`Status message was changed to ${uptime}!`)
.setFooter({ text: `Request from ${interaction.user.username}`, iconURL: user.displayAvatarURL()})
.setTimestamp();
let statusProfile = await Statuses.findOne({ channelId: interaction.guild.id });
if (!statusProfile) {
statusProfile = await new Statuses({
guildId: interaction.guild.id,
channelId: uptime,
});
await statusProfile.save().catch(console.error);
interaction.reply({ embeds: [status] })
console.log(statusProfile);
} else {
await interaction.reply({ embeds: [status2] });
console.log(statusProfile);
}
}
}

How to get other member discord id?

i want to make command that can give me information about someone that i mention like !info #Someone i try code below, but didnt work.
This is the schema
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const profileSchema = new Schema({
_id: mongoose.Schema.Types.ObjectId,
userID: String,
nickname: String,
ar: Number,
server: String,
uid: Number,
});
module.exports = mongoose.model("User", profileSchema);
and this is what i try, but show nothing, didnt show any error sign.
client.on("message", async msg => {
let member = msg.mentions.users.first().username
if (msg.content === `!info #${member}`){
userData = await User.findOne({userID : msg.mentions.users.first().id});
if (userData) {
const exampleEmbed = new MessageEmbed()
.setColor('#808080')
.setTitle('Data Member')
.setDescription(`**Nickname :** ${userData.nickname}\n**Adventure Rank :** ${userData.ar}\nServer: ${userData.server}\n**User ID :** ${userData.uid}`)
.setThumbnail(msg.author.avatarURL())
msg.reply({ embeds: [exampleEmbed] });
} else{
msg.reply("Please registration first")
}
}
}
);
By seeing your code, it might shuffle all of your .first() lets modify your code.
client.on("message", async msg => {
let member = msg.mentions.members.first() || msg.guild.members.fetch(args[0]); //You can also use their ID by using these
if (msg.content === `!info ${member.username || member.user.username}`) { //then adding the user.username
const userData = await User.findOne({
userID: member.id || member.user.id //same as here
}); //userData shows as "any" so you need to change it to const userData
if (userData) {
const exampleEmbed = new MessageEmbed()
.setColor('#808080')
.setTitle('Data Member')
.setDescription(`**Nickname :** ${userData.nickname}\n**Adventure Rank :** ${userData.ar}\nServer: ${userData.server}\n**User ID :** ${userData.uid}`)
.setThumbnail(msg.author.avatarURL())
msg.reply({
embeds: [exampleEmbed]
});
} else {
msg.reply("Please registration first")
}
}
});
Change the if condition. How Discord Mentions Work
Discord uses a special syntax to embed mentions in a message. For user mentions, it is the user's ID with <# at the start and > at the end, like this: <#86890631690977280>.
if (msg.content === `!info ${message.mentions.users.first()}`)
For example:
const member = msg.mentions.users.first();
if (msg.content === `!info ${member}`){
User.findOne({ userID: member.id }, (err, user) => {
if (err) return console.error(err);
if (!user) return msg.reply("User not found");
console.log(user);
});
}
Going through your code, I found these errors.
first of all you need members not users in message.mentions.members.first().
Second of all, you need to define UserData first like const UserData = ...
client.on("message", async msg => {
let member = msg.mentions.members.first()
if (msg.content === `!info #${member}`){
User.findOne({userID : member.id}, async (err, userData) => {
if (userData) {
const exampleEmbed = new MessageEmbed()
.setColor('#808080')
.setTitle('Data Member')
.setDescription(`**Nickname :** ${userData.nickname}\n**Adventure Rank :** ${userData.ar}\nServer: ${userData.server}\n**User ID :** ${userData.uid}`)
.setThumbnail(msg.author.avatarURL())
msg.reply({ embeds: [exampleEmbed] });
} else{
msg.reply("Please registration first")
}
}
});
});
Let me know if it works after fixing these errors.
Also message event is depricated so try using MessageCreate instead from now on

MongoDB doesn't add a new user after previous request with UNIQUE referral code. MongoDB webpage "REFRESH" helps only. What I did wrong?

Auth.js (CONTROLLER):
const bcrypt = require('bcryptjs')
const jwt = require('jsonwebtoken')
const User = require('../models/User')
const keys = require('../config/keys')
const errorHandler = require('../utils/errorHandler')
// REGISTRATION
module.exports.register = async function(req, res) {
const candidate = await User.findOne({email: req.body.email})
const phone = await User.findOne({phone: req.body.phone})
if (candidate) {
// user exist
res.status(409).json({
message: 'Email is taken. Try another one!'
})
} else {
// new user
const salt = bcrypt.genSaltSync(10)
const password = req.body.password
const user = new User({
username: req.body.username,
password: bcrypt.hashSync(password, salt),
})
try {
await user.save()
res.status(201).json(user)
} catch(e) {
errorHandler(res, e)
}
}
}
Auth.js (ROUTES):
const express = require('express')
const controller = require('../controllers/auth')
const router = express.Router()
//localhost:5000/api/auth/register
router.post('/register', controller.register)
module.exports = router
User.js(MODEL):
const mongoose = require('mongoose')
const Schema = mongoose.Schema
const userSchema = new Schema({
username: {
type: String,
unique: false
},
password: {
type: String,
required: true
},
codeRef: {
type: String,
default: makeid(6).toString(),
unique: true
}
})
// GENERATE RANDOM REFFERAL LINK
function makeid(length) {
var result = ''
var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
var charactersLength = characters.length
for ( var i = 0; i < length; i++ ) {
result += characters.charAt(Math.floor(Math.random() * charactersLength))
}
return result
}
module.exports = mongoose.model('users', userSchema)
Everything works fine, the new user added successfully.
The problem comes with another added user after the first one. Generated referral code gives error until we refresh the MongoDB database or restart the server (NPM).
(Results from POSTMAN)
{
"success": false,
"message": "E11000 duplicate key error collection: Cluster.users index: codeRef_1 dup key: { codeRef: \"DDP1SF\" }"
}
Logs from console:
POST /api/auth/register 201 457.563 ms - 350
POST /api/auth/register 500 304.148 ms - 142
POST /api/auth/register 500 190.155 ms - 142
Tried to change the const to var... Should I clear the Schema data of previous request somehow? Because my generated random code for 6 symbols is the same until refresh.
MODELS should NOT contain functions because they are static.
Placing the function inside CONTROLLER file did the work.
// GENERATE RANDOM REFFERAL LINK
function makeid(length) {
var result = ''
var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
var charactersLength = characters.length
for ( var i = 0; i < length; i++ ) {
result += characters.charAt(Math.floor(Math.random() * charactersLength))
}
return result
}
To show the response after the server was updated, use new: true :
const user = new User({
username: req.body.username,
password: bcrypt.hashSync(password, salt),
codeRef: makeid(6).toString(),
new: true
})

Accounts.createUser without username, password and email

My application is built with React, which is completely separate from Meteor. I use Asteroid to interface to Meteor which serves as backend only. I have manually created the Facebook login button at front end and want to pass the data fetched from Facebook to Accounts.createUser. This method asks for two parameters which is not available because I have formatted it like so:
const data = {
services: {
facebook: fb
},
profile: {
first_name: fb.first_name,
last_name: fb.last_name,
}
}
I have created a method as below but I failed to log the user in with appropriate token or what ever indicator that Meteor needed:
getLoginByExternalService(options) {
if (Meteor.userId()) throw new Meteor.Error('400',`Please logout ${Meteor.userId()}`);
const email = options.services.facebook.email
const facebookId = options.services.facebook.id
const user = {services: {}}
user.services = options.services
const users = Meteor.users.find({"services.facebook.id": facebookId}).fetch();
if (!users.length) {
const userId = Accounts.insertUserDoc(options, user)
if (Meteor.isServer)
this.setUserId(userId)
else
Meteor.setUserId(userId)
return userId
} else {
if (Meteor.isServer)
this.setUserId(users[0]._id)
if (Meteor.isClient)
Meteor.setUserId(userId)
return {users, userId: Meteor.userId()}
}
}
How to properly log the user in?
Okay I already got the answer. I don't have to format the data return from facebook response. So here the implementation at the backend
getLoginByExternalService(resp) {
if (Meteor.userId()) Meteor.logout(Meteor.userId()) //who knows?
const accessToken = resp.accessToken
const identity = getIdentity(accessToken)
const profilePicture = getProfilePicture(accessToken)
const serviceData = {
accessToken: accessToken,
expiresAt: (+new Date) + (1000 * resp.expiresIn)
}
const whitelisted = ['id', 'email', 'name', 'first_name', 'last_name', 'link', 'username', 'gender', 'locale', 'age_range']
const fields = _.pick(identity, whitelisted)
const options = {profile: {}}
const profileFields = _.pick(identity, getProfileFields())
//creating the token and adding to the user
const stampedToken = Accounts._generateStampedLoginToken()
//hashing is something added with Meteor 0.7.x,
//you don't need to do hashing in previous versions
const hashStampedToken = Accounts._hashStampedToken(stampedToken)
let ref = null
_.extend(serviceData, fields)
_.extend(options.profile, profileFields)
options.profile.avatar = profilePicture
try {
ref = Accounts.updateOrCreateUserFromExternalService("facebook", serviceData, options);
} catch (e) {
if (e.reason === "Email already exists.") {
const existingUser = Meteor.users.findOne({ 'emails.address': identity.email })
if ( existingUser ) {
if ( identity.verified ) {
Meteor.users.update({ _id: existingUser._id }, { $set: { 'services.facebook': serviceData }})
ref = { userId: existingUser._id }
console.log(`Merged facebook identity with existing local user ${existingUser._id}`);
} else {
throw Meteor.Error(403, "Refusing to merge unverified facebook identity with existing user")
}
}
} else {
throw Meteor.Error(e.error, e.reason)
}
}
Meteor.users.update(ref.userId, {$push: {'services.resume.loginTokens': hashStampedToken}})
return {id: ref.userId, token: stampedToken.token}
}
so somewhere at the front end
asteroid.call("getLoginByExternalService", data).then(response => response)

How to test node.js-mongodb app using Mocha, Chai and Sinon? I am having difficulty with Sinon part

I am new to testing and having difficulty with Sinon stubs and mocks.
How can I write test for 'quote.list_quote' for following senario.
Here is the routes file : quotes.js
const express = require('express');
const request = require('request');
const async = require('async');
const validator = require('validator');
const quote_router = express.Router();
const confg = require("../../confg/confg");
const quote = require("../models/mquotes");
const quotes_model = quote.quotes;
// host name - needs to be set up using the environment variable
const hostname = confg.hostname;
// route for "quotes/"
quote_router.route("/")
// get route : display the random quote
.get((req, res) => {
// display random quote
async.waterfall([
(callback) => {callback(null, {res});},
quote.count_quotes
], check_quote_exist
);
})
// post route : create a new quote
.post((req, res) => {
const doc_json = {author : validator.escape(req.body.quote_author), quote_text : validator.escape(req.body.quote_text)};
const params = {res, doc_json, quote_action : quote.create_quote};
add_edit_quote(params);
})
// put route : edit the quote
.put((req, res) => {
const doc_json = {author : validator.escape(req.body.quote_author), quote_text : validator.escape(req.body.quote_text)};
const params = {res, doc_json, quote_action : quote.update_quote, qid : req.body.quote_id};
add_edit_quote(params);
})
// delete quote : delete the quote
.delete((req, res) => {
const qid = req.body.qid;
const condition = {_id : qid};
async.waterfall([
(callback) => {callback(null, {res, condition});},
quote.delete_quote
], request_quote_list
);
});
// route for "quotes/list" : display quotes list
quote_router.get("/list/", (req, res) => {
// mention the main operation
let operation;
if(req.body.operation != 'undefined') {
operation = req.body.operation;
} else {
operation = "list_quotes";
}
async.waterfall([
(callback) => {callback(null, {res, operation});},
quote.list_quote
], display_quotes_list
);
});
// display the quotes list
const display_quotes_list = (err, params, quotes_list) => {
if (err) {return console.log(err);}
const res = params.res;
const operation = params.operation;
const header_msg = "List of all the quotes";
let alert_msg;
if(operation == "list_quotes") {
alert_msg = null;
} else if(operation == "delete_quote") {
alert_msg = "Quote has been deleted";
}
const params_out = {
page: "quote_list",
title: 'Quotes Manager',
host: hostname,
header_msg,
alert_msg,
quotes_list
};
res.render('index', params_out);
};
// send http request for quote list page
const request_quote_list = (err, params) => {
if (err) {return console.log(err);}
const res = params.res;
const operation = "delete_quote";
request.get('http://' + hostname + '/quotes/list/', {json:{operation}},
(error, response, body) => {
if (!error && response.statusCode == 200) {
res.send(body);
}
});
};
module.exports = quote_router;
This is not complete file. I have included only a portion of it.
And her is the model file : mquotes.js
const mongoose = require('mongoose');
// Define quote schema
const quoteSchema = new mongoose.Schema({
author: String,
quote_text: {type: String, required: true}
},
{timestamps: true}
);
const quote = {};
// Define quotes model
quote.quotes = mongoose.model('quotes', quoteSchema);
// error handler
error_handler = (callback, err, params, return_value) => {
if(err) { return callback(err);}
else {callback(null, params, return_value);}
};
// add quote - create
quote.create_quote = (params, callback) => {
const res = params.res;
const doc_json = params.doc_json;
quote.quotes.create(doc_json, (err, quotes_detail) => {
error_handler(callback, err, {res, operation : 'create_quote'}, quotes_detail);
});
};
// count the number of quotes
quote.count_quotes = (params, callback) => {
quote.quotes.count({}, (err, quotes_count) => {
error_handler(callback, err, params, quotes_count);
});
};
// delete quote - delete - id
quote.delete_quote = (params, callback) => {
quote.quotes.remove(params.condition, (err, query) => {
error_handler(callback, err, params);
});
};
// list quote - find
quote.list_quote = (params, callback) => {
quote.quotes.find({}, (err, quotes_list) => {
error_handler(callback, err, params, quotes_list);
});
};
// find quote by id
quote.quote_by_id = (params, callback) => {
quote.quotes.findById(params.qid, (err, quotes_detail) => {
error_handler(callback, err, params, quotes_detail);
});
};
// returns the detail of random quote
quote.random_qoute = (params, callback) => {
const random_number = params.random_number;
// select one quote after skipping random_number of times
quote.quotes.findOne({}, (err, quotes_detail) => {
error_handler(callback, err, params, quotes_detail);
}).skip(random_number);
};
// update quote - update - id
quote.update_quote = (params, callback) => {
const options = {new: true};
const qid = params.qid;
const update_json = params.doc_json;
quote.quotes.findByIdAndUpdate(qid, {$set: update_json}, options, (err, quotes_detail) => {
params.operation = 'update_quote';
error_handler(callback, err, params, quotes_detail);
});
};
module.exports = quote;
I have installed mocha globally. Now, I want to test the model. Lets take the quote.list_quote function for example.
const mongoose = require('mongoose');
const chai = require('chai');
const sinon = require('sinon');
const expect = chai.expect; // use the "expect" style of Chai
const mquotes = require('./../../app/models/mquotes');
describe('Tests for quote models', () => {
describe("List quote", () => {
it('list_quote() should return list of quotes', () => {
});
});
});
Can anyone tell me about my coding practice too. I mean the way I use functions and modules.
First of all, you should try to use statics methods. And after that, you should use sinon-mongoose and sinon-as-promised if you want to use Promise in mongoose.
And this is my sample code and test with mocha, chai, and sinon. Hope useful for you.
model.js
var Schema = new mongoose.Schema({
name: String,
created_at: {
type: Date,
default: Date.now
},
updated_at: {
type: Date,
default: Date.now
}
});
Schema.statics.findByName = function(name, cb) {
this.findOne({
name: name
})
.exec()
.then(function getTemplate(template) {
if (!template) {
var error = new Error('Not found template by name: "' + name + '"');
error.status = 404;
return cb(error);
}
return cb(null, template);
})
.catch(function catchErrorWhenFindByTemplateName(error) {
error.status = 500;
return cb(error);
});
}
module.exports = mongoose.model('model', Schema);
test.js
var expect = require('chai').expect,
sinon = require('sinon'),
mongoose = require('mongoose');
require('sinon-as-promised');
require('sinon-mongoose');
var Model = require('../../app/models/model');
describe('Model', function () {
describe('static methods', function () {
describe('#findByName', function () {
var ModelMock;
beforeEach(function () {
ModelMock = sinon.mock(Model);
});
afterEach(function () {
ModelMock.restore();
});
it('should get error status 404 if not found template', function (done) {
var name = 'temp';
ModelMock
.expects('findOne').withArgs({name: name})
.chain('exec')
.resolves(null);
Model.findByName(name, function (error) {
expect(error.status).to.eql(404);
ModelMock.verify();
done();
});
});
it('should get message not found template if name is not existed', function (done) {
var name = 'temp';
ModelMock
.expects('findOne').withArgs({name: name})
.chain('exec')
.resolves(null);
Model.findByName(name, function (error) {
expect(error.message).to.match(/Not found template by name/gi);
ModelMock.verify();
done();
});
});
it('should get template when name is existed', function (done) {
var name = 'temp';
ModelMock
.expects('findOne').withArgs({name: name})
.chain('exec')
.resolves('SUCCESS');
Model.findByName(name, function (error) {
expect(error).to.be.null;
ModelMock.verify();
done();
});
});
it('should get error status 500 when model crashed', function (done) {
var name = 'temp';
ModelMock
.expects('findOne').withArgs({name: name})
.chain('exec')
.rejects(new Error('Oops! Crashed'));
Model.findByName(name, function (error) {
expect(error.status).to.eql(500);
ModelMock.verify();
done();
});
});
});
});
});