Push ObjectId in Mongoose - javascript

I'm using express, passport, and mongoose. I don't know why but the code below pushes same newTaxReturn._id twice into user.taxReturnIds field. If I remove user.save().catch(() => {}) line, it pushes the newTaxReturn._id correctly i.e. just once. The user argument is from passport.
Problem:
const createTaxReturn = ({ user }) => {
const newTaxReturn = new TaxReturn({ userId: user._id })
user.taxReturnIds.push(newTaxReturn._id)
user.save().catch(() => {})
return newTaxReturn.save().catch(() => {})
}
Schema:
const User = new mongoose.Schema({
taxReturnIds: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'TaxReturn',
}],
})
const TaxReturn = new mongoose.Schema({
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
},
})

On your return you are also calling .save() thus the duplication and the single input when you remove
user.save().catch(() => {})
place your return in a .then or .catch to retrieve the response from mongo
user.save().catch(error => { if (error) return error })

Related

mongoose create function in not function error(sub document)

schema
const UserSchema = new mongoose.Schema({
...,
suported: [{name:String, id:String}],
suporting: [{name:String, id:String}]
},
{ timestamps: true });
Query
const requester = await User.findOne({ _id })
const suporter = await User.findOne({ _id: _idSuporter })
// Result ok
requester.suported.create(data); // causing error
suporter.suporting.create(data);
Error message: requester.suported.create is not a function.
Edited
Links to where you can see what I am expecting
https://mongoosejs.com/docs/subdocs.html#adding-subdocs-to-arrays
https://attacomsian.com/blog/mongoose-subdocuments
The error is happening because it is not possible to call the create function on the "supported" attribute of the User object. What you can do is create a static function that takes the data as a parameter and does something when the function is called, like this:
userSchema.statics.createSupported = function(data: any) {
// do something here..
}
userSchema.statics.createSupporting = function(data: any) {
// do something here..
}
And when you call the query:
const requester = await User.findOne({ _id })
const supporter = await User.findOne({ _id: _idSuporter })
// Result ok
User.createSupported(date)
User.createSupporting(data)

How to push data with Mongoose to a nested array in MongoDB

I'm trying to push data to a nested array in mongodb. I'm using mongoose as well.
This is just mock code to see if i can get it working:
User model:
import mongoose from "mongoose";
const CoinSchema = new mongoose.Schema({
coinID: { type: String },
});
const CoinsSchema = new mongoose.Schema({
coin: [CoinSchema],
});
const WatchlistSchema = new mongoose.Schema({
watchlistName: { type: String },
coins: [CoinsSchema],
});
const NameSchema = new mongoose.Schema({
firstName: { type: String },
lastName: { type: String },
username: { type: String },
});
const UserSchema = new mongoose.Schema({
name: [NameSchema],
watchlists: [WatchlistSchema],
test: String,
});
const User = mongoose.model("User", UserSchema);
export default User;
route:
fastify.put("/:id", async (request, reply) => {
try {
const { id } = request.params;
const newCoin = request.body;
const updatedUser = await User.findByIdAndUpdate(id, {
$push: { "watchlists[0].coins[0].coin": newCoin },
});
await updatedUser.save();
// console.dir(updatedUser, { depth: null });
reply.status(201).send(updatedUser);
} catch (error) {
reply.status(500).send("could not add to list");
}
});
request.body // "coinID": "test"
I've tried a lot of different ways to push this data but still no luck. I still get 201 status codes in my terminal which indicates something has been pushed to the DB, but when I check nothing new is there.
Whats the correct way to target nested arrays and push data to them?
It's not perfect but you could get the user document, update the user's watchlist, and then save the updated watchlist like so:
fastify.put("/:id", async (request, reply) => {
try {
const { id } = request.params;
const newCoin = request.body;
// get the user
let user = await User.findById(id);
// push the new coin to the User's watchlist
user.watchlists[0].coins[0].coin.push(newCoin);
//update the user document
const updatedUser = await User.findOneAndUpdate({ _id: id },
{
watchlists: user.watchlists,
},
{
new: true,
useFindAndModify: false
}
);
reply.status(201).send(updatedUser);
} catch (error) {
reply.status(500).send("could not add to list");
}
});

HTTP put request in node and mongodb

I am trying in mongoDB and node to add subscriptions to my competitionEvent object.
My issue is that I can write only one subscription and not add them one after another.
Here is my http file:
const express = require('express')
import * as bodyParser from 'body-parser'
// import { eventApplication } from './compositionRoot'
import { CompetitionModel } from './mongo'
export const app = express()
app.use(bodyParser.json())
// WORKS - find all events
app.get('/events', async (_req: any, res: any) => {
const comp = await CompetitionModel.find()
res.send(comp)
})
// WOKRS - find just one event
app.get('/events/:event_id', async (req: any, res: any) => {
const searchedComp = await CompetitionModel.find(req.params)
res.send(searchedComp)
})
// WORKS - posts a new comp event
app.post('/new-comp', async (req: any, res: any) => {
const data = await new CompetitionModel(req.body).save()
res.json(data)
})
// WORKS - posts a new subscription into a comp
app.put('/update/:event_id', async (req: any, res: any) => {
const subs = await CompetitionModel.findOneAndUpdate(
{ event_id: req.params.event_id },
{ subscriptions: req.body },
)
res.send(subs)
})
// TO TEST - deletes a competition event
app.delete('/delete/:event_id', async (req: any, res: any) => {
const toDel = await CompetitionModel.deleteOne({
event_id: req.params.event_id,
})
res.json(toDel)
})
and here is my mongo file:
const mongoose = require('mongoose')
mongoose.connect('mongodb://localhost:27017/CompetitionEvent')
export const CompetitionSchema = new mongoose.Schema({
event_id: String,
compName: String,
place: String,
time: String,
subscriptions: [],
date: Date,
cost: {
currency: String,
amount: Number,
},
})
export const CompetitionModel = mongoose.model(
'CompetitionModel',
CompetitionSchema,
)
export const connection = () =>
new Promise((resolve, reject) => {
mongoose.connection.once('open', () => {
resolve()
})
mongoose.connection.once('error', () => {
reject('oooooh shit')
})
})
Every time I tried to change it it would either not modify the competitionEvent, not put anything or simply replace the old subscription with a new one, which makes little sense I am sure you'll agree
You need to use the $push-operator to add a new subscription to your competition. Assuming req.body holds the new subscription, you can do:
app.put('/update/:event_id', async (req: any, res: any) => {
const subs = await CompetitionModel.findOneAndUpdate(
{ event_id: req.params.event_id },
{ $push: { subscriptions: req.body }},
)
res.send(subs)
});
First of all fix your schema for subscription mongoose.Schema like below, for better type casting:
Optional
const CompetitionSchema = new mongoose.Schema({
event_id: String,
compName: String,
place: String,
time: String,
subscriptions: [{
//what ever field you wanna add
_id: false //if you don't wanna use it as a sub-document
}],
date: Date,
cost: {
currency: String,
amount: Number,
},
})
Then in your competetionEvent controller either use mongo $push operator for adding event subscription at the end of the subscription or use mongo $addToSet operator for adding the subscription in the subscription field without any duplication.
Remember, $push doesn't check if the subscription is unique or not, it just pushes elements like javascript Array.push(). On the other hand, $addToSet checks if the subscription exists or not. If yes then it doesn't add that subscription. If no, then it pushes it to the field Array.
I suggest using $addToSet as it is more secure & will not create any duplicates of the same subscription.
CODE
app.put('/update/:event_id', async (req: any, res: any) => {
const subs = await CompetitionModel.findOneAndUpdate(
{ event_id: req.params.event_id },
{ $addToSet: {subscriptions: req.body}},
)
res.send(subs)
})

How to Encrypt Realm in React Native

I am trying to encrypt Realm on my React native app using the PIN user sets for the app after loging in.
User logs into the app and then sets a PIN which user is going to use for the following interactions with the app. I want to use this PIN as the encryption key.
I have looked at examples on
https://realm.io/docs/javascript/latest/#encryption
Key Generation/Storage for react-native apps to encrypt realm db
Got an idea that I need to add the encryption key at the point of opening the database but how can I pass key to the database options. if you can suggest any better way that would be greatful.
Here is my flow,
ConfirmPIN.js
import { insertUser, deleteUser } from '../../../databases/schemas';
savePin = (userPIN) => {
const newUser = {
id: 1,
userName: 'Here goes the user name',
pin: userPIN
}
deleteUser(1).then().catch((error) => {
console.log(error);
});
insertUser(newUser).then().catch((error) => {
alert(error);
});
}
schemas.js
export const userSchema = {
name: USER_SCHEMA,
primaryKey: 'id',
properties: {
id: 'int',
userName: 'string',
pin: 'string'
}
}
const databaseOptions = {
path: 'retailApp.realm',
schema: [ userSchema, appDataSchema],
schemaVersion: 0 //optional
//SHOULD I BE ADDING THE KEY HERE? IF SO HOW CAN I PASS THE KEY SET BY THE
//USER
}
export const insertUser = user => new Promise ((resolve, reject) => {
Realm.open(databaseOptions).then(realm => {
realm.write(() => {
realm.create(USER_SCHEMA, user);
resolve(user);
});
}).catch((error) => reject(error));
});
export const deleteUser = userID => new Promise((resolve, reject) => {
Realm.open(databaseOptions).then(realm => {
realm.write(() => {
let userToDelete = realm.objectForPrimaryKey(USER_SCHEMA, userID);
realm.delete(userToDelete);
resolve();
});
}).catch((error) => reject(error));
});
securityUtils.js
Code to get the base64Encoded to be the KEY (Not tested how it works yet)
export const getBase64Encodedpin = pin => {
var utf8 = require('utf8');
var binaryToBase64 = require('binaryToBase64');
var text = pin;
var bytes = utf8.encode(text);
var base64Encoded = binaryToBase64(bytes);
return base64Encoded;
}
I want to be able to pass this key to schemas.js when opening the database, can you suggest on how I will be able to do that.
Thanks
R
Here Retrieve your key from secure location or create one like below...
var key = new Int8Array(64);
Add Encryption Key in the schema
export const userSchema = {
name: USER_SCHEMA,
primaryKey: 'id',
properties: {
id: 'int',
userName: 'string',
pin: 'string'
},
encryptionKey: key, // add Your Pin/key Here
}
Add key in databaseOptions
const databaseOptions = {
path: 'retailApp.realm',
schema: [ userSchema, appDataSchema],
schemaVersion: 0, //optional
encryptionKey: key, // add Your Pin/key Here
}
After searching I implemented this ,

MongoDB/Express: Why does Array.includes return false instead of true?

I'm working on an tiny app that allows user to participate in polls, but I'm having problems checking if the current user has already voted in the poll. Everything else works fine, save for the IIFE that checks for said condition, as seen in the code snippet included. Indeed, I'm getting false as opposed to true with the data I have i.e. I already seeded the DB with sample data, including a random poll that contains the array of IDs for users who've already voted. I tried testing one ID against said array, which returns false as opposed to the expected true. What gives?
Below are the relevant snippets.
Model
import mongoose from 'mongoose';
const Schema = mongoose.Schema;
const ChoiceSchema = new Schema({
name: { type: String },
votes: { type: Number }
});
const PollSchema = new Schema({
title: { type: String },
category: { type: String },
choices: [ChoiceSchema],
addedBy: { type: Schema.Types.ObjectId, ref: 'User' },
votedBy: [{ type: Schema.Types.ObjectId, ref: 'User' }]
});
const Poll = mongoose.model('Poll', PollSchema);
export default Poll;
Controllers
import Poll from '../models/poll';
export default {
fetchAllPolls: async (req, res) => {
/*...*/
},
fetchSpecificPoll: async (req, res) => {
/*...*/
},
voteInPoll: async (req, res) => {
const { category, pollId } = req.params;
const { name, choiceId, voterId } = req.body;
try {
const poll = await Poll.findById(pollId);
const choice = await poll.choices.id(choiceId);
const votedChoice = {
name,
votes: choice.votes + 1,
};
// Check if user has already voted in poll
const hasVoted = ((votersIds, id) => votersIds.includes(id))(
poll.votedBy,
voterId
);
if (!voterId) {
res
.status(400)
.json({ message: 'Sorry, you must be logged in to vote' });
} else if (voterId && hasVoted) {
res.status(400).json({ message: 'Sorry, you can only vote once' });
} else {
await choice.set(votedChoice);
await poll.votedBy.push(voterId);
poll.save();
res.status(200).json({
message: 'Thank you for voting. Find other polls at: ',
poll,
});
}
} catch (error) {
res.status(404).json({ error: error.message });
}
},
createNewPoll: async (req, res) => {
/*...*/
},
};
I think you are trying to compare ObjectId with String representing the mongo id.
This should work:
const hasVoted = ((votersIds, id) => votersIds.findIndex((item) => item.toString() === id) !== -1)(
poll.votedBy,
voterId
);
or
const hasVoted = ((votersIds, id) => votersIds.findIndex((item) => item.equals(new ObjectId(id))) !== -1)(
poll.votedBy,
voterId
);
EDIT:
As #JasonCust suggested, a simpler form should be:
const hasVoted = poll.votedBy.some(voter => voter.equals(voterId));
It is more than likely that poll.votedBy is not an array of ID strings. If you are using it as a reference field then it is an array of BSON objects which would fail using includes because it uses the sameValueZero algorithm to compare values. If that is true then you could either convert all of the IDs to strings first or you could use find and the equals methods to find a match.
Update: showing actual code example
Also, some would provide an easier method for returning a boolean value.
const hasVoted = poll.votedBy.some((voter) => voter.equals(voterId));

Categories