In my MEAN application, I have a product model like this :
product: {
field: //some string value//,
reviews: //an array of review objects//
}
Now I am going to apply different restrictions for saving field and reviews, so my API endpoints will be like this :
/* specific route for updating reviews */
router.put('/products/reviews/:id', checkRightsToUpdateReviews, updateProductReviews);
/* specific route for updating field */
router.put('/products/:id', checkRightsToUpdateField, updateProductField);
Those endpoints are reached by my Angular service productData, respectively via methods productData.updateReview(product) and productData.updateField(product).
Thus in my productData service, I'm using two resources :
One with url /products/reviews/:id for the updateReview method, and one with url /products/:id for the updateField method.
I feel like I'm not doing the separation of concerns properly.
The problem originates from the fact that my model has fields that have to be treated differently. But it makes sense to me to have all the CRUD operations for products in one service.
What would be an obviously more elegant solution for this?
I'd use one authorization middleware to check for both cases.
router.put('/products/:id', checkUpdateRights, updateProduct);
function checkUpdateRights(req, res, next){
if(req.body.reviews && !hasReviewRights(req)){ // check only if reviews exists in body
res.status(401).send({ error: "Unauthorized to update product reviews" });
}
if(!hasUpdateRights(req)){ // for other fields
res.status(401).send({ error: "Unauthorized to update product" });
}
else{
return next();
}
};
function hasReviewRights(req){
// return true or false
};
function hasUpdateRights(req){
// return true or false
};
Related
I have created several user accounts on mongodb and i want to sort them out by user name. I compare the user names in the database against a string provided through aaxios request with a body value that is taken from an input value, like this:
frontend
const findUsers = async () => {
try {
const response = await axios.post(`http://localhost:8080/search-users/${_id}`, { searchValue });
setReturnedUser(response.data.matchedUser);
} catch (error) {
console.log(error);
}
}
findUsers();
backend
exports.sort = (req, res) => {
let result;
User.find({ name: req.body.searchValue }).exec((error, users) => {
if (error) {
return res.status(400).json({
message: error,
});
}
result = users;
res.status(200).json({
message: 'Description added successfully',
matchedUser: result,
});
});
};
The problem with this approach is that the users are returned only after I type in the entire name.
What I want is that the users to get returned as I type in the name, so several matching users will het returned when I start typing and as I continue the list will narrow down, until only the matching user remains.
I have successfully achieved this on the react side, but that was possible only by fetching all the users from the database, which would be a very bad idea with a lot of users. The obvious solution is to do the sorting on the server.
Filtering on the client-side is possible but with some tweaks to your architecture:
Create an end-point in node that returns all the users as JSON. Add caching onto this end-point. A call to origin would only occur very infrequently. YOu can then filter the list easily.
Use something like GraphQL and Appollo within node. This will help performance
To do the filtering in node you can use a normal array.filter()
I woul do the filter in mongo as the quick approach and then change it if you notice performance issues. It is better no to do pre-optimisation. As Mongo is NoSQL it wshould be quick
I'm working on a Node app with Express. I'm chaining several http calls to data api's, each dependent on the previous req's responses.
It's all working except the last call. The last call needs to happen multiple times before the page should render.
Searching has turned up excellent examples of how to chain, but not make a call to the same API (or HTTP GET, data endpoint, etc.) with different params each time.
I'm trying to do something like this: Using a generator to call an API multiple times and only resolve when all requests are finished?
var getJSON = (options, fn) => {
.....
}
router.route("/")
.get((req, res) => {
var idArray = [];
var results = [];
getJSON({
.... send params here, (result) => {
//add response to results array
results.push(result);
//create var for data nodes containing needed id params for next call
let group = result.groupsList;
//get id key from each group, save to idArray
for(i=0;i<groups.length;i++){
idArray.push(groups[I].groupId);
}
//use id keys for params of next api call
dataCallback(idArray);
});
function dataCallback(myArray){
// number of ID's in myArray determine how many times this API call must be made
myArray.forEach(element => {
getJSON({
.... send params here, (result) => {
results.push(result);
});
// put render in callback so it will render when resolved
}, myRender());
};
function myRender() {
res.render("index", { data: results, section: 'home'});
}
})
I learned the problem with the above code.
You can call functions that are outside of the express route, but you can't have them inside the route.
You can't chain multiple data-dependent calls, not in the route.
Anything inside route.get or route.post should be about the data, paths, renders, etc.
This means either using an async library (which I found useless when trying to build a page from multiple data sources, with data dependent on the previous response), or having an additional js file that you call (from your web page) to get, handle and model your data like here: Using a generator to call an API multiple times and only resolve when all requests are finished You could also potentially put it in your app or index file, before the routes.
(It wasn't obvious to me where that code would go, at first. I tried putting it inside my router.post. Even though the documentation says "Methods", it didn't click for me that routes were methods. I hadn't really done more than very basic routes before, and never looked under the hood.)
I ended up going with a third option. I broke up the various API calls in my screen so that they are only called when the user clicks on something that will need more data, like an accordion or tab switch.
I used an XMLHttpRequest() from my web page to call my own front-end Node server, which then calls the third party API, then the front-end Node server responds with a render of my pug file using the data the API provided. I get html back for my screen to append.
In page:
callFEroutetoapi(_postdata, _route, function (_newdata){
putData(_newdata);
});
function putData(tData){
var _html = tData;
var _target = document.getElementById('c-playersTab');
applyHTML(_target, _html);
}
function callFEroutetoapi(data, path, fn){
//url is express route
var url = path;
var xhr = new XMLHttpRequest();
console.log('data coming into xhr request: ', data);
//xhr methods must be in this strange order or they don't run
xhr.onload = function(oEvent) {
if(xhr.readyState === xhr.DONE) {
//if success then send to callback function
if(xhr.status === 200) {
fn(xhr.response);
// ]console.log('server responded: ', xhr.response);
}
else {
console.log("Something Died");
console.log('xhr status: ', xhr.status);
}
}
}
xhr.onerror = function (){console.log('There was an error.', xhr.status);}
xhr.open("POST", url, true);
xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
xhr.send(JSON.stringify(data));
}
It adds an extra layer, but was necessary to show the latest, frequently changing data. It's also reusable which is better for a multiscreen web app. If there were fewer views (completely different screens and co-dependent datasets), a more centralized model.js file mentioned above would work better.
I am developing a react js application and we are using a promise based library axios for calling APIs.
Now, in the initial part of application, user gets a login page, when the login is successful, we contact different systems to retrieve some extra information about user.
axios
.get('url to authentication endpoint') // 1st call
.then(response => {
// if login is successful
// 1. retrieve the user preferences like, on the customised screens what fields user wanted to see
axios.get('user preference endpoint') // 2nd call
// 2. send a request to one more external systems, which calculates what user can see and not based on LDAP role
axios.get('role calculation endpoint') // 3rd call
})
.catch(error => {
})
Now I can see that I can use
axios.all()
for second and third call, but with promised based client, how to chain first and second call? To retrieve user preferences, I have to wait for user to be authenticated.
How to chain this calls in a promise based way, rather than callback style?
as mentioned in the thread for this Github issue, axios() and axios.all() return Promise objects which can be chained however you see fit:
axios.get('/auth')
.then(function(response) {
return axios.all([ axios.get('/preferences'), axios.get('/roles') ]);
})
.then(function(responses) {
const [
preferencesResponse,
rolesResponse
] = responses;
// do more things
})
.catch(function(error) {
console.log(error);
});
Dan O's answer is very good and it works perfectly but it's much readable using async/await although it's also working with promises under the hoood
async yourReactClassFunction(){
try{
let getAuth = await axios.get('/auth');
//if login not successful return;
let result = await Promise.all([axios.get('/preferences'), axios.get('/roles')]);
//Do whatever with the results.
}catch(e){
//TODO error handling
}
}
Although it's the same thing, 'feels' more readable in my very subjective opinion
I've searched around a lot but have been unable to find a simple way to get flash messages from Express and render them in React.
I need to access the data on my Express server, but what is the best way of storing this and passing it down to React? I was thinking of passing an object down when the React index.html file is rendered, but I'm not sure how I can access this data, or send the correct data when certain events happen, for example a user enters the wrong password.
I solved the issue.
I simply have a variable in my session called flash which is set to false by default.
In the correct part of the passport flow I redefine this to a string, depending on the error. I have a React action and reducer to get this data and if it's truthy, render it to the screen. When the component unmounts or the site is refreshed I reset it to false.
EDIT: I have found a better solution
1. In the passport middleware set an optional message if something goes wrong.
return done(null, false, { message: 'Email not found' });
2. In the login route send this information as a response.
router.post('/login', (req, res, next) => {
passport.authenticate('local-login', (e, user, info) => {
if(e) return next(e);
if(info) return res.send(info);
req.logIn(user, e => {
if(e) return next(e);
return res.send(user);
});
})(req, res, next);
});
3. Handle the submission and response in a Redux action generator. If the user authenticates, then the message property will be undefined.
const res = await axios.post('/auth/login', { email, password });
dispatch({
type: 'FLASH',
payload: res.data.message
});
4. In the reducer, the state will be either a string or false:
return action.payload || false;
5. Then it's a question of rendering the state to the screen. Another action can be sent when the component unmounts to reset the state.
Hope this helps someone else out there.
expressjs/flash will place an array of flash objects onto res.locals. Per the docs: https://github.com/expressjs/flash#reslocalsflash
res.locals.flash
An array of flash messages of the form:
{
"type": "info",
"message": "message"
}
From my understanding, anything placed on res.locals is available in the global scope. In other words, you should be able to do window.flash which should return an Array of flash objects.
So you would simply loop over the array as you would normally in JavaScript. That is just my guess.
const makeFlashElement = ({type, message}) => {
return (
<div>
<h1>message</h1>
<h2>type</h2>
</div>
)
}
for (message in flash) {
makeFlashElement(message)
// ...
}
Typically you'd return a JSON response which React can easily digest.
See Karl Taylor's comment.
I am not sure if this is a limitation to React and Meteors connection as documentation suggests that it should be possible without the extra parameter.
When I call a meteor subscription in react if I do not explicitly state the parameter in the query it returns any data, ignoring the specified data in the publish function.
Meteor.publish("supplier", function() {
if(this.userId) {
var user = Meteor.users.findOne(this.userId, { fields : { active : 1 }});
if(user.active != this.userId || user.active != undefined){
// This only returns 1 singular supplier - is correct
var supplier = Supplier.find({ _id : user.active, users : this.userId });
return supplier;
} else {
return this.ready();
}
} else {
return this.ready();
}
});
Now I call the subscription in react as so
getMeteorData: function () {
var data = {}
handle = Meteor.subscribe("supplier");
if(handle.ready()) {
data.supplier = Supplier.findOne(); // Returns Wrong supplier
//data.supplier = Supplier.findOne({_id: session.get("active")}) // Returns correct supplier
data.supplierReady = true
}
return data;
},
This returns the first supplier in the collection not the one logged in the publish function on the server! However if I explicably pass { _id : user.active} it works!
Now it was my understanding that by doing the logic on the server within the publish function that I could simply use Supplier.findOne() but this is not the case and I don't understand why. Is this a limitation on React/Meteor or am I implementing this wrong?
This isn't a React-specific issue, it's a result of the way findOne works. If you have one or more documents in your client side Supplier collection, Supplier.findOne() will just grab the first record available without reference to the document(s) you just fetched from your subscription.
This means either (a) you have more than one supplier available on the client side due to other preexisting subscriptions, or (b) you are returning more than one supplier from the handle subscription.
Check the state of the client side collection prior to the handle subscription. If there's 1 or more docs and that is the intended state of your application, then modify the client side findOne to add {_id: user.active} as you have before.