How can I access form elements from server function? - meteor

I use Mesosphere and I want to make a custom rule to validate equalsField:
Mesosphere.registerRule("equalsField", function(fieldValue, ruleValue){
//var ruleValue = $('#'+ruleValue).val();
//var ruleValue = document.getElementById(ruleValue).value;
return fieldValue === ruleValue;
});
But I can't use jquery $ or document because is not accesible on the server side (these works only on the client side)

So it looks like what you want to to is check that one field is equal to another field.
In actuality when a rule is validated in Mesosphere, the rule is passed 5 parameters: fieldValue, ruleValue, fieldName, formFieldsObject, and fields. Since formFieldsObject is an object containing the raw unvalidated data from the form, with the name of each input as the key and the current value as the key value, This means that you can create your new rule as follows..
Mesosphere.registerRule("equalsField", function(fieldValue, ruleValue, fieldName, formFieldsObject, fields){
return fieldValue === formFieldsObject[ruleValue];
});
Then when you set up your rules, pass the name of the field that the current field should be equal to and you should be good to go.

Related

Firestore rule to only add/remove one item of array

To optimize usage, I have a Firestore collection with only one document, consisting in a single field, which is an array of strings.
This is what the data looks like in the collection. Just one document with one field, which is an array:
On the client side, the app is simply retrieving the entire status document, picking one at random, and then sending the entire array back minus the one it picked
var all = await metaRef.doc("status").get();
List tokens=all['all'];
var r=new Random();
int numar=r.nextInt(tokens.length);
var ales=tokens[numar];
tokens.removeAt(numar);
metaRef.doc("status").set({"all":tokens});
Then it tries to do some stuff with the string, which may fail or succeed. If it succeeds, then no more writing to the database, but if it fails it fetches that array again, adds the string back and pushes it:
var all = await metaRef.doc("status").get();
List tokens=all['all'];
List<String> toate=(tokens.map((element) => element as String).toList());
toate.add(ales.toString());
metaRef.doc("status").set({"all":toate});
You can use the methods associated with the Set object.
Here is an example to check that only 1 item was removed:
allow update: if checkremoveonlyoneitem()
function checkremoveonlyoneitem() {
let set = resource.data.array.toSet();
let setafter = request.resource.data.array.toSet();
return set.size() == setafter.size() + 1
&& set.intersection(setafter).size() == 1;
}
Then you can check that only one item was added. And you should also add additional checks in case the array does not exist on your doc.
If you are not sure about how the app performs the task i.e., successfully or not, then I guess it is nice idea to implement this logic in the client code. You can just make a simple conditional block which deletes the field from the document if the operation succeeds, either due to offline condition or any other issue. You can find the following sample from the following document regarding how to do it. Like this, with just one write you can delete the field which the user picks without updating the whole document.
city_ref = db.collection(u'cities').document(u'BJ')
city_ref.update({
u'capital': firestore.DELETE_FIELD
})snippets.py

What variables are available for use as "wildcards" in a Firestore Rules Match Statement?

The syntax for specifying the syntax of a rule that you might want to declare in order to protect documents in a Firestore collection are specified at https://firebase.google.com/docs/reference/security/storage. But this describes the wider context of Google Cloud storage and consequently, when you're trying to use these specifically for Firestore applications, the documentation seems a bit opaque.
The match statement used to specify the subset of documents in a collection myCollection to which the rule is to be applied typically takes the form:
match myCollection/{wildcard}
My question is "what variables are available for use as wildcards?". According to the Google document referenced above "A wildcard variable is declared in a path by wrapping a variable in curly braces: {variable}. This variable is accessible within the match statement as a string."
I now understand that if myCollection contains a field called email for example, a match statement of the form
match myCollection/{email}
will allow me to specify a rule to be applied to a document whose documentId is the value specified by the email field. In this case, a rule to allow only logged-in users to read their own record in myCollection would take the form:
match myCollection/{email}
allow read : if request.auth != null && request.auth.token.email == email;
where email is the user's email address filed in myCollection
But supposing that email isn't being used as the unique key to identify a myCollection record - ie that other fields identify documents and a user may have many records tagged with their email within myCollection? This is surely the more likely case but the Google documentation doesn't seem to give any clues to how you would specify a match to cover this situation.
A previous stackoverflow question at How write Firestore rule to match email field in document has directed me towards the keyword doc. This has allowed me to specify a more general rule that can be applied to all the records in a collection with a particular email value:
match /myCollection/{doc} {
allow read : if request.auth != null &&
request.auth.token.email == resource.data.email;
}
There are two concepts here that are new to me (at least in the context of Firestore rules): first the keyword doc and second the resource.data.email construct that gives my rule access to document fields. Can anybody tell me where these constructs are documented, and also if there are any more "wrinkles" to the "wildcard" concept (I already know about the documents=** one).
match myCollection/{wildcard} - This rule tells firebase to match all documents in the myCollection collection. Essentially, it says match all documents that match this path myCollection/*
wildcard above is simply a variable that holds the document path (the asterisks path). This variable can be anything (apart from the reserved strings). If this is your path:
'myCollection/tests', then wildcard variable is holds 'tests';
'myCollection/test#email.com', then wildcard variable holds 'test#email.com'
wildcard is simply a place holder for anything that matches this path: myCollection/*. Instead of wildcard, you can use anything e.g email (myCollection/{email} and email here becomes the variable that holds the document path).
resource.data is the data being fetched. So if your data has an 'email' field, then resource.data.email always holds the email field data (regardless of what the wildcard variable is). Do not use reserved strings e.g 'resource' as the wildcard variable.
what variables are available for use as wildcards?
Any variable you put in curly braces becomes the wildcard variable. It simply holds the document path. It is not a special string.
UPDATE #MartinJ.
For autogenerated ids. Lets assume this is your doc path myCollection/1s6SUqi8M6sC6DZdTKjt, (1s6SUqi8M6sC6DZdTKjt is autogenerated)
match /myCollection/{email} {
allow read : if email == '1s6SUqi8M6sC6DZdTKjt'; // true because
// email variable holds the value '1s6SUqi8M6sC6DZdTKjt'
}
match /myCollection/{doc} {
allow read : if doc == '1s6SUqi8M6sC6DZdTKjt'; // true because
// doc variable holds the value '1s6SUqi8M6sC6DZdTKjt'
}
match /myCollection/{stackoverflow} {
allow read : if stackoverflow == '1s6SUqi8M6sC6DZdTKjt'; // true
// because stackoverflow variable holds the value '1s6SUqi8M6sC6DZdTKjt'
}
// It does not matter what we use as wildcard variable (we can even
// use MartinJ), it will always hold the value of '1s6SUqi8M6sC6DZdTKjt'
// (doc id if the path matches).
// As #Frank said, these are simply variables, not keywords. You
// can call it anything and it will have the value of doc path.
}
if this is your document path sales/2K5CwGU9ZWwNoO9efyuv/payments/40ciObsicEntBZParkon (autogenerated document ids)
match /sales/{doc=**} {
allow write: if doc == '2K5CwGU9ZWwNoO9efyuv/payments/40ciObsicEntBZParkon';
// this will be true because doc=** (**with asterisks**) matches
// everything after sales
}
match /sales/{saleId} {
match /payment/{paymentId} {
allow write: if saleId == '2K5CwGU9ZWwNoO9efyuv' && paymentId == '40ciObsicEntBZParkon';
// this will be true because saleId holds the value
// '2K5CwGU9ZWwNoO9efyuv' and paymentId holds this value '40ciObsicEntBZParkon'
}
}

change.after.val() returns full JSON object rather than value of the object

I'm working on a Firebase Cloud Function. When I log the value of change.after.val() I get a printout of a key-value pair
{ DCBPUTBPT5haNaMvMZRNEpOAWXf3: 'https://m.youtube.com/watch?v=t-7mQhSZRgM' }
rather than simply the value (the URL). Here's my code. What am I not understanding about .val() ? Shouldn't "updated" simply contain the URL?
exports.fanOutLink = functions.database.ref('/userLink').onWrite((change, context) => {
const updated = change.after.val();
console.log(updated);
return null
});
If you want only the URL value, you should include a wildcard in your trigger path for the URL key:
exports.fanOutLink = functions.database.ref('/userLink/{keyId}').onWrite((change, context) => {
console.log('keyId=', context.params.keyId);
const updated = change.after.val();
console.log(updated);
return null
});
In the Realtime Database, data is modeled as a JSON tree. The path specified in an event trigger identifies a node in the tree. The value of the node, being JSON, includes all child nodes. The change parameter for the trigger event refers to the value of the entire node.
I indicated above that you can change the trigger path to refer one level down. An alternative is to access the children of the node using the child() method of DataSnapshot.
Without knowing your use-case, it's hard to be more specific about the trigger event path you should use. Keep in mind that the event fires when any element of the node value changes, whether it be a simple value at the root level, or a value of a child node. It is often the case that you want the trigger to be as specific as possible, to better identify what changed. That's where wildcards in the path are useful. As I showed in the code I posted, the string value of a wildcard is available from the context parameter.

Update user's collection sub-object with index auto increment

In a method, I need to write in the user's collection a new token on each call.
I need also to stock every token (no replacement or udpate of a single token).
I'm using sub-objects (like multi user's emails) : how can I auto increment dynamically the index, to write each new token 'after' the last written?
For example, if there is already 6 tokens written (so Auth.0.token to Auth.5.token), the new one will be automatically Auth.6.token.
Thanks
Below my working static code.
var authtoken = Random.id([20]);
Meteor.users.update({_id: Meteor.userId()}, {$set: {"Auth.1.token": authtoken}});
Just $push the token onto the end of the array instead of trying to set then value.
Meteor.users.update({_id: Meteor.userId()}, {$push: {Auth.token: authtoken}});

Meteor filtering data when asserting fields from queries

Why does running this code
var userId = Meteor.userId();
var user = Users.findOne(userId, { fields: { earnings: 1 } });
Return
{ _id: 'Co5bMySeaqySgDP6h', earnings: { period: 0.6, total: 52.5 } }
Instead of returning all the fields on the user, including the earnings (custom field)
Also, is there a way to make user queries automatically return custom specified fields, so I dont have to manually specify it each time I need it?
Much appreciated
The reason that you only get the specified field (plus the id) is given in the docs:
To include only specific fields in the result documents, use 1 as the value. The _id field is still included in the result.
If instead you just call Meteor.users.findOne(userId) it will return all of the available fields. If this is called on the server, that will be the entire document, but if you use it on the client, it will only return the fields that have been published from the server, which by default is just the username and the emails and profile fields. Again, per the docs:
On the client, this will be the subset of the fields in the document that are published from the server (other fields won't be available on the client). By default the server publishes username, emails, and profile (writable by user). See Meteor.users for more on the fields used in user documents.
This means that if you have added a new field to you user docs, you need to explicitly publish it for it to be available on the client (assuming autopublish has been removed). Note that it's fine to do this using the previously discussed fields specifier as the other required details (username, profile) will not be overwritten by another publish function unless you try to publish the same top-level field again.
Meteor.publish('earnings', function() {
return Meteor.users.find(this.userId, { fields: { earnings: 1 } });
};
(Publish functions expect you to return a cursor rather than an array, so you need to use find rather than findOne even if there will only be one result).
Finally, it's easy to add your own methods to a collection to make finding stuff you want more concise.
Meteor.users.findSimple = function(selector, options) {
options = options || {};
options.fields = options.fields || {};
options.fields.earnings = 1;
\\ same thing for any other fields you want to limit this find to;
return this.find(selector, options);
};

Resources