Loopback conditional data validation - javascript

How can I define some validation rules depending on some conditions in request body.
For example, I want to validate that post description field is set only if the post is published (isPublished flag equals true), something like:
module.exports = function(Post) {
if(req.body.isPublished === true) {
Post.validatesPresenceOf('description');
}
}

Simply, you can use options parameter
Post.validatesPresenceOf('description', {if: 'isPublished'});
Ref: #validatable-validatespresenceof

May be you are looking for something like this
Post.observe('before save',(ctx,next)=>{
//if post is created
if(ctx.isNewInstance) {
if(ctx.instance.isPublished)
Post.validatesPresenceOf('description');
}
//if post is updated
else{
if(ctx.data.isPublished)
Post.validatesPresenceOf('description');
}
return next();
})

Related

How to upsert MongoCollection but keep some values?

Hi,
I don't know how to put a good title for it but I'm sure you will understand
So I'm trying to set 2 fields on my Mongo collection when the user click on a button BUT every 60sc I have a method that is running and who make that:
upsertCollectionMachines = Meteor.bindEnvironment(function() {
machinesCount = 0;
var protocol;
var port;
Machine.list({
inspect: true
}, Meteor.bindEnvironment(function(err, machines) {
if (typeof machines === "undefined") {
console.error("Impossible to access 'machines' from 'Machine.list: '" + err);
} else {
machines.forEach(Meteor.bindEnvironment(function(machineInfo) {
machinesCount += 1;
if (machineInfo.url != null) {
var urlToArray = machineInfo.url.split(":")
port = urlToArray[urlToArray.length - 1];
protocol = "https";
} else {
port = "2376";
protocol = "https";
}
InfosMachines.upsert({
nameMachine: machineInfo.name,
}, {
nameMachine: machineInfo.name,
stateMachine: machineInfo.state,
ipAddr: machineInfo.driver.ipAddress,
port: port,
protocol: protocol
//here I want to add isUsed: false,
//whoUseIt: "", but I think if I set isUsed true --> after 60s it will be false again right ?
});
}));
if (apiKeyGenerated == false) {
getTheAPIKeyGrafana(function(err, res) {
if (!err) {
apiKeyGenerated = true;
updateDashboard();
} else {
console.error(err);
}
});
}else{
updateDashboard();
}
}
}));
})
Where the important part is the InfosMachines.upsert(). Now I want to set 2 fields like this :
'lockTheMachine': function(nameMachine, nameUser){
InfosMachines.upsert({
nameMachine: nameMachine,
}, {
nameMachine: nameMachine,
isUsed: true,
whoUseIt: nameUser
});
//upsertCollectionMachines();
console.log(nameUser +" has locked the machine: " + nameMachine);
},
But first it doesn't look likes it add the 2fields to the collection and secondly the 60s interval method will delete them right ?
Thank you for the help
It was a little hard to follow exactly what you are trying to do in your question, but I think I got the main gist of it.
Basically, you want the lockTheMachine() function upsert to only ever set nameMachine, isUsed, and whoUseIt fields and you only want upsertCollectionMachines() to set stateMachine, ipAddr, port, protocol fields on update and only set isUsed and whoUseIt to default values on insert?
You should be able to do that using the below upserts.
Use this upsert inside your upsertCollectionMachines() function.
InfosMachines.upsert({
nameMachine: machineInfo.name,
}, {
$set: {
stateMachine: machineInfo.state,
ipAddr: machineInfo.driver.ipAddress,
port: port,
protocol: protocol
},
$setOnInsert: {
isUsed: false,
whoUseIt: "",
}
});
It will always set stateMachine, ipAddr, port, protocol fields regardless if it is inserting or updated, but will only modify isUsed and whoUseIt if it is inserting (therefore, it will not overwrite the values set from the other function).
Use this upsert inside your lockTheMachine() function.
InfosMachines.upsert({
nameMachine: nameMachine,
}, {
$set: {
isUsed: true,
whoUseIt: nameUser
}
});
It will only ever insert or update those 2 specific fields (isUsed and whoUseIt).
Now, why does this work? First, we are using update operator expressions (instead of only field:value expressions) which allow you to only update specific fields when needed and using $setOnInsert ensures that we only ever modify those fields on insert only. Note, that if the upsert determines it needs to insert a new document, per the mongodb upsert option behavior, it creates a new document using...
The fields and values of both the <query> and <update> parameters if the <update> parameter contains update operator expressions
This means you don't have to ever actually $set the nameMachine field explicitly in the update expression.

Make old links backwards compatible in express

You have an application created in express and angular that allows the user to perform a search. The URL is built based upon the search that was just performed. So if you perform a search on “Will” the url looks like http://localhost.com:9000/search/query?q=Will Everything works fine but you forgot that the app previously performed searches without the /query?= and now all of your old links like http://localhost.com:9000/search/will or http://localhost.com:9000/search/roberto no longer work.
What would be the correct approach to get the old links working again?
Should you use JavaScript on the frontend to look for /query?= missing in the URL and add after the search path but before the queried text?
It'd be easier to do a redirect on the Express back-end.
Say your code for the /search/query path is initially like this :
app.get("/search/query", function (req, res) {
// Do your query validation and fetch your search result.
// Here, I just check if a query value was given or not for the q param.
// I recommend you use better ways to check for empty queries.
// (ex: lodash's `isEmpty()` function)
if (req.query.q) {
// Serve the page !
res.send("What you want to render if the search result finds something.");
}
else {
// Return an error !
res.status(404).send("Nothing was found with the criterias entered.");
}
});
This is probably similar to what you have. Now, here is the answer to your question, based on the initial implementation above :
app.get("/search/query", function (req, res, next) {
// Check if a query value was given AND if the value isn't equal to "query".
// The later condition is to prevent infinite loops.
if (req.query.q && req.query.q !== "query") {
// Redirect using the value assigned to the q query param.
res.redirect("/search/" + req.query.q);
}
else {
// Since there is no query parameter named `q` in the request,
// we can be sure that `query` reffers to a search term.
next();
}
});
app.param("srchterm", function (req, res, next, value) {
// Check, for example, if the value isn't empty.
if (value) {
// Do your query validation and fetch your search result HERE.
// Add those results in an array in the res.locals object.
// Those results can be used later.
res.locals.results = ["all", "your", "search", "results"];
}
next();
});
app.get("/search/:srchterm", function (req, res) {
console.log("another blah");
// We don't need to fetch the data here anymore, since it's handled by the param parser above!
// However, it's still necessary to check if the search gave back some results.
if (res.locals.results) {
// Serve the results !
res.send("A total of " + res.locals.results.length + " results were found for " + req.params['srchterm']);
}
else {
// Return an error !
res.status(404).send("Nothing was found with the criterias entered.");
}
});
So from now on, every query using /search/query?q=123 will redirect towards /search/123. It even lets you use query as the search term!
Just use a regex and redirect
app.use(function(req, res, next) {
var searchRegEx = /\/search/g;
var searchedTerm = req.originalUrl.replace(searchRegEx, '');
var queryPath = req.originalUrl.match(/\/query[?]q=/);
if(!queryPath) {
var regexSlash = /\//g;
res.redirect('query?q=' + searchedTerm.replace(regexSlash, ''));
}
else {
next();
}
});

Proper way to findOne document in Template Event?

I am trying to findOne document in my Template.admin.events code. I have a form and onClick I want to verify if the ID of the ObjectID entered is an existing document in my collection and fetch that result to show it on the template.
My event code on the client :
Template.admin.events({
'click #btnAjouterObjet'(event) {
let objetIdInput = $('#object_id').val().toString();
Meteor.subscribe('objetsFindOne', objetIdInput, {
onReady: function () {
let obj = Objets.findOne();
if (obj) {
console.log("found");
console.log(obj);
objetsArr.push(objetIdInput);
}
else {
console.log("not found");
console.log(obj);
}
}
});
}
});
In my Objets api :
Meteor.publish('objetsFindOne', function objetsFindOne(param_id){
return Objets.find({_id : param_id});
})
I have verified and my objetIdInput always change on click when a different Id is entered but the subscribe always returns the first id entered. I also added the onReady because otherwise it returned undefined.
I am new to Meteor and I have also tried to subscribe to all the collection and doing the find on the client but I don't think it is the best idea as my collection has about 22000 documents.
Just to elaborate a little bit on the first answer, as to how to change this pattern:
(1) you should place your Meteor.subscribe() call in your Template.admin.onCreated() function.
(2) the subscription reads from a reactive value, for example, new ReactiveVar().
(3) now, anytime the reactive value changes, the subscription will re-run. So, in your template event, you set the reactive value to the id and let the subscription handle the rest.
Discover Meteor and other resources should be helpful on any details.
You are going about this all wrong. I suggest you take a look at Template-Level Subscriptions
I opted for the use of a method :
Client side:
'click #btnAjouterObjet'(event) {
let objetIdInput = $('#object_id').val().toString();
let result = Meteor.call('findObj', objetIdInput, function (error, result) {
if (error) {
console.log(error.reason);
return;
}
console.log(result);
});
}
On the server side :
Meteor.methods({
findObj: function (param_id) {
console.log(Objets.find({ _id: param_id }).fetch());
return Objets.find({ _id: param_id }).fetch();
},
});

Parse Pointer Permissions don't allow create

I've followed every step of this walkthrough, but when I try to create a new row, I get a 403:
code: 119
message: "This user is not allowed to perform the create
operation on Messages. You can change this setting in the Data Browser."
My code:
Messages = Parse.Object.extend("Messages")
var message = new Messages();
message.set("sender", Parse.User.current());
message.set("receiver", *anotherUser*);
message.set("subject", "foo")
message.set("body", "bar")
message.save()
.then(
function(message){
console.log("success!")
},function(error){
console.log("error: ", error);
});
My CLPs are set as follows:
It looks like someone else posted the same issue in a google group. What are we missing?
I've submitted this as a bug to Parse (Facebook), and they replied:
We have managed to reproduce this issue and it appears to be a valid bug. We are assigning this to the appropriate team.
I will update this answer once the issue has been resolved. If this issue is impacting you, please subscribe to the bug, as this will help prioritize the fix.
UPDATE
Facebook replied:
Turns out that this is actually by design. To create an object, the class should have public create permissions on it
Unfortunately, with this solution, I can create a message "from" any other user (another user set as the sender). This is unacceptable and unusable IMHO.
That has been a bug since the launch of Pointer Permissions, which effectively makes them useless. My impression is they built this with the idea of letting developers secure existing schemas in one go, but of course you need it to work for future creation.
One workaround would involve combining the older Class Level Permissions and per-row ACL's while being careful to not disable your Data Browser. Let's assume you have classes "Puppy" and "Cat" and both have a field called "owner".
In your Data Browser, for each class where it makes sense to have an owner field, you set its Class Level Permissions for Puppy and Cat each to:
Public - Read: Yes or No, depends on your use case, Write: Yes
Add a Pointer Permission for "owner" - Read: Yes, Write: Yes (can skip this for now, see below)
Then in your cloud/main.js, you can use the following as a starting point (which I often call "types" below, sorry).
When Parse fixes the creation issue, you remove the Public Write Class Level permission (above), leave the Pointer Permission one, and get rid of the workaround code below.
--
var validateAndUpdateOwnerWritePerms = function(request){
var object = request.object;
var error = null;
var owner = object.get('owner');
if (!Parse.User.current()) {
error = 'User session required to create or modify object.';
} else if (!owner) {
error = 'Owner expected, but not found.';
} else if (owner && owner.id != Parse.User.current().id && !object.existed()) {
error = 'User session must match the owner field in the new object.';
}
if (request.master) {
error = null;
}
if (error) {
return error;
}
if (object.existed()) {
return null;
}
var acl = new Parse.ACL();
acl.setReadAccess(owner, true);
acl.setWriteAccess(owner, true);
object.setACL(acl);
return null;
}
// Wrapper that makes beforeSave, beforeDelete, etc. respect master-key calls.
// If you use one of those hooks directly, your tests or admin
// console may not work.
var adminWriteHook = function(cloudHook, dataType, callback) {
cloudHook(dataType, function(request, response) {
if (request.master) {
Parse.Cloud.useMasterKey();
} else {
var noUserAllowed = false;
if (cloudHook == Parse.Cloud.beforeSave &&
(dataType == Parse.Installation || dataType == Parse.User)) {
noUserAllowed = true;
}
if (!noUserAllowed && !Parse.User.current()) {
response.error('Neither user session, nor master key was found.');
return null;
}
}
return callback(request, response);
});
};
// Set hooks for permission checks to run on delete and save.
var beforeOwnedTypeWriteHook = function(type) {
var callback = function (request, response) {
var error = validateAndUpdateOwnerWritePerms(request);
if (error) {
response.error(error);
return;
}
response.success();
};
return adminWriteHook(Parse.Cloud.beforeSave, type, callback);
return adminWriteHook(Parse.Cloud.beforeDelete, type, callback);
};
beforeOwnedTypeWriteHook('Puppy');
beforeOwnedTypeWriteHook('Cat');
Unfortunately it seems that Parse Pointer Permissions do not work as you expect it on Create. The quick fix would be to allow Create permission to Public. Then to ensure that the user who is creating a record is the same as the sender. So you need to perform a manual check in the beforeSave trigger for Messages class in cloud code and if that check fails, reject the record being created.

How to validate data with usage logic in Azure mobile service

My problem is how do I validate data. I don't know JS, so I tried do in this way:
function insert(item, user, request) {
if(typeof item.NamePlayer!=='empty') // in app default value is 'empty'
{
request.execute();
}
}
Does JS have a contain method on a table? For example I want a response to table 'NamePlayer' and not add an item with the same value.
Your condition will always be true. The operator typeof will return one of the following values: "number," "string," "boolean," "object," "function," and "undefined." - so it will never be "empty". If you want to check whether the item.NamePlayer is not empty, you can use the condition below:
if (item.NamePlayer !== '') {
// ...
}
You can also simplify the condition, which will also catch the case where the client didn't send a NamePlayer value in the input:
if (item.NamePlayer) {
// ...
}
One more thing: your script will only dealing with the "positive" case; it also needs to send a response in case the condition fails. Something like the code below:
function insert(item, user, request) {
if (item.NamePlayer) {
request.execute();
} else {
request.respond(400, 'NamePlayer is required');
}
}

Categories

Resources