Structuring Firebase database like Snapchat - swift

I've been working on a snapchat clone as a way to learn how to use firebase.
I am currently stuck on how to best structure my data so that I could mimic a simple version of Snapchat.
What I'd like to do in my simplified version:
Send a picture message to multiple users
Post the picture message to a "story" feed where all my friends can see it.
I don't need anything more really. I'm not trying to implement snapchats current feature of being able to send text messages or anything like that. I just want to send pictures to friends and also post them to a public feed.
I've structured my data like this:
And Breaking it down:
Users:
Friendships between users structured like this:
Individual messages structured like this:
An index for conversations like this:
Now I've seen plenty of posts on stack and online for structuring messages in chat applications. What I'm stuck on is how to structure my DB so that a message can be sent to a user so that only that users receiving users see it.
I've been reading the firebase docs and I know I should be denormalizing data so that I can read data more efficiently, but I can't really wrap my head around the best way to do so with Firebase.
In my current implementation of user-messages, I would have to implement a check in my code to see if the message was sent by my user, and then prevent the user from seeing it. Ideally only the person who the message was sent to should see the image. (just like snapchat)
Any suggestions on how to do so?
Do I need to have some reference to the chat/message in the user tree?

The question appears to be
how to structure my DB so that a message can be sent to a user so that
only that users receiving users see it
There are many 'directions' but here are two.
1) Manually 'tell' each user about a pic
users
uid_0
pics_for_me
pic_0
url: "http://...."
pic_1
url: "http://...."
my_users
uid_2: true
uid_1
pics_for_me
my_users
uid_0: true
uid_2: true
With this structure, uid_1 has two users they want to share pics with (uid_0 and uid_2) so when it comes time to share the pic, read the my_users node, iterate over it, and store a link to the pic in that users respective pics_for_me node. Obviously you could store other data such as who it's from etc. When uid_0 logs in, read the pic_for_me node and view the pics, perhaps deleting it when done.
2) Observe a node for pics for a user
users
uid_0
name: "Henry"
uid_1
name: "Joe"
all_pics
pic_0
for_user: "uid_0"
url: "http://..."
in this scenario, uid_0 logs on and adds a query to all_pics for any for_user that contains their user id. This would work for a 1-1 situation where the pic is meant for one user. If you want multiple users to get a pic then...
all_pics
pic_0
uid_0: true
uid_1: true
and when a user logs in add a query to all_pics where that users uid is true, and then any time a picture is added that has a child of their uid, they will receive an event.

Related

Firebase functions solution for building a secure Chat App with Seen functionality

I am building a chat app using Flutter and Firebase.
The way it works in Firebase Firestore is really simple, I just have a conversationId which represents the documents where there are the messages, all of the users have a list of their conversations, when they tap on a conversation, a new screen pops up where they see the messages based on data from the stream of the "Messages" collection that is under the conversationId document. Basically that's the structure
col: Chats
doc: conversationId
col: Messages
message documents...
And this is how I get the messages inside the Chat Screen.
_firestore
.collection("Chats")
.doc(_messageCollectionId)
.collection("Messages")
.orderBy("sentOn", descending: true)
.snapshots()
.map(...)
The message document is basically the message text and when it was sent and who sent it.
I want to create the "seen" functionality, inherently I want the user to see what conversation he read or not (which ones have new messages, like instagram chat or discord)
I can not come up with a good solution to this, my main 2 problems are:
If I were to call a cloud function which fetches the messages and somehow marks them as being read, that would break my app, as I need a continous stream of live message data for the chat to feel good, I can not stream data from the cloud function.
I would like to create a system which is not write intensive. If I would have to mark each message document in particular with some "seenOn :timestamp" value, that would mean that if the user is reading 200 new messages, there are 200 new writes on each document, which seems too much to me, there should be another way.
I am asking for guidance on how I should go about the architecture of such a chat using Firebase. Maybe my chat model is not really fit for what I need, how should I tweak it?
Another problem is that I do not know how the "seen" signal should be sent. If I manually write to a document and change the boolean value of some "isRead" field from my client, the client could easily skip that line of code and break my whole seen system, they could read messages without sending the seen signal just with a break point. This is quite exploitable, there is no cloud function trigger on documents "onRead" that could help me move that logic outside the client, so what is the solution to make this also secure?
so if you want to create the seen function you could made the database structure look like this first
you should create 2 collection for the db, the users collection would only save user data and in the chats collection inside of the uid is saving the chat room id that would be look like this
that was the collection inside of users. only put the roomId of connection that been made when user trying to send a new message to other user. put the the field exactly look like that. after that you could create a chatroom collection that look like this
to be sure that random uid inside of chats collection is a room id that you should register in your users/doc/chats/ collection. the field inside of the roomId would be a connection between of the 2 user for accesing the message that've been send to db. and inside of the chat collection you would send message data in this format
and after you put that you could retrieve the chat data using stream function that would look like this
Stream<QuerySnapshot<Map<String, dynamic>>> streamChats(String chatId) {
CollectionReference chats = firestore.collection("chats");
return chats.doc(chatId).collection("chat").orderBy("time").snapshots();
}
each time of user sending message you could put the total of message that've been send to other user in the total_unread field and update it when other of user open the chat roomId. and tada your seen could work properly
oh and you can create a function that check the total_unread is 0 already and you can put the seen/check icon beside of your user message bubble.

can i upload posts with the same auto id? swift 4 firebase

Is it possible to upload a post to firebase to 3 different locations and with the same autoId sense the posts is exactly the same. So I have just managed to let users to delete their posts, but when they upload the posts it is actually uploaded to 3 different locations like this
// this uploads the "post" to a global page
Database.database().reference().child("posts").childByAutoId().setValue(postObject)
// this uploads the "post" to a specific country page the user have choosen
Database.database().reference().child("AlbaniaPosts").childByAutoId().setValue(postObject)
// this uploads the "post" to the users uid so all their posts will be shown at the profile page
Database.database().reference().child(uid!).childByAutoId().setValue(postObject)
And when they are saved in firebase they are saved as different childByAutoId so I was wondering if it is possible to save them all in the same childByAutoId ID.
So that if the user delete's a specific post then I can search for the AutoId they deleted and delete it from all countries and profile page and the global page, but I can't right now sense I upload the post in diffrent Id's
So if it is still hard to understand my question here is an image of my database
As you can see the posts are exactly the same, but they are saved in different AutoId's
Goal? My goal is to set the same id for the post when they are uploaded at all 3 locations.
help would be very much appreciated.
thanks for your time.
Yes you can do this. The problem is with how you are uploading the information. Get one key and reuse it. Like this:
let key = ref.child("posts").childByAutoId().key // use this key for all uploads
// this uploads the "post" to a global page
Database.database().reference().child("posts").child(key).setValue(postObject)
// this uploads the "post" to a specific country page the user have choosen
Database.database().reference().child("AlbaniaPosts").child(key).setValue(postObject)
// this uploads the "post" to the users uid so all their posts will be shown at the profile page
Database.database().reference().child(uid!).child(key).setValue(postObject)
Now all posts will be uploaded using the same key. I did childByAutoId() on "posts" but you can do it on any location. The reason you are getting different keys is because they are determined by the time you request them. Since you're requesting them at different times you're getting different keys.
Additionally, I think you might want to carefully think about how you want to access and modify data within your app. For example modifying one post requires editing the data in three separate locations. This problem could be solved by the use of cloud functions. Another option would be to just copy the "key" to things like "AlbaniaPosts", then you can query the keys for all posts under "AlbaniaPosts" you can use the keys to load the full post from "posts". You'd still need cloud functions to copy and delete the key around your database. Let me know if you have any questions.

Facebook pixel events call from server

I have absolutelly the same question as dan here - Facebook conversion pixel with "server to server" option . There was written, that there was no way, but it was 2013, so I hope something changed.
So, is there any way to call facebook pixel events (e.g. CompleteRegistration) from server side now?
I can describe situation in more details. Imagine, that user visits our site, where fb pixel tracks 'PageView' of course. When user passes form and sends his phone number, we call 'Lead' event. But then we need to track one more event, when our manager successfully confirmes this user! Of course, it happens on other computer and so on, so there is no idea, how to "connect" to base user.
I've seen a lot of documentation departments like this, but I can't fully understand even if it's possible or not.
Logically, we need to generate specific id for user (or it can be phone number really), when 'Lead' event is called. Then, we should use this id to 'CompleteRegistration' for that user. But I can't understand, how to do it technically.
It would be gratefull, if somebody could explain it.
P.S. As I understand, it is fully available in API for mobile apps. Is it ok idea to use it for our situation, if there is no other solution?
Use Offline Conversions to record events that happen after a user has left your website. Logging these conversions, technically, is very easy. Setting everything up takes a little effort
tldr; check the code below
Follow setup steps in the FB docs (Setup steps 1-5) which are:
Setup facebook Business Manager account
Add a new app to Business Manager account
Create an Ad account, if you don't already have one
Create a System User for the ad account
After the setup, follow Upload Event Data steps on the same page, steps 1-3 to create an offline event set and associate it with your ad. These can be carried out in the Graph API Explorer by following the links in the examples. These can be done programmatically, but is out of the scope of making the event calls from the server for one campaign.
Once you have created the event set, then you can upload your CompleteRegistration events!
You will need to make a multipart form data request to FB, the data key will be an array of your conversion events. As #Cbroe mentioned, you must hash your match keys (the data you have available about your user to match them with a FB user) before sending to FB. The more match keys you are able to provide, the better chance at matching your user. So if you can get their email and phone at the same time, you're much more likely to match your user.
Here's an example of the call to FB using node.js:
var request = require('request')
// The access token you generated for your system user
var access_token = 'your_access_token'
// The ID of the conversion set you created
var conversionId = 'your_conversion_set_id'
var options = {
url: 'https://graph.facebook.com/v2.12/' + conversionId + '/events',
formData: {
access_token: access_token,
upload_tag: 'registrations', //optional
data: [{
match_keys: {
"phone": ["<HASH>", "<HASH>"]
},
currency: "USD",
event_name: "CompleteRegistration",
event_time: 1456870902,
custom_data: { // optional
event_source: "manager approved"
},
}]
}
}
request(options, function(err, result) {
// error handle and check for success
})
Offline Conversion Docs
Facebook has now a Server-Side API: https://developers.facebook.com/docs/marketing-api/server-side-api/get-started
Implementing this is similar to implementing the offline events outlined in the accepted answer.
Keep in mind that it will always be cumbersome to track and connect events from the browser and from your server. You need to share a unique user id between the browser and server, so that Facebook (or any other analytics provider) will know that the event belongs to the same user.
Tools like mixpanel.com and amplitude.com may be more tailored to your needs, but will get very expensive once you move out of the free tier (100+ EUR at mixpanel, 1000+ EUR at Amplitude, monthly). Those tools are tailored towards company success, whereas Facebook is tailored towards selling and measuring Facebook ads.

FB Chatbot how to get the previous message

Is it possible to receive the previous message that the user have send to the chatbot (without using quick replies or postback buttons). Example:
User: "Can you call a friend?"
Bot: "Who should I call?"
User: "Tim"
In the API I now have just the information "Tim", without knowing if I should call him or text him or make him a sandwich or whatever. So I basically I want to add some Postbackdata or metadata additionally to the text "Can you call a friend" (intent: 'CALL'), so the message "Tim" will come with that data.
Is there a way without storing the data into a database? AWS Lambda with ClaudiaJs.
I found the metadata field in the FB API which turns out to be the wrong field for that since it is only for communicating between several apps?!
What you are looking for a called a "slot-based bot", or slot-filling, basically meaning that you have a "slot", or blank that needs to be filled in before your bot can perform an action. In your example you have two slots: action and person
Actions could be: call, text, message
Person: name of a person, friend, etc.
I don't think any of the message frameworks (Slack, Facebook, etc) will provide you with the information you need. You will need to build this logic out yourself.
You can look at using wit.ai stories to achieve this.
Look to this similar Stack Overflow question and answer.
You can reverse order of conversation, and at beginning user writes some text or send you something else. After receiving, you should send to user buttonsTemplate, where postbacks will be like "CallTo&Tim" where instead of Tim you can put every text you need to pass to next executor(and you also can store previous user message here). Than just make substring of postback, check it`s type and do whatever you want.

Meteor: The Correct way to access user information

I'm new to meteor and I am in the process of building my application, but I am running into some "best practice" questions.
I have three databases. One for the users, one for questions, and one for answers. Think of my site as a quora of sorts.
I am iterating through the questions, and I am having a hard time accessing the user information. At the moment when a question gets recorded, I record the question, and I only record the user's id. Coming from a rails background, this is usually all I need to access all the other information when I need it. I don't record the name and all the other info.
The question I have is what is the right way to access the user information? I am doing an each loop and it seems that Meteor.user is only for logged in users (only for my information basically). I can't seem to do something like Meteor.users.find({_id: this.user_id}) on the questions helper so that I could get each users info. It retrieves nothing like this.
In the examples I have seen, they usually record the information on the questions database. So they would record first,last name, and anything else that would be necessary. But this seems like I am recording information twice.
What is the right way to access this info? Should I record it when a question is posted or should I call it from the users database everytime?
I have tried accessing the info through a server method, but can't seem to get it to work. Been at this for a while so I though I should ask instead.
Thanks in advance!!
My solution would be just save the user_id field in db as you're doing right now,
If you save firstname or lastname with the questions they may change in future (considering the fact that in most sites we have option to change names)
I can't seem to do something like Meteor.users.find({_id:
this.user_id})
this is because user data is not availbale in the client, that is the reason why you can't see the data
solution is while publishing the question to client you should publish the information of the user too
Example
Meteor.publish('question,function(question_id){
var question = Question.findOne(question_id);
//get info of the question user
var user = Meteor.users.find({_id: question.user_id });
//you cannot publish findone result, so do another query
var ques_rec= Question.find({_id: question_id});
return [ques_rec, user];
});
Now, in the client side when you do I can't seem to do something like Meteor.users.find({_id: this.user_id}) you'll get the user data.
I think I found a solution. I haven't turned the autopublish off, so this seems to be working for now.
Template.questionItem.helpers({
user: function(){
user = Meteor.users.find({_id: this.user_id}).fetch();
return user[0]
}
I think basically you can search users, but for some reason if I do
Meteor.users.find({_id: this.user_id})
it returns an object and I can't seem to find the actual data.
But if I add the .fetch() at the end it will return an array for the user. So if declare it as a variable, I can access all the info by saying user[0].
I'm not sure if if this is the best way to do it in meteor, but it does seem to work.
So when I call the object in the handlebars I go something like:
{{user.profile.name}}
I think once I turn autopublish off I will have to do the same thing but from the server side.