Using output of one mongoose query for the input of another in express (async/await) - mongodb

I am using express and mongoose to implement a server/db. I have a working route that gets all the games involving a player by playerID. I am now trying to implement one that can take username instead of playerID.
PLAYER_SCHEMA:
const mongoose = require('mongoose');
const PlayerSchema = mongoose.Schema( {
username: {
type:String,
required:true,
unique:true
},
date_registered: {
type: Date,
default:Date.now
}
});
module.exports = mongoose.model('Player', PlayerSchema);
GAME_SCHEMA:
const mongoose = require('mongoose');
const GameSchema = mongoose.Schema( {
player_1: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Player',
required: true
},
player_2: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Player',
required: true
},
status: {
type:String,
},
hero_1: {
type:String
},
hero_2: {
type:String
},
date_registered: {
type: Date,
default:Date.now
}
});
module.exports = mongoose.model('Game', GameSchema);
Here's what I have to query all games involving a player by playerId:
//GET GAMES INVOLVING PLAYER BY PLAYER_ID
router.get('/player/:playerId', async (req, res) => {
try {
const games = await Game.find({$or:[{ player_1: req.params.playerId }, { player_2: req.params.playerId}]});
console.log(games)
res.json(games);
// weird cuz doesn't throw error if not found, just returns empty list...
}
catch (err) {
res.json({ message: err });
}
});
The following outlines what I want to do, but it doesn't work, for I'm sure many reasons:
I am trying to get the userId from username first, then pass that into a query for the games.
//GET ALL GAMES ASSOCIATED WITH PLAYER BY USERNAME
router.get('/username/:username', async (req, res) => {
try {
const player = await Player.findOne({username:req.params.username});
console.log(player);
const games = Game.find({ $or:[{ player_1: player._id }, { player_2: player._id }] });
res.json(games);
}
catch (err) {
res.json({ message: err });
}
});
I've been reading about .populate(), promises, and waterfalls, but I'm new to this and would love some guidance!

Please try this :
//GET ALL GAMES ASSOCIATED WITH PLAYER BY USERNAME
router.get('/username/:username', async (req, res) => {
try {
const player = await Player.findOne({ username: req.params.username });
console.log(player);
/**
* .findOne() should return a document or null - if no match found..
*/
if (player) {
/**
* .find() will return empty [] only if it didn't find any matching docs but won't throw an error in successful DB operation
* (irrespective of whether docs matched or not, if op is successful then there will be no error).
*/
const games = await Game.find({ $or: [{ player_1: player._id }, { player_2: player._id }] }).lean();
(games.length) ? res.json(games) : res.json(`No games found for ${player._id}`);
} else {
res.json('No player found')
}
}
catch (err) {
res.json({ message: err });
}
});

Related

Having problems to get correct properties with Find and MongoDB

I have these collections:
const { Schema, model } = require("mongoose");
const TaskToDoSchema = Schema({
equipo: {
type: Schema.ObjectId,
ref: "Equipo",
autopopulate: {select: "codigo"}
},
tarea: {
type: Schema.ObjectId,
ref: "Task",
autopopulate: {select: "tipotarea"}
},
encargado: {
type: Schema.ObjectId,
ref: "User",
autopopulate: {select: "name dni"}
},
frecuencia: String
});
TaskToDoSchema.plugin(require("mongoose-autopopulate"));
module.exports = model("TaskToDo", TaskToDoSchema, "taskstodo");
const {Schema, model} = require("mongoose");
const TaskDoneSchema = Schema({
tasktodo: {
type: Schema.ObjectId,
ref: "TaskToDo",
autopopulate: true
},
fechaDone: {
type: Date,
default: Date.now
}
})
TaskDoneSchema.plugin(require("mongoose-autopopulate"));
module.exports = model("TaskDone", TaskDoneSchema, "tasksdone");
The first model defines a determined task that must be performed in a determined machine, by a determined person, all this info is taken from other collections.
The second model is used to define that a tasktodo is done, so I save a new record with the tasktodo Id and the date this task was performed.
I wish to list all the tasks done in a specific machine, by its ID, so, I defined this route:
router.get("/listbyequipo/:id", check.auth, TaskDoneController.listbyequipo);
where check.auth is an authentication middleware.
To perform the query I tried with:
const listbyequipo = async (req, res, next) => {
try {
let equipo = req.params.id;
let taskDone = await TaskDone.find({'tasktodo.equipo._id': equipo})
.exec()
console.log(taskDone)
res.send({
status:"Succes",
message:"Consulta de registros exitosa",
taskDone
})
} catch (error) {
res.send({
status: "Error",
message: "Error al mostrar los registros"
});
next(error);
}
}
But the result is an empty array.
I´m not sure where is the problem, I also tried defining 'tasktodo.equipo._id': mongoose.type.objectId(equipo) but the problem persists.
Probably, this image can help to understand how levels are defined in the collection:
I have also tried this:
//Metodo para listar todas las tareas realizadas en un equipo especifico
const listbyequipo = async (req, res, next) => {
try {
const id = (req.params.id);
const query ={'tasktodo.equipo._id': mongoose.Types.ObjectId(id)}
console.log(id);
let taskDone = await TaskDone.find({query})
res.send({
status:"Succes",
message:"Consulta de registros exitosa",
taskDone
})
} catch (error) {
res.send({
status: "Error",
message: "Error al mostrar los registros"
});
next(error);
}
}
But the result is a list with all the registers in the collection.

How to update a user profile which has a property which is a ref in MongooseJS?

I have a User schema which has reference to a profile schema.
const UserSchema = new Schema(
{
_id: mongoose.Schema.Types.ObjectId,
email: {
....email props...
},
password: {
...password props...
},
profile: [{
type: mongoose.Schema.Types.ObjectId,
ref: "Profile",
}],
},
);
const Profile = new Schema({
_user: {
type: Schema.Types.ObjectId, ref: 'User'
},
'displayName': {
type: String,
default: ''
},
'interestedActivities': ['Ping-pong'], <---- This bad boy/girl is an array
'memberSince': { type: Date, default: Date.now }
}
)
I'd like to create a route which can update the User properties AND the Profile properties in one shot—with a caveat one of the properties on the Profile model is an array!!!
I tried this....
handler
.use(auth)
.put((req, res, next) => {
emailValidator(req, res, next, 'email');
},
async (req, res, next) => {
await connectDB()
const {
profileDisplayName,
profileEmail,
interestedActivities } = req.body;
const update = {
email: profileEmail,
'profile.$.displayName': profileDisplayName,
'profile.$.interestedActivities': interestedActivities
}
const filter = { _id: req.user.id };
const updatedUser = await User.findOneAndUpdate(filter, update, { new: true })
try {
console.log("updatedUser ", updatedUser);
if (updatedUser) {
return res.status(200).send({
updatedUser,
msg: `You have updated your profile, good job!`
});
}
} catch (error) {
errorHandler(error, res)
}
})
export default handler;
My response is:
Status Code: 500 Internal Server Error
Cast to ObjectId failed for value "[
{
id: 'ae925393-0935-45da-93cb-7db509aedf20',
name: 'interestedActivities',
value: []
}
]" (type Array) at path "profile.$"
Does anyone know how I could also afford for the property which is an array?
Thank you in advance!

How create relationship between two collections

I'm creating server app using nodejs(express) and mongodb(mongoose). I must create relationships between Organization model and Users model. After creating an organization, I want to create a user that will apply to a specific organization. One user can apply to many organizations. How can I do this?
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
// UserShema
const UserSchema = Schema({
login: {
type: String,
require: true,
unique: true
},
password: {
type: String,
require: true
},
organization: {
ref: "Organization",
type: Schema.Types.ObjectId
}
});
// Organization Schema
const OrganizationSchema = Schema({
label: {
type: String
},
users: [{
type: Schema.Types.ObjectId,
ref: "Users"
}]
});
//For now I have simple route for creating an Organization.
// request:
// {
// "label": "testOrg"
// }
exports.createOrganization = async (req, res) => {
try {
const org = await new Organization(req.body);
await org.save();
} catch (error) {
return res.status(500).json({error})
}
}
//And I have this route for user registration
exports.signup = async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
};
const {login} = req.body;
try {
const checkUser = await Users.findOne({login});
if (!checkUser) {
const user = await new Users(req.body);
await user.save();
return res.status(200).json({ user });
} else {
return res.status(400).json({error: "User already exist"})
}
} catch (error) {
return res.status(500).json({error})
}
};
You could embed the organization id into a string into the user document
Like this {
name: "Name",
location: "CA",
organizations: [123456789, 234567890, ...]
}

MongoDB query won't return object in my Express API (React)

I have done this so many times before, but I can't seem to find the issue, it's probably something small and stupid. Take a look at the /server.js file here! (Shortened for demonstration purposes)
/* Make Mongoose promise based */
mongoose.Promise = Promise;
mongoose.connect('mongodb://localhost:27017', options);
const db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error: '));
/* Routes */
app.route('/games')
.post(postGame)
.get(getGames);
app.route('/games/:id')
.get(getGame)
.delete(deleteGame);
app.route("*").get((req, res) => {
res.sendFile('client/dist/index.html', { root: __dirname });
});
const port = 8080;
app.listen(port, () => {
console.log(`Connected! Server listening on port: ${port}`);
});
Then for my Game model, I have that in app/models/game.js.
import mongoose from 'mongoose';
const Schema = mongoose.Schema;
const gameSchema = new Schema(
{
name: {
type: String,
required:true
},
year: {
type: Number,
required:true
},
description: {
type: String,
required:true
},
picture: {
type: String,
required:true
},
postDate : { type: Date, default: Date.now }
}
);
export default mongoose.model('Game', gameSchema);
This is where I believe I am having the issue.
/* Import Game model schema */
import Game from '../models/game';
const getGames = (req, res) => {
Game.find({}, (err, games) => {
console.log(err, games)
if (err) {
res.send(err);
}
res.json(games);
});
}
const getGame = (req, res) => {
const { id } = req.params;
Game.findById(id, (err, game) => {
if (err) {
res.send(err);
}
res.json(game);
});
}
const postGame = (req, res) => {
let game = Object.assign(new Game(), req.body);
game.save(err => {
if (err) {
res.send(err);
}
res.json({ message: 'Game successfully created!' });
});
};
const deleteGame = (req, res) => {
Game.remove(
{ _id: req.params.id },
err => {
if (err) {
res.send(err);
}
res.json({ message: 'Game successfully deleted!' });
}
);
};
export {
getGames,
getGame,
postGame,
deleteGame
};
Just do be clear... I went into the mongo shell.
I did...
connecting to: test
> db.createCollection('Game')
> db.Game.insert({name: "SSB", year: 2001, description: "Fun Game", picture: "http://google.com", postDate: "2017-01-03T08:51:45.888Z"});
And when I type > db.Game.find({}); I am returned with exactly what I have...
{
"_id" : ObjectId("58c2223e32daa04353e35bdc"),
"name" : "SSB",
"year" : 2001,
"description" : "Fun Game",
"picture" : "http://google.com",
"postDate" : "2017-01-03T08:51:45.888Z"
}
You see when I go to http://localhost:8080/games I am returned with an empty JSON and I just wanna know why. I am 70% sure, it is because it isn't connected to the right collection but I don't remember how to test that :(
I wanted to make this a comment but it won't let me because I don't have a 50 reputation, but I believe I found the issue.
const getGame = (req, res) => {
const { id } = req.params;
Game.findById(id, (err, game) => {
if (err) {
res.send(err);
}
res.json(game);
});
}
In this piece of code you are setting the id to req.params, but you need to set it to req.params.id which is what you passed in your route.
Should look like this:
const {id} = req.params.id;
If you logged id, you would probably get an object that says:
{ id: "[whatever_id_you_put_here]" }
however if you log req.params.id you should get the correct id you put in that spot..
The reason you're getting [] is because you're actually connected to the database and you are actually trying to "get" something, but that something doesn't exist so it sends an empty response.
I hope this helps..

How to make querys when tou have many to many relationships between models?

i am trying to make a game. I need tu create a Match. I think the problem on this Way. The User create a Match. In a third table I save playerId and gameId. When another user join the match, I save again, playerId and gameId. Then, I make a query with player with gameId in common, and start the game.
first, One User may have many Games. second, One Match may have many Games. this is the Match model:
module.exports = {
attributes: {
name: {
type: 'string'
},
description: {
type: 'string'
},
game: {
collection: 'game',
via: 'gameId',
}
}
};
This is the User model:
var bcrypt = require('bcrypt');
module.exports = {
attributes: {
name: {
type:'string'
},
email: {
type: 'email',
required: true,
unique: true
},
password: {
type: 'string',
},
passwordConfirmation: {
type: 'string'
},
passwordEncrypted: {
type: 'string'
},
creator: {
collection: 'game',
via: 'playerId'
},
toJSON: function(){
var obj = this.toObject();
delete obj.password;
delete obj.passwordConfirmation;
delete obj._csrf;
return obj;
}
}, beforeCreate: function(values, next){
console.log("Acabo de entrar a eforeCreate");
var password = values.password;
var passwordConfirmation = values.passwordConfirmation;
if(!password || !passwordConfirmation || password != values.passwordConfirmation) {
var passwordDoesNotMatchError = [{
name: 'passwordDoesNotMatchError',
message: 'Las contraseñas deben coincidir'
}]
return next({
err: passwordDoesNotMatchError
});
}
require('bcrypt').hash(values.password, 10, function passwordEncrypted(err, EncryptedPassword){
values.EncryptedPassword = EncryptedPassword;
next();
});
}
};
This is the Game model:
module.exports = {
attributes: {
gameId: {
model: 'match'
},
playerId: {
model: 'user'
}
}
};
finally, this is my controller:
module.exports = {
createMatch: function(req,res){
var matchObj = {
name: req.param('name'),
description: req.param('description'),
}
Match.create(matchObj, function(err, match){
if(err){
console.log("el error fue: " + err);
return res.send(err);
} console.log("Entro en create");
return res.json(match);
})
var gameObj = {
gameId: 'aclaration: I dont know how do I get the match.id',
playerId: req.session.me
}
Game.create(gameObj,function(err,game){
console.log("entro a GameCreate");
if(err){
return res.send(err);
} return res.json(game);
})
}
};
I can create the Match, but Game.create send this error:
_http_outgoing.js:344 throw new Error('Can\'t set headers after they are sent.'); ^
Error: Can't set headers after they are sent.
Somebody can help me? probably, I have many errors. Thanks.
Couple of things here:
Having an explicit Game model is not required in Sails. It can manage it implicitly, unless you want to store more information than just gameId and userId. So, you can just do away with Game model.
Please refer for async programming: How do I return the response from an asynchronous call?
Below code should work for you. Hope it helps.
module.exports = {
createMatch: function(req, res) {
var matchObj = {
name: req.param('name'),
description: req.param('description'),
};
Match.create(matchObj, function(err, match) {
if (err) {
console.log("el error fue: " + err);
return res.send(err);
}
console.log("Entro en create");
var gameObj = {
gameId: match.id,
playerId: req.session.me
};
Game.create(gameObj, function(err, game) {
console.log("entro a GameCreate");
if (err) {
return res.send(err);
}
return res.json(game);
// return res.json(match);
});
});
}
};