today I finished the functional part of my website, so I went into the secure-my-app part of development. I want to give to users only the content they are related to, so, to my teachers ( = a user with user.role == "teacher"), I only want to give them access to a given assignment if their _id is in the assignment.teachersList array of _ids. I want to make this verification in the publish so i MUST get a cursor in the end of the query.
After looking at the OFFICIAL documentation of mongo , it seems like doing what I want should be as simple as :
// in a publish
Assignements.find({ teachersList: this.userId });
However, this always returns me false. First, being afraid of a this context problem, I tried something like :
// in a publish
let self = this;
Assignements.find({ teachersList: self.userId });
and it's not better, I still get nothing. I tried to use Cursor.map() and put my condition there, but as map doesnt return a cursor, I get the data but it's not working either since we are in a publish.
It's written in the doc that the first try I made should work, so no doubts I'm making something wrong, but what .. ?
Right now i'm starting to wonder if the problem comes from the fact that it's an array of _ids. Right now, it's only an array of Strings. And it seems that this.userId in a method only returns a String.... But maybe i'm wrong and I should use Meteor.Mongo.ObjectId(the_string_id) objects instead?
Alright that's it ! Please, if you have any idea why such an easy query doesnt work, tell me ! Thanks :)
teachersList is an array, and not an id. You are trying to find an document with an id that has the value of an array... not going to work...
I usually use this pattern on all private owner collections: add an onInsert event to my collection and then add the user as ownerId to the document being inserted. Then, when it comes to publish, I publish all documents with the ownerId of the user. Clean and simple, no roles even needed...
If, however you decide not to use the pattern above...
From MongoDB:
db.inventory.find( { tags: { $in: [ /^be/, /^st/ ] } } )
This query selects all documents in the inventory collection where the tags field holds an array that contains at least one element that starts with either be or st.
In Meteor your query might look something like:
Assignments.find({ownerId: { $in: teachersList}});
Related
I have a Firebase database with a list of entries. Now, on click, I want to copy a certain document from this database to a user database (create new document in user DB) while at the same time updating one of the fields.
I figured out how to copy the document, but I have problems with updating the field. I think I'm simply using a wrong syntax, but couldn't find this example in Firebase documentation.
This is how the original document looks like: (https://i.stack.imgur.com/ux18x.png)]
And this is how it's copied into the user database: (https://i.stack.imgur.com/QsBVy.png)]
I use this code to make the copy:
setDoc(doc(userSentencesRef, newSentenceIDText), ...newSentence)
And I tried many ways to update the "Level" field, but none of them works:
setDoc(doc(userSentencesRef, newSentenceIDText), ...newSentence, {data: {LEVEL: "exclude"}})
setDoc(doc(userSentencesRef, newSentenceIDText), ...newSentence, {LEVEL: "exclude"})
setDoc(doc(userSentencesRef, newSentenceIDText), {...newSentence, LEVEL: "exclude"})
(the first two do nothing, the last one adds a separate level field).
I also wouldn't want to list every single field that needs to be added { EN: data.EN, DE: data.DE }, etc, because I want to make sure it works even if the other fields change in future.
I will really appreciate your help.
Specifically I feel like I am looking for the answer to the question asked here, but it turns out the title of the question isn't a perfect match for the actual question.
What I am looking to do involves Relay, React, and GraphQL (I suppose I could have stated Relay, and you could have figured out the other two).
What I want to do, and what I can't seem to find an answer to, is to create a query that lists a variable list of query fragments based on some predefined JSON array.
If you look at my home page, jimmyvanveen.com you will see I have a list of projects I have worked on (or am working on), and each renders as a React component. I am pulling this data from Github through their REST API (v3) - but now I want to migrate to GraphQL (v4).
I can make an array containing the repo name, and owner as required by the Schema of Github's API, but I don't know how to dynamically create a query based off a simple array such as:
repos: [
{"name": "repo name", "owner": "Repo Owner"},
{"name": "other repo", "ownder": "Other Owner"}
]
I know how to make a query that can find all of this information manually, but I was hoping there was a GraphQL way (perhaps by sending vars?) to run through the entire array in one query and return the array of repo data.
I've been racking my brain for days trying to find the answer here, or anywhere, and I'm striking out.
Thanks in advance for any help you can provide!
So, in the end I figured out I am in general just submitting the wrong information to Github in order to pull Repo info from a curated list.
My objective above is still not possible exactly the way I wanted to solve the problem, however the method mentioned above is very similar to a usable method that DOES work. Simply put, supply Github with an array of unique node ID's, rather than Repo Name and Repo Owner, as I had been trying to do.
This way, you are not trying to use a 2D array, and instead you are using a single array, with an equal amount of specificity as the 2D array.
So formulating an array such as:
const repos = [
'uniqueID1',
'uniqueID2',
'uniqueID3',
...
];
...can then be passed through Relay (I had the QueryRenderer set to run on my App component) like so:
query AppQuery($repos:[ID!]!) {
projects: nodes(ids:$repos){
...projects_projects
}
}
You can obtain the node ID's in multiple ways, but the way I did this was by pulling the repo info one by one in GraphiQL with a query such as this:
query {
repository(owner:"JimmayVV", name:"JimmyVanVeen.com") {
id
}
}
I then consolidated all of these ID's into an array, and passed that through to Relay and got the desired results.
I'm sure there are other ways, perhaps using a simple Node script to formulate the array for you, but this was simple enough for me, that I am happy with it.
The system itself is pretty easy to understand but tricky to implement. Moreover, security reasons made me think how to do that.
I was thinking to make the function work in frontend firebase scripts simply doing everything there as checking if there is already like/dislike posted by this user and remove/add/switch if user clicked. The problem lies in security of this method: can't user create a new function which won't check if the like was posted?
And if that's possible, how should this system work? Right now my logic:
Clicked like:
locally activate/deactivate like button and remove dislike active class if on
check docs for this user/doc like
`1`? -> remove this doc from collection
`0`? -> switch to `1`, because `0` is dislike
`undefined`? -> create doc with `vote: 1`
change (+1/-1 or +2/-2) the value of post votes fields
Same for dislike. But this sounds really complicated as for such a small feature. Maybe it is possible to wave additional collection with user/votes without losing that level of security? Or using http-triggers may help with this somehow? This feature would be much easier on some PHP-like languages, so I am freaking out right now.
Here are my assumptions.
You have a post with a unique id, let's call it post_id
You have a user with a unique id, let's call it user_id
There are 3 valid states: (Undefined), (Like), (Dislike)
Basic process follows
To store the likes/dislikes, you create a collection called feelings which uses post_id+':'+user_id as it's document id (this makes it easy to look up).
Documents in feelings have a single field called state that stores -1 for dislike, 1 for like.
As you mention, you can simply set or overwrite this value to whatever the user desires. If they decide to remove their 'feeling' and neither like or dislike, issue a delete command (this is cheaper than doing a write to set state to 0).
Use Cloud Functions to listen to the feelings collection and update the post documents like/dislike count based on how this state changes (or gets created/deleted).
Security Rules can enforce only allow states of -1 and 1, and if you're using Firebase Auth, you can trivially enforce only allowing the user matching user_id from being able to change state.
What have you got now?
You now have a system with the following properties:
Users can like and dislike posts
Users can remove and/or change their like/dislikes
Users can only like or dislike a post once - they cannot do it multiple times
Only valid states (like, dislike) can be written to the database
Only the user can update their likes/dislikes
Scalable: Whether it's 10 posts, or millions of posts, this system will work
Bonus credit
Using the same Cloud Functions event you register to update counts, you can also use this to add or remove from a list of user ids in both a like and dislike array. This would allow you to list out users who liked or disliked a post without have to query for each individual document in the feelings collection
Also remember that Cloud Functions has is a small chance it gets triggered more than once for a single event. If you want the counts to be guaranteed to be accurate, either make the code idempotent, or just have a manually triggered 'recount' process you can trigger if you or a user detects the counts seem off by one.
If somebody's wondering for Realtime Database!
var ref = firebase.database().ref(selectchapter + questionId + user_id);
ref.once("value", function (snapshot) {
var exists = snapshot.val() !== null;
if (!exists) {
firebase
.database()
.ref(
selectchapter +
questionId + user_id
)
.set({
status: 1
}).then(function () {
firebase.database().ref(q +
questionId + "likes").transaction(function (likes) {
return (likes || 0) + 1
});
});
}
});
I have a collection, and the collection.models returns an array of models. However, when I call collection.get(someId) (and this id is the id of the model that is in the collection.models array), I get undefined. Looking at collection._byId, it looks like an empty object.
How do I properly populate _byId, so that I can use get? Or perhaps I'm doing something wrong when initializing my collection, which is why _byId is empty.
I'm a little late, but hopefully this is still useful to some other people.
Collection._byId is just a normal js hash object. There's really nothing fancy about it. If you want Collection.get to work, just add all the models into the _byId hash.
Inside the collection's scope:
var someId = '123'; // any id will do
this._byId[someId] = someModel; // someModel.id = '123'
console.log(!!this.get(someId)); // should return true
Since I'm using this with Rails, the default json generated by Rails doesn't work well with Backbone. I don't know why I didn't see it while trying to learn Backbone. Anyway, you could either:
Change the way Rails generates its JSON
Change the way your Backbone app reads the JSON.
Sounds like the OP had a slightly different problem, but I experienced a similar issue and thought I'd post what worked for me.
Like the original issue, collection.models contained the right model, but in my case, the _byId hash contained a cid version of the model that wasn't empty. Nevertheless, _byId didn't contain a model with normal id (there's usually two version - an id one and a cid one), so I wasn't able to use collection.get(id) to retrieve it. My problem became a bit clearer when I read up about cid. From the docs:
Client ids are handy when the model has not yet been saved to the server, and does not yet have its eventual true id, but already needs to be visible in the UI.
I didn't think it was a problem with waiting for the server as my cid model and the collection.model had the correct ids. However passing in { wait : true } as an option in collection.create fixed this issue for me.
I need a CouchDB view where I can get back all the documents that don't have an arbitrary field. This is easy to do if you know in advance what fields a document might not have. For example, this lets you send view/my_view/?key="foo" to easily retrieve docs without the "foo" field:
function (doc) {
var fields = [ "foo", "bar", "etc" ];
for (var idx in fields) {
if (!doc.hasOwnProperty(fields[idx])) {
emit(fields[idx], 1);
}
}
}
However, you're limited to asking about the three fields set in the view; something like view/my_view/?key="baz" won't get you anything, even if you have many docs missing that field. I need a view where it will--where I don't need to specify possible missing fields in advance. Any thoughts?
This technique is called the Thai massage. Use it to efficiently find documents not in a view if (and only if) the view is keyed on the document id.
function(doc) {
// _view/fields map, showing all fields of all docs
// In principle you could emit e.g. "foo.bar.baz"
// for nested objects. Obviously I do not.
for (var field in doc)
emit(field, doc._id);
}
function(keys, vals, is_rerun) {
// _view/fields reduce; could also be the string "_count"
return re ? sum(vals) : vals.length;
}
To find documents not having that field,
GET /db/_all_docs and remember all the ids
GET /db/_design/ex/_view/fields?reduce=false&key="some_field"
Compare the ids from _all_docs vs the ids from the query.
The ids in _all_docs but not in the view are those missing that field.
It sounds bad to keep the ids in memory, but you don't have to! You can use a merge sort strategy, iterating through both queries simultaneously. You start with the first id of the has list (from the view) and the first id of the full list (from _all_docs).
If full < has, it is missing the field, redo with the next full element
If full = has, it has the field, redo with the next full element
If full > has, redo with the next has element
Depending on your language, that might be difficult. But it is pretty easy in Javascript, for example, or other event-driven programming frameworks.
Without knowing the possible fields in advance, the answer is easy. You must create a new view to find the missing fields. The view will scan every document, one-by-one.
To avoid disturbing your existing views and design documents, you can use a brand new design document. That way, searching for the missing fields will not impact existing views you may be already using.