How to save mongoose data after PATCH? - mongodb

My Feathers.js app has a questionnaire and I'm trying to save the results for each input. Here is my client-side code:
$.ajax({
url: "/answer",
type: "PATCH",
data: newAnswer,
success: function () {
console.log("Answer submitted!");
},
error: function () {
console.log("Error submitting answer.");
}
});
And here is my server-side code:
app.patch("/answer", (req, res) => {
users.patch(user[0]._id, req.body);
});
Currently, only the last input is saved. What am I doing wrong?
EDIT: Here is my schema
answers: {
yourOrganization: {
page1: {
idOfParameterInClient: response
}
}
}
The idOfParameterInClient and the response are dynamic. In page1, there are a number of key/value pairs. How do I keep them?

patch does not do a recursive merge (neither will MongoDB). If that is what you are looking for you will have to do it yourself e.g. using the deepmerge package:
const merge = require('deepmerge');
app.patch("/answer", (req, res) => {
const id = user[0]._id;
users.get(id).then(user => {
const mergedUser = merge(user.toObject(), req.body);
users.patch(id, mergedUser);
});
});

#Daff put me on the right track. In the end, object-patch allowed me to do recursive merges. Here is my code:
const patch = require("object-patch");
app.patch("/answer", (req, res) => {
const id = user[0]._id;
users.get(id).then(user => {
patch(user, req.body);
users.update(user._id, user);
});
});
👍🏾

Related

Inserting a record from a mongoose model.statics function

I want to create a static function on a mongoose "log" module, which would allow me to write a message as a log entry.
How do I access the model from within the static function? Can I use this.model like below? I don't want to simply use native MongoDB insert command, because I want the model to validate the input, etc.
// ... schema defined above...
var Log = mongoose.model('Log', LogModelSchema)
Log.statics.log = function(message) {
var x = new this.model({message: message})
x.save()
.then(() => { .. do something .. }
.catch((err) => { .. handle err .. }
}
Is this the way it's supposed to be done?
You can make it work like this using this.create:
const mongoose = require("mongoose");
const logSchema = new mongoose.Schema({
message: String
});
logSchema.statics.log = function(message) {
this.create({ message: message })
.then(doc => console.log(doc))
.catch(err => console.log(err));
};
module.exports = mongoose.model("Log", logSchema);
Now you can use this in your routes like this:
Log.log("test");
or just return promise from statics:
logSchema.statics.log = function(message) {
return this.create({ message: message });
};
And use like this:
const Log = require("../models/log");
router.get("/log", (req, res) => {
Log.log("test")
.then(result => {
console.log(result);
res.send("ok");
})
.catch(err => {
console.log(err);
res.status(500).send("not ok");
});
});

Admin-on-rest 'Edit Page - Incorrect Element Error // Create Page - element does not exist Error'

I'm using admin on rest with express/mongodb, every things works correctly (CRUD), but I have some errors that have appeared and I have no explanation for that,
when I create an object a notification is displayed "element does not exist" but the object is created correctly and stored in mongodb.
And when I try to update a object (Edit) a notification is displayed "Incorrect element" but the object is been updated correctly and stored in mongodb.
this is my server code:
// =================================================================
// configuration ===================================================
// =================================================================
var port = process.env.PORT || 8060; // used to create, sign, and verify tokens
mongoose.connect(config.database, {
useNewUrlParser: true,
user: config.database_user,
pass: config.database_pass
});
// connect to database
app.set('superSecret', config.secret); // secret variable
// use body parser so we can get info from POST and/or URL parameters
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
// use morgan to log requests to the console
app.use(morgan('dev'));
app.use(cors());
app.use(function (req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
res.header("Access-Control-Expose-Headers", "X-Total-Count, Content-Range");
next();
});
app.set('etag', false);
// =================================================================
// Post module ================================================
// =================================================================
//-------------------------------
// list all post-----------------
//-------------------------------
app.get('/post', function (req, res) {
Post.find({}, function (err, posts) {
var postsMap = [];
posts.forEach(function (post) {
postsMap.push({ id: post._id, title: post.title, content: post.content})
});
res.setHeader('Content-Range', posts.length);
res.send(postsMap);
});
});
//-------------------------------
// find a postt-----------------
//-------------------------------
app.get('/post/:id', function (req, res) {
Post.findById({_id: req.params.id }, function (err, post) {
res.send(post);
});
});
//-------------------------------
// create new post-----------------
//-------------------------------
app.post('/post', apiRoutes, function (req, res) {
var post = new Post({
title: req.body.content,
content: req.body.title
});
post.save(function(err) {
if (err) throw err;
res.json({ success: true });
});
});
//-------------------------------
// update a post-----------------
//-------------------------------
app.put('/post/:id', apiRoutes, function (req, res) {
if (typeof req.body.content === 'undefined' || typeof req.body.title === 'undefined') {
res.send(400, { message: 'no content provided' })
} else {
Post.update({ '_id': req.params.id }, { title: req.body.title, content: req.body.content }, function (err, post) {
if (err) return res.send(500, { error: err });
return res.send({ message: 'success update', post: post });
});
}
});
//-------------------------------
// delete a post-----------------
//-------------------------------
app.delete('/post/:id', apiRoutes, function (req, res) {
if (typeof req.body.content === 'undefined' || typeof req.body.title === 'undefined') {
res.send(400, { message: 'no content provided' })
} else {
Post.delete({ '_id': req.params.id }, { title: req.body.title, content: req.body.content }, function (err, post) {
if (err) return res.send(500, { error: err });
return res.send({ message: 'success update', post: post });
});
}
});
this is some of my rest client request apicalls :
OPTIONS /post 204 0.096 ms - 0
POST /post 200 2.179 ms - 16
OPTIONS /post/undefined 204 0.098 ms - 0
GET /post/undefined 200 0.288 ms - -
OPTIONS /post?filter=%7B%7D&range=%5B0%2C9%5D&sort=%5B%22id%22%2C%22DESC%22%5D 204 0.065 ms - 0
GET /post?filter=%7B%7D&range=%5B0%2C9%5D&sort=%5B%22id%22%2C%22DESC%22%5D 200 2.977 ms - 589
OPTIONS /post/5d4819ed1458a84b14295626 204 0.061 ms - 0
GET /post/5d4819ed1458a84b14295626 200 1.411 ms - 76
PUT /post/5d4819ed1458a84b14295626 200 1.422 ms - 64
OPTIONS /post?filter=%7B%7D&range=%5B0%2C9%5D&sort=%5B%22id%22%2C%22DESC%22%5D 204 0.071 ms - 0
GET /post?filter=%7B%7D&range=%5B0%2C9%5D&sort=%5B%22id%22%2C%22DESC%22%5D 200 1.947 ms - 643[![enter image description here][1]][1]
These two requests are ambiguous for some reason
OPTIONS /post/undefined 204 0.088 ms - 0
GET /post/undefined 200 0.536 ms - -
I'm using simpleRestClient ,
my App.js :
const httpClient = (url, options = {}) => {
if (!options.headers) {
options.headers = new Headers({ Accept: 'application/json' });
}
options.headers.set('x-access-token', localStorage.getItem('token'));
return fetchUtils.fetchJson(url, options);
};
const restClient = simpleRestClient(API_URL, httpClient);
const App = () => (
<Admin
title="أرشيفارا"
customRoutes={customRoutes}
customReducers={{ theme: themeReducer }}
menu={Menu}
authClient={authClient}
restClient={restClient}
appLayout={Layout}
messages={translations}
>
<Resource name="post" list={PostList} edit={PostEdit} create={PostCreate} />
</Admin>
);
export default App;
This is most probably because mongo react-admin expects all resources to have an id property, and not _id like mongo set it by default.
You'll have to decorate the dataProvider (or modify your API) so that it transform _id into id.
If you're not sure about how to decorate the dataProvider, ping me here and I'll update the answser with an example.
PS: migrate from admin-on-rest to react-admin (the new shiny version) :)
// In src/myRestClient.js
// Convert a MongoDB entity which has an _id property
// to an entity with an id property as react-admin expect
const convertToReactAdmin = ({ _id, ...item }) => ({
id: _id,
...item,
});
// Decorate the simple rest client so that it convert the data from the API
// in the format expected by react-admin
const mongoDBClient = dataProvider => async (type, resource, params) => {
// Execute the API call and wait for it to respond
// It will always return an object with a data, and sometime a total (GET_LIST and GET_MANY)
const { data, total } = await dataProvider(type, resource, params);
switch (type) {
case 'GET_LIST':
case 'GET_MANY':
case 'GET_MANY_REFERENCE':
return {
data: data.map(convertToReactAdmin),
total, // For GET_MANY, total will be undefined
};
case 'GET_ONE':
case 'CREATE':
case 'UPDATE':
case 'DELETE':
return { data: convertToReactAdmin(data) };
default:
return { data };
}
};
const httpClient = (url, options = {}) => {
if (!options.headers) {
options.headers = new Headers({ Accept: 'application/json' });
}
options.headers.set('x-access-token', localStorage.getItem('token'));
return fetchUtils.fetchJson(url, options);
};
const restClient = simpleRestClient(API_URL, httpClient);
export default MongoDBClient(restClient);
// In src/App.js
import restClient from './myRestClient';
const App = () => (
<Admin
title="أرشيفارا"
customRoutes={customRoutes}
customReducers={{ theme: themeReducer }}
menu={Menu}
authClient={authClient}
restClient={restClient}
appLayout={Layout}
messages={translations}
>
<Resource name="post" list={PostList} edit={PostEdit} create={PostCreate} />
</Admin>
);
export default App;
That's how I fixed this problem, like #GildasGarcia said the problem was in mongodb's id property set by default : _id .
So I created a new json object data that accepts the properties of the mongodb object
//-------------------------------
// find a postt-----------------
//-------------------------------
app.get('/post/:id', function (req, res) {
Post.findById({_id: req.params.id}, function (err, post) {
var data = {
id: req.params.id,
title: post.title,
content:post.content
};
res.status(200).json(data);
});
});

Puppeteer and express can not load new data using REST API

I'm using puppeteer to scrape page that has contents that change periodically and use express to present data in rest api.
If I turn on headless chrome to see what is being shown in the browser, the new data is there, but the data is not showing up in get() and http://localhost:3005/api-weather. The normal browser only shows the original data.
const express = require('express');
const server = new express();
const cors = require('cors');
const morgan = require('morgan');
const puppeteer = require('puppeteer');
server.use(morgan('combined'));
server.use(
cors({
allowHeaders: ['sessionId', 'Content-Type'],
exposedHeaders: ['sessionId'],
origin: '*',
methods: 'GET, HEAD, PUT, PATCH, POST, DELETE',
preflightContinue: false
})
);
const WEATHER_URL = 'https://forecast.weather.gov/MapClick.php?lat=40.793588904953985&lon=-73.95738513173298';
const hazard_url2 = `file://C:/Users/xdevtran/Documents/vshome/wc_api/weather-forecast-nohazard.html`;
(async () => {
try {
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
await page.setRequestInterception(true);
page.on("request", request => {
console.log(request.url());
request.continue();
});
await page.goto(hazard_url2, { timeout: 0, waitUntil: 'networkidle0' });
hazard = {
"HazardTitle": "stub",
"Hazardhref": "stub"
}
let forecast = await page.evaluate(() => {
try {
let forecasts = document.querySelectorAll("#detailed-forecast-body.panel-body")[0].children;
let weather = [];
for (var i = 0, element; element = forecasts[i]; i++) {
period = element.querySelector("div.forecast-label").textContent;
forecast = element.querySelector("div.forecast-text").textContent;
weather.push(
{
period,
forecast
}
)
}
return weather;
} catch (err) {
console.log('error in evaluate: ', err);
res.end();
}
}).catch(err => {
console.log('err.message :', err.message);
});
weather = forecast;
server.get('/api-weather', (req, res) => {
try {
res.end(JSON.stringify(weather, null, ' '));
console.log(weather);
} catch (err) {
console.log('failure: ', err);
res.sendStatus(500);
res.end();
return;
}
});
} catch (err) {
console.log('caught error :', err);
}
browser.close();
})();
server.listen(3005, () => {
console.log('http://localhost:3005/api-weather');
});
I've tried several solutions WaitUntil, WaitFor, .then and sleep but nothing seems to work.
I wonder if it has something to do with express get()? I'm using res.end() instead of res.send() is because the json looks better when I use res.end(). I don't really know the distinction.
I'm also open to using this reload solution. But I received errors and didn't use it.
I also tried waitForNavigation(), but I don't know how it works, either.
Maybe I'm using the wrong search term to find the solution. Could anyone point me in the right direction? Thank you.

ES6 Promises in express app not properly resolving data

I'm writing an a async function with ES6 promises, that 1) saves the query parameters for a user 2) fetches data from mongodb using mongoose, 3) manipulates the json into a DSL, 4) and queries another db with it.
mongoose": "^4.7.7"
//myController.js
const myQuery = require('../models/myQuery_model');
require('mongoose').Promise = global.Promise
const uuidV4 = require('uuid/v4');
exports.saveNewQuery = function(req, res, next) {
const rawQuery = req.body;
const queryToStore = new myQuery(rawQuery);
const uid = uuidV4();
const queryToStore.uid = uid
queryToStore.save().then(() => {
fetchQueryFromMongo(uid);
}).then((storedQuery) => {
compileQueryToString(storedQuery);
}).then((queryString) => {
fetchResultsFromOtherDb(queryString);
}).then((results) => {
res.json({ results });
}).catch((error) => {
console.log(error)
})
}
Currently I'm not able to resolve the response from mongodb step 2. Still, the controllter goes on to compileQueryToString rather than catch the error from fetchQueryFromMongo
// fetchQueryFromMongo.js
const myQuery = require('../models/myQuery');
require('mongoose').Promise = global.Promise
module.exports = (uid) => {
return new Promise(
(resolve, reject) => {
myQuery.find({ uid }).then((err, res) => {
if (err) {
reject(err);
}
console.log('response success!')
resolve(res);
});
}
);
};
I'm new to promises so any tips / suggestions would be appreciated!
Make sure to return a value from your then handlers. The code below does this by using the concise body form of arrow functions.
queryToStore.save()
.then(() => fetchQueryFromMongo(uid))
.then(storedQuery => compileQueryToString(storedQuery))
.then(queryString => fetchResultsFromOtherDb(queryString))
.then(results => res.json({ results }))
.catch(console.log);

REST Routes with mongoose and express

When I try to add a review to my product from the front-end I am getting a 404 error for PUT http://localhost:3000/products. But I am to add/update data using the following curl command using my routes:
curl --data "name=Product 1&description=Product 1 Description&shine=10&price=29.95&rarity=200&color=blue&faces=3" http://localhost:3000/products
My products router
// This handles retrieving of products
// Includes Express
var express = require('express');
// Initialize the router
var router = express.Router();
var moment = require('moment');
var _ = require('underscore');
var color = require('cli-color');
var mongoose = require('mongoose');
var Product = mongoose.model('Product');
var Review = mongoose.model('Review');
// Route middleware
router.use(function(req, res, next) {
console.log("Something is happening in products!!");
next();
});
// GET route for all Products
router.get('/', function (req, res, next) {
Product.find( function (err, products) {
if (err) {
return next(err);
}
res.json(products);
});
});
// POST route for adding a Product
router.post('/', function (req, res, next) {
var product = new Product (req.body);
product.save( function (err, post) {
if(err) {
return next(err);
}
res.json(product);
});
});
// Pre-loading product object
router.param('product', function (req, res, next, id) {
var query = Product.findById(id);
query.exec( function (err, product) {
if (err) {
return next(err);
}
if(!product) {
return next(new Error('can\'t find product'));
}
req.product = product;
return next();
})
});
// GET route for retrieving a single product
router.get('/:product', function (req, res) {
req.product.populate('reviews', function (err, product) {
if (err) {
return next(err);
}
res.json(req.product);
});
});
// POST route for creating a review
router.post('/:product:reviews', function (req, res, next) {
var review = new Review(req.body);
review.product = req.product;
review.save( function (err, review){
if (err) {
return next(err);
}
req.product.reviews.push(review);
req.product.save( function (err, review) {
if (err) {
return next(err);
}
res.json(review);
});
});
});
This code is taken from a tutorial on thinkster for [MEAN stackl2
Original Post
I am having trouble figuring out how to update an existing entry in my mongodb database using a service I defined with ngResource in my Angular app. So far I have been unable to create a function that will update the back-end after a user clicks my submit button. I have been looking around for a solution for about 2 days but so far have not found a solution. I know the solution is similar to how I delete users in My User's Controller, but nothing I have tried has worked.
My Product Service
angular.module('gemStoreApp.productService',['ngResource'])
.factory('productsService', function($resource) {
return $resource('/products/:id', {},{
'update': { method: 'PUT'}
});
});
My Product Detail
angular.module('gemStoreApp')
.controller("ReviewCtrl", ['$scope', '$resource', 'productsService', function ($scope, $resource, productsService) {
this.review = {};
this.addReview = function(product){
product.reviews.push(this.review);
productService.save({id: product._id}, function() {
// I have tried .update, .$update, and .save methods
});
this.review = {};
};
}]);
I have verified that the products.review variable contains the update. Here is a sample of my JSON output from my console before and after adding the review:
Before the review is added to the front end
{"_id":"product_id","name":"Product 1","description":"Product 1 Description",...,"reviews":[{}]}
After the review is added to the front end
{"_id":"product_id","name":"Product 1","description":"Product 1 Description",...,"reviews":[{"stars":4,"body":"An Awesome review!","author":"user#domain.com","createdOn":1436963056994}]}
And I know that my productsService.save() function is being called as well, as I can put a console log in and see it run when I view in the browser.
My User's Controller
angular.module('gemStoreApp')
.controller('UsersCtrl', ['$scope', '$http', 'usersService', function ($scope, $http, usersService) {
$scope.users = {};
$scope.users = usersService.query();
$scope.remove = function(id) {
var user = $scope.users[id];
usersService.remove({id: user._id}, function() {
$scope.users.splice(user, 1);
});
};
}]);
My full source code is available on my Github page. Any help will be greatly appreciated.
I actually put it into work in this plunker
Took the same factory :
app.factory('productsService', function($resource) {
return $resource('product/:id', {id:"#id"},{
'update': { method: 'PUT'}
});
});
here is my controller :
$scope.products = productsService.query();
$scope.saveProduct = function(product){
product.$update();
}
and how i pass the value in the HTML :
<div ng-repeat="product in products">
<input type="text" ng-model="product.text">
<button ng-click="saveProduct(product)">Update</button>
</div>
If you track the networks request in the javascript console you will see a request : PUT /product/id with the updated data.
Hope it helped. If you have anymore question fell free to ask.