Posting Schema.Types.ObjectId arrays to MongoDB - arrays

How can I post an array of Schema.Types.ObjectId (s) to MongoDB? I'm trying to create User Groups, which is a group of the 'User' Model e.g.
var UserGroup = new Schema({
users: [{
type: Schema.Types.ObjectId,
ref: 'User'
}]
});
New UserGroup Function
module.exports.create = function(request, response) {
var group = new UserGroup({
users = request.body.users
});
group.save(function(error) {
if(error) { throw error; } else { response.send('Group Created Successfully.');
});
};
I'm currently using Postman to test the functionality, how exactly should the data be posted?
As a Javascript array i.e ['A_USER_ID', 'A_USER_ID'] ?
Thanks!

#Answer
I was using the older syntax of the select() function, and therefore was passing invalid parameters to the $push function. When sending the request, I simply pass the ObjectIds as id,id,id and once they get to the server, simply put it into an array using var my_array = request.body.users.split(','); and then push it to the database using the following:
$push: { users: { $each: my_array } }
I hope this was helpful, the documentation isn't particularly clear on this matter.

Related

Having trouble using Mongoose's find(), what is the correct way to use it?

I'm currently learning MongoDB using mongoose and nodeJS. I'm trying to store notes to a database called 'notes'. For this, first I connected to the database like this:
mongoose.connect(`mongodb+srv://pedro_yanez:${password}#fsopen-2021-project.ngteq.mongodb.net/note-app?retryWrites=true&w=majority`,
{
useNewUrlParser: true,
useUnifiedTopology: true,
})
Then, I created a Note Schema and a Note Model:
const noteSchema = new mongoose.Schema({
content: String,
date: Date,
important: Boolean
})
const Note = mongoose.model('Note', noteSchema)
Then, I saved three documents to the database:
const note = new Note({
content: 'Note #N',
date: new Date(),
important: true
})
note.save().then(result => {
console.log('note saved!')
mongoose.connection.close()
})
This was successfull as I can see them on MongoDB Atlas' collections, but when I try to query the uploaded notes using mongoose's find() method the following way:
Note.find({}).then(result => {
result.forEach(note => {
console.log(note)
})
mongoose.connection.close()
})
I get the following error:
node_modules/mongoose/lib/query.js:2151
return cursor.toArray(cb);
^
TypeError: cursor.toArray is not a function
Note that the code that I attached is from HY's 'Full Stack Open 2021' course, from part3.c.
I also tried to use find() with a callback function as stated here:
Note.find({}, function (err, docs) {console.log(docs)});
mongoose.connection.close()
But I get 'undefined' and another error:
/node_modules/mongodb/lib/collection.js:238
throw new error_1.MongoInvalidArgumentError('Method "collection.find()" accepts at most two arguments');
^
MongoInvalidArgumentError: Method "collection.find()" accepts at most two arguments
I could really use a hint on what's wrong with my implementation, as I've been fighting with this all day!
I see we are in the same exercise on Fullstack open.
I managed to log all "notes" following the quick start from https://mongoosejs.com/
After mongoose.connect:
const db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', function() {
// Here I used the Model.find() at the end of the page and closed
// the connection as they say in the lesson.
});
It worked here, hope it helps.

Comparing results from two API calls and returning their difference in MEAN app

EDIT: Since I wasn't able to find a correct solution, I changed the
application's structure a bit and posted another question:
Mongoose - find documents not in a list
I have a MEAN app with three models: User, Task, and for keeping track of which task is assigned to which user I have UserTask, which looks like this:
const mongoose = require("mongoose");
const autopopulate = require("mongoose-autopopulate");
const UserTaskSchema = mongoose.Schema({
completed: { type: Boolean, default: false },
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
autopopulate: true
},
taskId: {
type: mongoose.Schema.Types.ObjectId,
ref: "Task",
autopopulate: true
}
});
UserTaskSchema.plugin(autopopulate);
module.exports = mongoose.model("UserTask", UserTaskSchema);
In my frontend app I have AngularJS services and I already have functions for getting all users, all tasks, and tasks which are assigned to a particular user (by getting all UserTasks with given userId. For example:
// user-task.service.js
function getAllUserTasksForUser(userId) {
return $http
.get("http://localhost:3333/userTasks/byUserId/" + userId)
.then(function(response) {
return response.data;
});
}
// task-service.js
function getAllTasks() {
return $http.get("http://localhost:3333/tasks").then(function(response) {
return response.data;
});
}
Then I'm using this data in my controllers like this:
userTaskService
.getAllUserTasksForUser($routeParams.id)
.then(data => (vm.userTasks = data));
...and because of autopopulate plugin I have complete User and Task objects inside the UserTasks that I get. So far, so good.
Now I need to get all Tasks which are not assigned to a particular User. I guess I should first get all Tasks, then all UserTasks for a given userId, and then make some kind of difference, with some "where-not-in" kind of filter.
I'm still a newbie for all the MEAN components, I'm not familiar with all those then()s and promises and stuff... and I'm really not sure how to do this. I tried using multiple then()s but with no success. Can anyone give me a hint?
You can do at server/API side that will more efficient.
In client side, if you want to do then try below
var userid = $routeParams.id;
userTaskService
.getAllTasks()
.then((data) => {
vm.userTasks = data.filter(task => task.userId !== userid)
});

configParams undefined in getData() call

I'm in the process of building a community connector and am scratching my head; the documentation states:
getData()
Returns the tabular data for the given request.
Request
#param {Object} request A JavaScript object containing the data
request parameters.
The request parameter contains user provided values and additional
information that can be used to complete the data request. It has the
following structure:
{ "configParams": object, "scriptParams": {
"sampleExtraction": boolean,
"lastRefresh": string }, "dateRange": {
"startDate": string,
"endDate": string }, "fields": [
{
object(Field)
} ] }
I've correctly setup getConfig() (at least, my configurations are requested from the user), but my getData function is not being passed a configParams object. Here's my code.
function getConfig(request) {
var Harvest = HarvestService({
token: getHarvestAuthService().getAccessToken()
});
var accounts = Harvest.accounts.list();
var options = accounts.map(function(account) {
return {
label: account.name,
value: account.id
};
});
var config = {
configParams: [
{
type: 'SELECT_SINGLE',
name: 'harvestAccountId',
displayName: 'Harvest Account ID',
helpText: 'The ID of the Harvest Account to pull data from.',
options: options
}
],
dateRangeRequired: true
};
return config;
}
function getData(request) {
var startDate = request.dateRange.startDate;
var endDate = request.dateRange.endDate;
var accountId = request.configParams.harvestAccountId;
var harvestAuthService = getHarvestAuthService();
var Harvest = HarvestService({
token: harvestAuthService.getAccessToken(),
account: accountId
});
var fieldKeys = request.fields.map(function(field) { return field.name; });
var entries = Harvest.entries.list({
startDate: new Date(startDate),
endDate: new Date(endDate)
});
var rows = entries.map(entryToRow);
return {
schema: request.fields,
rows: rows,
cachedData: false
};
}
When I test/debug, I can select an Account at the config step, the schema is correctly returned, but I get the following exception when I try and add a widget to the report:
Script error message:
TypeError: Cannot read property "harvestAccountId" from undefined.
Script error cause: USER Script
error stacktrace: getData:244
Any advice greatly appreciated.
Found out the problem - the issue was that the value attribute of the option was a number, but it MUST be a string:
https://developers.google.com/datastudio/connector/reference#getconfig
Leaving this here in case anyone else gets stuck on this. Your config select options for your Data Studio Community Connector must have strings for both the label and the value, and nobody will coerce them for you. Fix was this:
var options = accounts.map(function(account) {
return {
label: account.name,
value: account.id + ''
};
});
Usually, request.configParams is undefined when there are no configuration values passed from the user config.
When testing the connector, are you selecting a value in the dropdown for harvestAccountId?
If you plan to share this connector with other users, it might be a good idea to have a default value for harvestAccountId in case the user does not select an option.
You can use Apps Script logging to see the response for getConfig() to ensure that right values are getting passed for options. Then you can also log the request for getData() to have a better understanding of what exactly is getting passed in the request.
Leaving this in case it helps anyone, note that the config params in the UI although they have a placeholder value need to be physically populated to appear in the request. Indeed, if none of these are filled in the configParams value does not appear in the request object.

Mongoose: Part of array

I have this MongoDB Notification Model:
User: Mongo ID
Created: Date
Content: {}
Subscriptions: [ {user: Mongo Id, read: Boolean} ]
Using Mongoose in NodeJS I create a query that return all the documents where my user is inside the Subscriptions array.
Notification
.find({subscriptions:{$elemMatch: {user: req.user}}})
.sort('-created')
.exec(function (err, notifications) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
res.jsonp(notifications);
}
});
This query works, and returns the documents that I wanted... But I don't want exactly this... This code returns, in the documents returned, all subscriptions array, including other users. If there are 10.000 users inside the subscriptions array, all the users are returned in all documents.
How can I extract a part of the array? I want that only my user is returned inside the subscriptions array. Why? for security and performance reasons.
I tried this query:
.find({subscriptions:{$elemMatch: {user: req.user}}}, {'subscriptions.$': 1})
This query only returns one user in the subscriptions array (my user). But... Is not returned any field more except the subscriptions array...
Any suggestion?
You can include other fields in your result like this:
.find({ 'subscriptions': { '$elemMatch': { 'user': req.user }}},
{ 'subscriptions.$': 1, 'content': 1, 'created': 1, 'user': 1 }
)

NodeJS create json object from array

I'm working with some arrays in node and I want to send if as one JSON object to the front-end. I use express to do this. I have a model called User where I find users based on their email. That email is provided in an array. I do get the user object but I can't create one JSON object out of them!
I have tried some middleware but that didn't give me any result! https://www.npmjs.com/package/node.extend
var users = {};
for (var i = 0; i <emails.length; i++) {
User.findOne({
'email': project.students[i]
}, function (err, user) {
if (err) {
res.send(err);
}
// Fill the users object with each user found based on the email
});
}
console.log(users); // Should be one JSONObject
Thanks for the help!
You should be able to do this in a single query. It looks like you're using mongoose, so try something like this:
User.find({ email: { $in: emails } }, function(err, results) {
if (err) return res.send(err);
res.send(results);
});
It's also worth noting that javascript is single threaded. This means that a lot of operations happen asynchronously, meaning you have to wait for the operation to get done before you can move on. Your console logging statement above doesn't wait for the database operation to complete. You have to wait for the callback function to execute.
UPDATE: Also just noticed that you are looping over emails but then using project.students[i] within each iteration. I can't see the rest of your code, but this is just buggy code. You should be either looping over project.students or using emails[i] within each iteration.
UPDATE 2: It appears that you are wanting to send more than just an array of user with the response. So the first goal is to use a single query using the $in operator (see example above - you should be able to pass a list of emails to mongoose). Anything mongo-related, you always want to reduce the amount of queries to the database if you care at all about performance. The second task is to reformat your users and other data accordingly:
var finalResponse = { token: "12341234", users: null };
User.find({ email: { $in: emails } }, function(err, results) {
if (err) return res.send(err);
if (!results.length) return res.send(finalResponse);
// OPTION 1: Array of users (recommended)
finalResponse.users = results;
// OPTION 2: users object, keyed by the users email
finalResponse.users = {};
results.forEach(function(user) {
finalResponse.users[user.email] = user;
});
// FINALLY, send the response
resp.send(finalResponse);
});

Resources