Adding upvotes to reactjs and Firebase chatroom - firebase

https://github.com/kristinyim/ClassroomChat
I want to add an upvoting feature to the messages on this chatroom similar to what you have on GroupMe, but I'm new to React and built this off of a tutorial so don't know where to even begin. I'm good with webdev but am just getting started with the basics of React.js and Firebase. Thanks!

NB: There are many ways to achieve this, so the following is just a suggestion.
First you must think of how you want to store your data in the database. If you have users, messages and message-likes, you could structure it like this:
"root": {
"users": {
"$userId": {
...
"messages": {
"$messageId1": true,
"$messageId2": true,
...
}
}
},
"messages": {
"$messageId": {
"author": $userId,
"timestamp": ServerValue.TIMESTAMP
}
},
"likesToMessages": {
"$messageId": {
"$likeId": {
liker: $userId,
"message": $messageId,
"timestamp": ServerValue.TIMESTAMP
}
}
}
}
Whenever a user clicks "like" on a message, you want to write to
var messageId = ?; // The id of the message that was liked
var like = {
liker: currentUserId, // id of logged in user
message: messageId,
timestamp: firebase.database.ServerValue.TIMESTAMP
};
firebase.database.ref().child('likesToMessages').child(messageId).push(like);
Then you get a new like in the database, matching the proposed structure.
Then, when you want to read and show the count of likes for a message, you can do like this:
const Message = React.createClass({
propTypes: {
message: React.PropTypes.object,
messageId: React.PropTypes.string // you need to add this prop
}
componentWillMount() {
firebase.database.ref().child('likesToMessages').child(this.props.messageId).on('value', this.onLikesUpdated)
}
onLikesUpdated(dataSnapshot) {
var likes = snap.val();
this.setState({
likes
});
}
render() {
const {name, message} = this.props.message;
const emojifiedString = emoji.emojify(message);
return (
<p>
{name}: {emojifiedString} [{this.state.likes.length}♥]
</p>
);
}
});
Also, in your database security rules, you'd want to index by timestamp for message and like so you can quickly query the newest messages.
Also, feel free to check out a similar app I made, code in GitHub and demo on wooperate.firebaseapp.com.

Related

Allow Create And Read in all the cases But don't allow update if the data does not exist in firebase real time database

I'm working on a chat module using fiebase , I have a structure as below .
My main node is mychatexperiment
inside it I have another node ChatListing and some other chat keys.
Today I set up setValues in my structure and when I passed my url without any node it deletes all of my data inside the parent node.
What i want is to set up the rules
One can create the node in any case
One can update the node in any case
One can not delete the node in any case
One Can only update or set the data inside ChatListing
I was trying using this but it does not work . any idea how to implement these things .
{
"rules": {
".write": "!data.exists() || !newData.exists()",
"read" : true
}
}
Note : I'm not using any authentication so need to implement rules without any authentication
Revise Requirements :
I have a structure where I have a single parent node and inside it I have multiple chat keys that is generated through firebase , Inside chat keys I have a node of individual message inside that chat .
The second thing which is most important is that I have a node called chatlisting in which I am storing my userids in a unique format like If My userid is 5 so inside chatlisting I am storing 5 and inside that 5 I have all the chat keys nodes which are related to me .
{
"ChatListing": {
"1126_2": { //userUnique key
"MjmhpHb6qR7VSkYzbjI": { // 1126 userid has this chat key and inside this chat last msg was welcome and its unread count is 0
"UnReadCount": 0,
"lastmessage": "Welcome",
"lastmessagetime": 1631870264251
}
},
"4184_1": {
"MjmhpHb6qR7VSkYzbjI": { // 4184 userid has this chat key as well and inside this chat last msg was welcome and its unread count is 1
"UnReadCount": 1,
"lastmessage": "Welcome",
"lastmessagetime": 1.6318646965369204E12
}
}
},
"MjmhpHb6qR7VSkYzbjI": { // chat key
"-MjmhpQbBaL7EbHPHayA": { // mesg key
"data": "Thankyou",
"time": 1.6318646965369204E12,
"type": 0,
"user": 4184 // the msg is sent by this user
},
"-Mjn21A4067dT4emYe05": { // another msg in the same chat
"data": "Welcome",
"time": 1631870264251,
"type": 0,
"user": 1126 // the msg is sent by this user
}
}
}
What I want is to setup the rules in which no one can run update , set or delete inside parent node (except ChatList node) . Any one can create chat keys and read chat keys inside parent node , nothing else they can do .
but inside chatlist they can perform create read , set and update(not delete) as I need to update the last message in this node against user chat .
So reusing the points as covered by my other answer, you would apply those rules using:
{
"rules": {
"ChatListing": {
"$userid": { // the user's unique ID
// anyone who knows this user ID can read their messages
".read": true,
"$chatid": { // a chatroom the user is in
// data stored here MUST have this shape (with nothing else)
// {
// UnReadCount: number,
// lastmessage: string,
// lastmessagetime: number
// }
// Data may be created or updated, but not deleted
".validate": "newData.child('UnReadCount').isNumber() && newData.child('lastmessage').isString() && newData.child('lastmessagetime').isNumber()",
"UnReadCount": { ".write": "newData.exists()" },
"lastmessage": { ".write": "newData.exists()" },
"lastmessagetime": { ".write": "newData.exists()" }
}
}
},
// when using $ keys at the same level as defined keys,
// this rule will catch everything that doesn't match
// the above rules
"$chatId": { // a chatroom where messages can be sent
// anyone who knows this chat ID can read its messages
".read": true,
"$msgId": { // a message in this chatroom
// Data stored here MUST have this shape (with nothing else)
// {
// data: string,
// time: number
// type: number,
// user: string, // see note
// }
// Data may be created, but not edited or deleted
// change like above if needed
".validate": "newData.child('data').isString() && newData.child('time').isNumber() && newData.child('type').isNumber() && newData.child('user').isString()",
"data": { ".write": "!data.exists()" },
"time": { ".write": "!data.exists()" },
"type": { ".write": "!data.exists()" },
"user": { ".write": "!data.exists()" }
}
}
}
}
Notes:
Don't use numeric user IDs as they are easily guessable, generate something random. You could even use const userId = push(getReference(getDatabase())).key. Consider securing the data with anonymous authentication.
Unlike your requirements, I have made the messages in the chat immutable. Once sent, no one can edit them. This prevents someone other than the sender from coming in and changing the message. With authentication, edits could be allowed because it's more secure.
Take note how unlike my /cars example, I haven't put ".read": true at the root of the database or at /ChatListing. This prevents someone coming along and running either of the below pieces of code to pull all of your stored data or pull all stored user IDs at once which will then allow them to find messages not meant for them. It does not prevent brute-forcing the data though.
const rootRef = ref(getDatabase());
rootRef
.then((snapshot) => {
console.log(snapshot.val()) // all data in database!
});
const chatListingRef = ref(getDatabase(), "ChatListing");
chatListingRef
.then((snapshot) => {
const usersArray = [];
const chatIdSet = new Set();
snapshot.forEach(userData => {
usersArray.push(userData.key)
userData.forEach(lastChatData => chatIdSet.add(lastChatData.key));
});
// logs all user IDs in the database!
console.log("User IDs:", usersArray)
// logs all chatroom IDs in the database!
console.log("Chatroom IDs:", [...chatIdSet])
});

Uncaught TypeError: Cannot read property 'Active' of undefined

I've got an account page in my SPA that i'm currently trying to populate with information from my database.
I've used Postman to see that the property I am trying to access is available. As well as I'm able to access another property from the same element.
JSON from Postman
{
"newsSubscription": {
"SubscriptionID": "1",
"ObjectID": "00000000-0000-0000-0000-000000000000",
"OrganizationNumber": null,
"CompanyName": null,
"Active": true,
"SubscriptionType": 1,
"ObjectType": 1
},
"Subscriptions": [],
"Email": "email#email.com"
}
I'm accessing the API using axios. I'm able to get the email address, but can't seem to access the true/false of "Active", which will determine if the checkbox is checked or not.
class Example extends React.Component {
state = {
emailAddress: [],
}
componentDidMount() {
axios.get(
'http://api',
{ withCredentials: true }
)
.then(res => {
const emailAddress = res.data;
this.setState({ emailAddress });
})
//error handling
}
render() {
return (
// Removed for simplicity
<Typography>The registered e-mail address for your account is <b>{this.state.emailAddress.Email}</b></Typography>
<Checkbox
checked={this.state.emailAddress.newsSubscription.Active}
value="email_notifications"
color="primary"
/>
);
}
}
I would like the API property "Active" to check or uncheck the checkbox based on a true/false response from the API call.
state = {
emailAddress: [],
}
Your state is quite confusing. Your state.emailAddress semms to be an array, but you use it like an object: this.state.emailAddress.newsSubscription.
Your API give you an object, or an array ? I think your issue is here.
Please add in the render section
console.log(this.state.emailAddress);
to see what is coming. Maybe there is an issue on how the data is arriving and that is why it fails.

QR code based login system [React-native + firebase]

I am newbie dev here.
I am working on a personal project (React Native + firebase) where the user login flow is something like this:
Each app user already has an id card with a unique user id provided to them in the form of a QR code.
User scans the QR code in the login page for the app.
If the unique id matches with the user id already present in the database - the user is authorized to access the app.
Note there is no sign up in my app.
I have trouble understanding how the implementation flow would look like in firebase. How do I authorize the user in firebase? Is it possible to use the firebase-auth for this?
Currently, I am just manually looking if the unique id exists in the database. I am allowing the user to navigate from the login page to other pages if the id matches with the id present in the database. Any user can make any changes to the database at present.
Here is the code I am using at the present:
_login = (status)=> {
if(status === "success") {
this.setState ({
logInStatus: "Login successful!"
})
this.props.navigation.navigate("Question")
}
else {
this.setState ({
logInStatus: "Login failed!"
})
this.props.navigation.navigate("Login")
}
}
//check if user exists in the database
userExists = async (data) => {
this.setState ({
logInStatus: "Logging you in!"
})
//connect to firebase
let userRef = firebase.database().ref('/users/')
let status
try {
let value = await userRef.child(data).once('value', function(snapshot) {
if (snapshot.exists()) {
status = "success"
}
})
}
catch(error) {
this.setState ({
logInStatus: "Login failed. Try again!"
})
console.log("Reached error")
}
this._login(status)
}
//when user has scanned once
_handleBarCodeRead = async ({data})=> {
this.userExists(data)
//stop user from scanning again
this.setState ({
barCodeRead: true,
fullScreen: false,
})
}
Clearly this is not a secure way to do the login...what is a better alternative?
This is the database structure which I am using as of now:
{
"schools": {
"school_001":{
"name": <name>,
"id": <school_id>,
.....other properties unique to the school
},
"school_002": {},
"school_003": {},
.....
"school_N": {}
},
"groups":{
"group_001":{
"group_id": <group ID>,
"school_id": <school ID>,
"members":[array list of group memebers/users]
....other group properties unique to the group
},
"group_002":{},
.....
"group_N":{}
},
"users":{
"user_001":{
"user_id":<user ID>,
"group_id":<group ID>,
"school_id": <school ID>
....other user properties unique to the user
},
"user_002":{},
....
"user_N":{}
},
"activity_feed":{
"school_001":{
"group_001":{
"activity_001":{
"actor": <name of the user>,
"action": <user's action>,
....other properties related to the activity
},
"activity_002":{},
......
},
"group_002":{},
....
},
"school_002":{},
......
}
}
I want users to have write access to only their own user properties, particular group properties and group activity feed.
You should put this in your firebase security rule
{
"rules": {
".read": "auth !== null",
".write": "auth !== null"
}
}

Vuejs & Firestore - How to Update when Data Changes in Firestore

I've gone through a bunch of tutorials and docs but cannot seem to be able to update on page when data changes in Firestore (NOTE: not Firebase)
Heres what I have currently which is working fine except if data changes in the DB it is not reflected on the page itself unless I refresh. Code below is within script tags:
import { recipeRef } from '../../firebase';
export default {
data() {
return {
recipes: []
}
},
firestore: {
recipes: recipeRef
},
created() {
db.collection('recipes').get().then((onSnapshot) => {
this.loading = false
onSnapshot.forEach((doc) => {
let data = {
'id': doc.id,
'name': doc.data().name
}
this.recipes.push(data)
})
})
}
I'm not using Vuex. Adding data, editing and reading works fine. Just not reflecting changes once data has changed. Maybe there is a life cycle hook Im supposed to be using? For "onSnapshot" - Ive tried "snap", "querySnapshot" etc. No luck.
Thanks in advance.
Remove the get() and just replace with snapshot - like so
created() {
db.collection('recipes').onSnapshot(snap => {
let foo = [];
snap.forEach(doc => {
foo.push({id: doc.id, name: doc.data().name})
});
}
});
I am not familiar with the firestore API, but glancing through the docs, it looks like calling get() is how you query a single time. Where you have onSnapshot should really be querySnapshot -- that is, the results of a one query. See:
https://firebase.google.com/docs/firestore/query-data/get-data
versus:
https://firebase.google.com/docs/firestore/query-data/listen
So to get live updates, it looks like you need to create a listener, like so:
db.collection('recipes')
.onSnapshot(function(snapshot) {
snapshot.forEach(function(doc) {
// Find existing recipe in this.recipes
// and swap in the new data
});
}, function(error) {
// handle errors
});
I think you will need to add that listener in addition to the get() query you are currently doing. Hope this helps!

Firebase on(child_added) some field 'undefined'

I am working on a real time application and i am using firebase with pure html and javascript (not angularJS).
I am having a problem where i saved user's data to firebase with the given code by firebase :
var isNewUser = true;
ref.onAuth(function(authData) {
if (authData && isNewUser) {
authData['status'] = 'active';
authData['role'] = 'member';
ref.child("users").child(authData.uid).set(authData);
}
});
This will add the authData to the /users/ node. As you can see that i also appended some custom fields to the authData, status and role.
Now i am using this code to get the user's data from firebase and display them.
ref4.on("value", function(snapshot) {
var snapshotData = snapshot.val();
console.log('username: '+snapshotData.status);
});
If i use on('value'), the status get printed out on the console but if i do it this way,
ref4.on("child_added", function(snapshot) {
var snapshotData = snapshot.val();
console.log('status: '+snapshotData.status);
});
It is showing undefined for the status. May i know what's wrong and how to fix this problem. Thank you.
Since value is returning the path provided by ref4, and child_added is returning each child of that path, it's unlikely both are going to have a key status.
Consider this data structure:
{
"users": {
"brucelee": {
"status": "awesome"
},
"chucknorris": {
"status": "awesomerest"
}
}
}
If I now query for this according to your incomplete example:
var ref = new Firebase('https://<instance>firebaseio.com/users/brucelee');
ref.on('value', function(snap) {
// requests the brucelee record
console.log(snap.name(), ':', snap.val().status); // "brucelee: awesome"
});
ref.on('child_added', function(snap) {
// iterates children of the brucelee path (i.e. status)
console.log(snap.name(), ':', snap.val().status); // THROWS AN ERROR, because status is a string
});
So to do this on child_added with a data structure like this (and presumably somewhat like yours), it would look as follows:
ref.on('child_added', function(snap) {
// iterates children of the brucelee path (i.e. status)
console.log(snap.name(), ':', snap.val()); // "status: awesome"
});

Resources