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
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");
});
});
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);
});
});
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.
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);
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.