Meteor Parties Example: http://www.meteor.com/examples/parties
What's the most efficient way of limiting 1 RSVP per user? I'd like to just modify the RSVP method somehow but unsure of how to go about it.
My general thought would be something like this:
if(Parties.find({rsvps:{user}}) > 1 )
Any help would be greatly appreciated!
You've basically got the right idea: add a check to the rsvp function in model.js. Here's the schema for the parties collection:
/* Each party is represented by a document in the Parties collection:
owner: user id
x, y: Number (screen coordinates in the interval [0, 1])
title, description: String
public: Boolean
invited: Array of user id's that are invited (only if !public)
rsvps: Array of objects like {user: userId, rsvp: "yes"} (or "no"/"maybe")
*/
To check if a user already has an rsvp, you'd want to add a line to that function like this:
if (Parties.find({'rsvps.user': this.userId}).count()) return;
This'll basically work. There's a minor race condition, though, where one user can call rsvp twice in quick succession, this check can pass for both updates, and then he'll be rsvp-ed twice. There are ways around that (ex: some form of two-phase commit...) but they're more involved.
Related
I'm quite new to Firebase Flutter. I'm developing a mobile application to share books among others.
In firebase firestore,
I have 'users' collections which contain all the user data with unique id
I have 'books' collection which contain all the book data with unique id created automatically
Also I have 'global' collection with single document with one integer field called 'bookcount'.
Users can can have many books.
Now I want to create a another unique id field for book. idea is to have simple integer id.
One way of doing this is get list of books and find the length (count) and add 1 when creating a new record. I have ruled out this method as if many users using simultaneously, I think this can lead to duplicate ids.
So I have created a another collection global with single document and field name bookcount. Which hold number of books (rough count) on books collection. So idea is each time when adding a book to a collection increase bookcount and use this value as simple unique id for a book. This bookcount may not represent actual books as user can discard the book entry before saving it, which is okay as I only need a simple unique id.
class DatabaseService {
...
...
//final CollectionReference bookCollection = Firestore.instance.collection('users');
//final CollectionReference bookCollection = Firestore.instance.collection('books');
final CollectionReference globalData = Firestore.instance.collection('global');
...
...
Future<String> bookId() async
{
String uniquebookid = await globalData.document('SomeHardcodedID').updateData(
{
'bookcount': FieldValue.increment(1)
}).then((voidvalue) async
{
String cid = await globalData.getDocuments().then((bookvalue) => bookvalue.documents.single.data['bookcount'].toString());
return cid;
});
return uniquebookid;
}//future bookId
...
...
}//class
Now this works. well somewhat, Can we do this better? In here there are two parts, first increment the value bookcount, and then retrieve it.
Can we do this in one go?
If I try to call this method consecutively really fast when returning a value it might skip few numbers. I have call this from a button and try to press as fast I could. I think counter increase but it return
same number few times. and then skip some when press again. for example 1,2,3,4,8,8,8,8,9,10,... So at counter 4 I try to press the button multiple times. I wonder how this will behave when multiple users adding multiple books at the same time.
How Can I fix this?
Please Help, Thanks.
I think the problem was since await globalData.document('SomeHardcodedID').updateData is not producing a return value (void), as soon as this fired next call also execute which is okay, which okay for most scenarios.
However if bookId called few times within very short period (milliseconds) this produce number before FieldValue.increment(1) process.
In my main page I have a list of users and i'd like to choose and open a channel to chat with one of them.
I am thinking if use the id is the best way and control an access of a channel like USERID1-USERID2.
But of course, user 2 can open the same channel too, so I'd like to find something more easy to control.
Please, if you want to help me, give me an example in javascript using a firebase url/array.
Thank you!
A common way to handle such 1:1 chat rooms is to generate the room URL based on the user ids. As you already mention, a problem with this is that either user can initiate the chat and in both cases they should end up in the same room.
You can solve this by ordering the user ids lexicographically in the compound key. For example with user names, instead of ids:
var user1 = "Frank"; // UID of user 1
var user2 = "Eusthace"; // UID of user 2
var roomName = 'chat_'+(user1<user2 ? user1+'_'+user2 : user2+'_'+user1);
console.log(user1+', '+user2+' => '+ roomName);
user1 = "Eusthace";
user2 = "Frank";
var roomName = 'chat_'+(user1<user2 ? user1+'_'+user2 : user2+'_'+user1);
console.log(user1+', '+user2+' => '+ roomName);
<script src="https://getfirebug.com/firebug-lite-debug.js"></script>
A common follow-up questions seems to be how to show a list of chat rooms for the current user. The above code does not address that. As is common in NoSQL databases, you need to augment your data model to allow this use-case. If you want to show a list of chat rooms for the current user, you should model your data to allow that. The easiest way to do this is to add a list of chat rooms for each user to the data model:
"userChatrooms" : {
"Frank" : {
"Eusthace_Frank": true
},
"Eusthace" : {
"Eusthace_Frank": true
}
}
If you're worried about the length of the keys, you can consider using a hash codes of the combined UIDs instead of the full UIDs.
This last JSON structure above then also helps to secure access to the room, as you can write your security rules to only allow users access for whom the room is listed under their userChatrooms node:
{
"rules": {
"chatrooms": {
"$chatroomid": {
".read": "
root.child('userChatrooms').child(auth.uid).child(chatroomid).exists()
"
}
}
}
}
In a typical database schema each Channel / ChatGroup has its own node with unique $key (created by Firebase). It shouldn't matter which user opened the channel first but once the node (& corresponding $key) is created, you can just use that as channel id.
Hashing / MD5 strategy of course is other way to do it but then you also have to store that "route" info as well as $key on the same node - which is duplication IMO (unless Im missing something).
We decided on hashing users uid's, which means you can look up any existing conversation,if you know the other persons uid.
Each conversation also stores a list of the uids for their security rules, so even if you can guess the hash, you are protected.
Hashing with js-sha256 module worked for me with directions of Frank van Puffelen and Eduard.
import SHA256 from 'crypto-js/sha256'
let agentId = 312
let userId = 567
let chatHash = SHA256('agent:' + agentId + '_user:' + userId)
I'm currently creating an app using ionic2 and firebase for a class and I've run into an issue.
I"m not posting code because I don't have anything relevant enough to the question.
In my database I have a 'group' that people can create. I need the 'group' to be able to store a list of users. The way I'm currently doing it is that users will click an addGroup button, and the currentUser will be added to the list of users in that group simply by typing in the name of the group. (yeah i know that's going to need a password or something similar later, people obviously shouldn't just be able to join by group name)
*Names of groups will all be different so I should be able to query the database with that and get a single reference point.
The problem is that I don't know how to properly query the database and get the key value of the group object I want.
I keep looking things up and cant find a simple way to get the key of an object. I feel like this is a dumb question but, I'm unfamiliar with typescript, firebase, and ionic2 so I'm pretty lost in everything.
Answer for the question in comment.
you can query the database like this
this.group_val:any="group1"
let group=db.list('/groups', {
query: {
orderByChild: GroupName, //here specify the name of field in groups
equalTo: this.group_val //here specify the value u want to search
}
});
group.subscribe(grp=> {
//you can use the grup object here
})
First, define your database structure. The below is probably best:
groups
<first group> (Firebase-generated ID)
name: "Cat lovers"
users:
user1ID: true
user2ID: true
<second group>
...
I assume that when the user clicks on the group he wants to join, you know the group ID. For instance, it could be the value of an option in a select.
Get the list of groups:
groups$ = this.angularFireDatabase.list('groups');
To populate a list of groups into a select:
<select>
<option *ngFor="let group of groups$ | async" [value]="group.$key">
{{group.name}}
</option>
</select>
To create a new group:
groups$.push({name: "Dog lovers"})
The select (drop-down) will update itself.
To add a user to a group:
addUserToGroup(groupId, userId) {
this.angularFireDatabase.list(`groups/${groupID}/users`).update(userID, true);
}
To find a group based on its name:
// Define the shape of a group for better type checking.
interface Group { name: string; users: {[userId: string]: boolean;}; }
// Keep local list of groups.
groups$: FirebaseListObservable<Group>;
groups: Group[];
// Keep an updated local copy of groups.
ngOnInit() {
this.angularFireDatabase.list('groups').subscribe(groups => this.groups = groups);
}
function findGroupByName(name) {
return this.groups.find(group => group.name === name);
}
In my main page I have a list of users and i'd like to choose and open a channel to chat with one of them.
I am thinking if use the id is the best way and control an access of a channel like USERID1-USERID2.
But of course, user 2 can open the same channel too, so I'd like to find something more easy to control.
Please, if you want to help me, give me an example in javascript using a firebase url/array.
Thank you!
A common way to handle such 1:1 chat rooms is to generate the room URL based on the user ids. As you already mention, a problem with this is that either user can initiate the chat and in both cases they should end up in the same room.
You can solve this by ordering the user ids lexicographically in the compound key. For example with user names, instead of ids:
var user1 = "Frank"; // UID of user 1
var user2 = "Eusthace"; // UID of user 2
var roomName = 'chat_'+(user1<user2 ? user1+'_'+user2 : user2+'_'+user1);
console.log(user1+', '+user2+' => '+ roomName);
user1 = "Eusthace";
user2 = "Frank";
var roomName = 'chat_'+(user1<user2 ? user1+'_'+user2 : user2+'_'+user1);
console.log(user1+', '+user2+' => '+ roomName);
<script src="https://getfirebug.com/firebug-lite-debug.js"></script>
A common follow-up questions seems to be how to show a list of chat rooms for the current user. The above code does not address that. As is common in NoSQL databases, you need to augment your data model to allow this use-case. If you want to show a list of chat rooms for the current user, you should model your data to allow that. The easiest way to do this is to add a list of chat rooms for each user to the data model:
"userChatrooms" : {
"Frank" : {
"Eusthace_Frank": true
},
"Eusthace" : {
"Eusthace_Frank": true
}
}
If you're worried about the length of the keys, you can consider using a hash codes of the combined UIDs instead of the full UIDs.
This last JSON structure above then also helps to secure access to the room, as you can write your security rules to only allow users access for whom the room is listed under their userChatrooms node:
{
"rules": {
"chatrooms": {
"$chatroomid": {
".read": "
root.child('userChatrooms').child(auth.uid).child(chatroomid).exists()
"
}
}
}
}
In a typical database schema each Channel / ChatGroup has its own node with unique $key (created by Firebase). It shouldn't matter which user opened the channel first but once the node (& corresponding $key) is created, you can just use that as channel id.
Hashing / MD5 strategy of course is other way to do it but then you also have to store that "route" info as well as $key on the same node - which is duplication IMO (unless Im missing something).
We decided on hashing users uid's, which means you can look up any existing conversation,if you know the other persons uid.
Each conversation also stores a list of the uids for their security rules, so even if you can guess the hash, you are protected.
Hashing with js-sha256 module worked for me with directions of Frank van Puffelen and Eduard.
import SHA256 from 'crypto-js/sha256'
let agentId = 312
let userId = 567
let chatHash = SHA256('agent:' + agentId + '_user:' + userId)
I have a meteor collection like this:
Cases = new Meteor.Collection('cases');
As well i have registered users (max 10). I now want to be able to "give" a single case to a registered user and be sure, that no other user is getting that specific case.
The User is working with the case (updating fields, deleting fields) and then sends it in some kind of archive after submitting the user should get a new case that is in the collection.
My thought was to have field called "locked" which initially is set to false and in the moment it is displayed at the user "locked" gets true and is not returned anymore:
return Cases.find({locked: false, done: false}, {limit: 1});
Any ideas how to do that in meteor?
Thanks
You just need to attach an owner field (or similar) to the case. That would allow you to do things like:
Only publish the case to the user who is also the owner using something like:
Meteor.publish('cases/unassigned', function() {
return Cases.find({owner: {$exists: false}});
});
Meteor.publish('cases/mine', function() {
return Cases.find({owner: this.userId});
});
Not allow a user to update or delete a case if it's not assigned to them:
Cases.allow({
update: function(userId, fieldNames, doc, modifier) {
return userId === doc.owner;
},
delete: function(userId, doc) {
return userId === doc.owner;
}
});
Obviously, these would need amending for stuff like super-users and you probably need some methods defined to allow users to take cases, but that's the general idea.
There are concurrency issues to deal with, to reliably allocate a case to only one person.
We need to solve two things:
1. Reliably assign the case to a user
2. Fetch the cases assigned to a user
Number 2. is easy, but depends on 1.
To solve 1., this should work:
var updated = Cases.update(
{_id: <case-to-assign>, version: "ab92c91"},
{assignedTo: Meteor.userId(), version: Meteor.Collection.ObjectID()._str});
if (updated) {
// Successfully assigned
} else {
// Failed to assign, probably because the record was changed first
}
Using this you can query for all of a users cases:
var cases = Cases.find({assignedTo: Meteor.userId()});
If 10 people try get a case at the same time, it should have a pre-set version field, and the MongoDB will only let the .update work once. As soon as the version field changes (due to an .update succeeding) the remaining updates will fail as the version field could no longer match.
Now that the allocation has taken place reliably, fetching is very simple.
As suggested by #Kyll, the filtering of cases should be done inside a Meteor publication.
It would also make sense to perform the case-assignment inside a Meteor method.
UPDATE:
#richsilv's solution is simpler than this one, and works fine.
This solution is useful if you need to know who won immediately, without making further requests to the server.