MongoDB issue with saving value returned from findByID - mongodb

I have an issue with function which update password. What I would like to have is a function which will update logged user data.
export const updateMe = async (req, res, next) => {
if (!req) {
res.status(400).end()
}
try {
const updatedDoc = await User.findById(req.user._id, function(err, doc) {
if (err) return next(err)
doc.password = req.body.password
doc.save()
})
.lean()
.exec()
res.status(200).json({ data: updatedDoc })
} catch (e) {
console.log(e)
res.status(400).end()
}
}
I have written middleware which will hash password before it will be saved.
userSchema.pre('save', function(next) {
if (!this.isModified('password')) {
return next()
}
bcrypt.hash(this.password, 8, (err, hash) => {
if (err) {
return next(err)
}
this.password = hash
next()
})
})
I do not know why error is always reciving with message "doc.save() is not a funcition"

You are mixing promise and await code, also doc.save() returns a promise so you need to await it.
( I assume you are already setting req.user._id in a middleware, and it is not null.)
So your method must be like this if async/await is used:
export const updateMe = async (req, res, next) => {
if (!req.body.password) {
return res.status(400).send("Password is required");
}
try {
let updatedDoc = await User.findById(req.user._id);
updatedDoc.password = req.body.password;
updatedDoc = await updatedDoc.save();
res.status(200).json({ data: updatedDoc });
} catch (e) {
console.log(e);
res.status(400);
}
};

Related

Why are my tests occasionally passing and occasionally failing? (Jest, Mongoose, MongoDB)

I have a setup file for my tests that looks like this:
const mongoose = require('mongoose');
mongoose.set('useCreateIndex', true);
mongoose.promise = global.Promise;
async function removeAllCollections() {
const collections = Object.keys(mongoose.connection.collections);
for (const collectionName of collections) {
const collection = mongoose.connection.collections[collectionName];
await collection.deleteMany();
}
}
async function dropAllCollections() {
const collections = Object.keys(mongoose.connection.collections);
for (const collectionName of collections) {
const collection = mongoose.connection.collections[collectionName];
try {
await collection.drop();
} catch (error) {
// Sometimes this error happens, but you can safely ignore it
if (error.message === 'ns not found') return;
// This error occurs when you use it.todo. You can
// safely ignore this error too
if (error.message.includes('a background operation is currently running'))
return;
console.log(error.message);
}
}
}
export default function setupDB(databaseName) {
// Connect to Mongoose
beforeAll(async () => {
const url = `mongodb://127.0.0.1/${databaseName}`;
await mongoose.connect(
url,
{
useNewUrlParser: true,
useCreateIndex: true,
useUnifiedTopology: true
},
err => {
if (err) {
console.error(err);
process.exit(1);
}
}
);
});
// Cleans up database between each test
afterEach(async () => {
await removeAllCollections();
});
// Disconnect Mongoose
afterAll(async () => {
await dropAllCollections();
await mongoose.connection.close();
});
}
I am then writing tests like this:
import User from 'db/models/User';
import setupDB from 'utils/setupDbForTesting';
setupDB('mongoose_bcrypt_test');
it('correctly hashes and salts passwords', async done => {
// create a user a new user
const newUser = new User({
username: 'jmar777',
password: 'Password123'
});
await newUser.save(function (err) {
if (err) {
console.log(err);
}
});
const user = await User.findOne({ username: 'jmar777' });
user.comparePassword('Password123', function (err, isMatch) {
if (err) throw err;
expect(isMatch).toBeTruthy();
});
user.comparePassword('123Password', function (err, isMatch) {
if (err) throw err;
expect(isMatch).toBeFalsy();
});
done();
});
However, every other time I run these tests, they pass (or fail) so for every time T that the tests pass, T + 1 they will fail. My question is - why?
The tests fail because user (in the callback for User.findOne) returns null, even though the user has been saved.
I think the issue lies in the tearing down of the database, but I really can't see any problems. Any help would be appreciated, thanks.

Firebase cloud functions not waiting for forEach to complete before jumping to the next then

Been trying to copy subcollections of a collection into another collection. The code below is aimed at that, but jumps from the first then and logs out "Done" without logging out anything before.
So the question is what is not correct here?
exports = module.exports = functions.https.onRequest(async (req, res) => {
let db = admin.firestore();
try {
await db.collection("users").get().then((query) => {
return query.forEach(async (doc) => {
console.log("Here"); //This doesn't print
const polCollection = await db.collection("users").doc(doc.id).collection("xyz").get();
if (polCollection.docs.length > 0) { //This checks if any subcollections
for (const x of polCollection.docs) { //This copies them into a doc in the copy collection
db.collection("CopyUsers")
.doc(doc.id)
.set({ x : x.data() }, { merge: true });
}
}
});
})
.then(() => {
console.log("Done"); //This is the only thing that prints in the console
res.end();
})
.catch((e) => {
console.log("e", e);
res.end();
});
} catch (error) {
console.log("error", error);
res.end();
}
});
After the suggestion below, it now looks as follows:
exports = module.exports = functions.runWith(runtimeOpts).https.onRequest(async (req, res) => {
const promises = [];
let count = 0;
let size = 0;
return await admin
.firestore()
.collection("testUsers")
.get()
.then((query) => {
console.log("query length:", query.size); //prints x of users
size = query.size;
query.forEach(async (doc) => {
const promise = async () => {
console.log("Here", doc.id); //This doesn't print
await admin
.firestore()
.collection("testUsers")
.doc(doc.id)
.collection("xyz")
.get()
.then(async (polCollection) => {
if (polCollection.docs.length > 0) {
for (const x of polCollection.docs) {
return await admin
.firestore()
.collection("testBackUpUsers")
.doc(doc.id)
.set(
{ xyz: x.data() },
{ merge: true }
);
}
} else {
return;
}
})
.catch((e) => console.log("error from then after get xyz", e));
};
count++;
return promises.push(promise);
});
return promises;
})
.then(async (promises) => {
if (size <= count) {
console.log("running return Promise.all(promises)", promises.length); //prints number of promises = users
return Promise.all(promises);
}
})
.catch((e) => console.log("err from the last catch", e));
});
Any thoughts?
Unfortunately the forEach iterator does not support async/await. Even if you write an await inside it will just go trough it without waiting on the execution.
I would recommend to use Promise.all. That would also execute the code in parallel and would finish faster.
If you would only change data you could also use a batch change but in your example you first get the data and then change it.
Here is an example how you could write your code:
exports = module.exports = functions.https.onRequest(async (req, res) => {
let db = admin.firestore();
const promises = [];
try {
const query = await db.collection("users").get();
query.forEach((doc) => {
console.log("doc", doc);
const promise = async () => {
console.log("Here", doc.id); //This doesn't print
const polCollection = await db
.collection("users")
.doc(doc.id)
.collection("xyz")
.get();
if (polCollection.docs.length > 0) {
//This checks if any subcollections
for (const x of polCollection.docs) {
//This copies them into a doc in the copy collection
await db
.collection("CopyUsers")
.doc(doc.id)
.set({ x: x.data() }, { merge: true });
}
}
};
promises.push(promise);
});
console.log("promises", promises);
await Promise.all(promises);
console.log("Done");
res.end();
} catch (error) {
console.log("error", error);
res.end();
}
});

"No current user" from currentSession() AWS Amplify and Cognito

I am trying to get a REST API to return currentSession() from AWS Amplify and Cognito. The SignIn function works and i get a CognitoUser object returned, however, the getSession() function returns "No current user"
const Amplify = require('aws-amplify');
const express = require('express');
const router = express.Router();
Amplify.default.configure({
Auth: {
region: 'REGION',
userPoolId: 'USERPOOLID',
userPoolWebClientId: 'WEBCLIENTID',
authenticationFlowType: 'AUTHTYPE',
}
})
router.post('/', (req, res) => {
async function signIn(username, password) {
try {
Amplify.Auth.signIn(username, password)
.then(() => getSession())
.catch(err => console.log(err));
} catch (error) {
res.json(error);
}
}
async function getSession() {
try {
Amplify.Auth.currentSession()
.then(data => console.log(data))
.catch(err => console.log(err));
} catch (error) {
res.json('error');
}
}
signIn(req.body.u, req.body.p);
});
Help!

asyncData get profile from db

So i want to fetch from db using asyncdata and axios, Here's the code, The problem is that no request is sent, And i'm wondering if someone can help me catch the error.
async asyncData({ $axios, store }) {
try {
let profile = await $axios.$get('/profile', store.state.auth.id)
return { profile }
} catch (error) {
console.log(error.message)
}
},
router.get('/profile', async (req, res) => {
const { userId } = req.body
try {
const profileUser = await User.findById(userId)
res.send(profileUser)
} catch (e) {
console.log(e)
res.status(400).json(e.message)
}
})
You may need to modify your route to the following, if you want to pass the id as parameter
router.get('/profile/:id', async (req, res) => {
const { userId } = req.params.id;
try {
const profileUser = await User.findById(userId)
res.send(profileUser)
} catch (e) {
console.log(e)
res.status(400).json(e.message)
}
})
and add profile id as route parameter
async asyncData({ $axios, store }) {
try {
let profile = await $axios.get('/profile/{profile_id_here}')
return { profile }
} catch (error) {
console.log(error.message)
}
}
Otherwise, if you want to get the id of the authenticated user (may be resolved from a Bearer token), it needs to be set to the request object in you authentication middleware.
In your authentication middleware,
const user = await _authService.validateFromToken(bearerToken);
if (user) {
req.user = user;
}
then you can access authenticated user as,
router.get('/profile', async (req, res) => {
const { userId } = req.user._id;
try {
const profileUser = await User.findById(userId)
res.send(profileUser)
} catch (e) {
console.log(e)
res.status(400).json(e.message)
}
})

How to call another process when AWS Context callbackWaitsForEmptyEventLoop = false

I'm using the best practice MongoDB with Lambda example from here https://docs.atlas.mongodb.com/best-practices-connecting-to-aws-lambda/
I need to publish to SNS but are unable to due to the callbackWaitsForEmptyEventLoop = false, if I uncomment this, it works fine but then my Lambda function just times out and never receive the success callback.
"use strict";
const MongoClient = require('mongodb').MongoClient;
const MONGODB_URI = process.env.MONGODB_URI; // or Atlas connection string
const AWS = require('aws-sdk');
const SNS_TOPICARN = process.env.SNS_TOPICARN;
const sns = new AWS.SNS({ apiVersion: '2010-03-31' });
let cachedDb = null;
function connectToDatabase(uri) {
console.log('=> connect to database');
if (cachedDb) {
console.log('=> using cached database instance');
return Promise.resolve(cachedDb);
}
return MongoClient.connect(uri)
.then(db => {
cachedDb = db;
return cachedDb;
});
}
function queryDatabase(db) {
console.log('=> query database');
return db.collection('items').find({}).toArray()
.then((data) => { return { statusCode: 200, data: data }; })
.catch(err => {
console.log('=> an error occurred: ', err);
return { statusCode: 500, data: null };
});
}
exports.handler = (event, context, callback) => {
context.callbackWaitsForEmptyEventLoop = false;
console.log('event: ', event);
connectToDatabase(MONGODB_URI)
.then(db => queryDatabase(db))
.then(result => {
console.log('=> returning result: ', result);
var params = {
Message: result.data,
Subject: 'Devices Lost Connection',
TopicArn: SNS_TOPICARN
};
sns.publish(params, function (err, data) {
if (err) console.log(err, err.stack);
else console.log(data);
});
callback(null, result);
})
.catch(err => {
console.log('=> an error occurred: ', err);
callback(err);
});
};
Problem solved, will leave it here if someone else has the same issue:
I need to do the handler callback in the callback of the second function:
sns.publish(params, function (err, data) {
if (err) console.log(err, err.stack);
else console.log(data);
callback(null, result);
});