Firebase retrieve data when certain child value within child changes - javascript

So I have a firebase realtime database which keeps users messages. At the moment I have it so when a users 'inbox' child is updated it retrieves the latest data for that child, for example:
firebase.database().ref('users/'+firebase.auth().currentUser.uid+'/inbox/').on('child_changed', function(snapshot) {
// get the latest data
});
But when using this method, if a message is deleted by the user, it will retrieve the child data again. Is there anyway to only retrieve the latest data only if a child inside of that child is updated?
So in my database I have it set out like this:
{
"users" : {
"IPenCEXKICROYWzYtyzybzhem9R2" : {
"inbox" : {
"32tcqIRaG2RAgb9dTwmcgKoVPhd2IPenCEXKICROYWzYtyzybzhem9R2" : {
"lastmessagetimestamp" : 1564139876817,
"conversationmessages" : {
"-LhKZ-MUVxbyRcqp-Q_c" : {
"from" : "32tcqIRaG2RAgb9dTwmcgKoVPhd2",
"messagetext" : "this is a test message",
}
},
},
},
},
}
}
I am saving that timestamp of the last message a user receives. So I would like it to only retrieve data if the 'lastmessagetimestamp' child value is changed.
The simplest way I could think of doing this would be:
firebase.database().ref('users/'+firebase.auth().currentUser.uid+'/inbox/**conversationid**/lastmessagetimestamp/').on('child_changed', function(snapshot) {
// get the latest data
});
But because my 'conversationid' is randomly generated and is not unique, it cannot be done. Is there a way to possibly skip the 'conversationid' and go directly to the 'lastmessagetimestamp'?

Related

How to pass/delete array params in HTTP Params with Angular

I have an Array of statuses objects. Every status has a name, and a boolean set at false by default.
It represent checkbox in a form with filters, when a checkbox is checked bool is set at true :
const filters.statuses = [
{
name: "pending",
value: false
},
{
name: "done",
value: false
},
];
I am using Angular HTTP Params to pass params at the URL.
filters.statuses.forEach((status) => {
if (status.value) {
this.urlParams = this.urlParams.append('statuses[]', status.name);
}
});
Url params looks like when a status is checked :
&statuses%5B%5D=pending
My problem is when I want to unchecked.
I know HTTP Params is Immutable, so, I'm trying to delete the param when checkbox is unchecked, so set to false :
...else {
this.urlParams = this.urlParams.delete('statuses');
}
But, it not works, URL doesn't change.
And if I re-check to true after that, the URL looks like :
&statuses%5B%5D=pending&statuses%5B%5D=pending
How can I delete params, if the status value is false, and keep others statuses in URL ?
Project on Angular 10.
Thanks for the help.
UPDATE : It works to delete, my param name was not good :
else {
this.urlParams = this.urlParams.delete('statuses[]', status.name);
}
But, my other problem, it's when I check 2 or more checkbox, the append function write on URL : &statuses%5B%5D=pending&statuses%5B%5D=pending&statuses%5B%5D=done
I have prepared an example to try to answer your question (If I understand this right way).
You can change the checkboxes state or the URL to play with it. Also, I added helper buttons, which will navigate you to different cases (by changing the URL).
Here is the example: https://stackblitz.com/edit/angular-router-basic-example-cffkwu?file=app/views/home/home.component.ts
There are some parts. We will talk about HomeComponent.
You have ngFor which displays statuses, I handled state using ngModel (you can choose whatever you want).
You have a subscription to the activatedRoute.queryParams observable, this is how you get params and set up checkboxes (the model of the checkboxes)
You have the ngModelChange handler, this is how you change the route according to the checkboxes state
Let's focus on 2 & 3 items.
The second one. Rendering the correct state according to the route query params. Here is the code:
ngOnInit() {
this.sub = this.activatedRoute.queryParams.subscribe((params) => {
const statusesFromParams = params?.statuses || [];
this.statuses = this.statuses.map((status) => {
if (statusesFromParams.includes(status.name)) {
return {
name: status.name,
active: true,
};
}
return {
name: status.name,
active: false,
};
});
});
}
Here I parse the statuses queryParam and I set up the statuses model. I decide which is active and which is not here.
The third one. You need to update the URL according to the checkboxes state. Here is the code:
// HTML
<ng-container *ngFor="let status of statuses">
{{ status.name}} <input type="checkbox" [(ngModel)]="status.active" (ngModelChange)="onInputChange()" /> <br />
</ng-container>
// TypeScript
onInputChange() {
this.router.navigate(['./'], {
relativeTo: this.activatedRoute,
queryParams: {
statuses: this.statuses
.filter((status) => status.active)
.map((status) => status.name),
},
});
}
Here you have the ngModelChange handler. When any checkbox is checked/unchecked this handler is invoked. In the handler, I use the navigate method of the Router to change the URL. I collect actual checkboxes state and build the query parameters for the navigation event.
So, now you have a binding of the checkboxes state to the URL and vice versa. Hope this helps.

Restricting writing in firebase to both "mainsibbling" and "slavesibbling" nodes (if "slavesibbling" has a variable key)?

I am trying to restrict writing to two related firebase nodes. I have a the following data structure on firebase:
"mainsibblings" : {
"-L9ygIWI-TKeNZvQ-TmP" : {
"MACaddress" : "111111111111",
},
},
"slavesibbling" : {
"111111111111" : {
"onMainSibling" : "-L9ygIWI-TKeNZvQ-TmP",
}
}
The slavesibbling pushkey is a unique MAC Address (freely created and updated by the user).
Conditions:
1) user should have permission to write a new mainsibbling and slavesibbling
2) shouldn't have permission to overwrite neither (mainsibbling & slavesibbling) if slavesibbling key (in this case: "111111111111") already exists.
3) the "owner" should be able to update both node even after he creates them
So if someone types a MAC Address that is registered already, the child nodes can't be updated, but if the user (referenced in ownerID) want to update a MAC Address he has created he should be able to do it.
How do I write the firebase database rules to control this?
I am trying this to prevent duplicates but it's not working:
"mainsibblings": {
".read": true,
"$pushKey": {
".write": "data.child('MACaddress').val() != newData.child('MACaddress').val()",
}
},
"slavesibbling": {
"$MACaddress":{
".read": true,
".write": "!data.hasChild(newData.val())",
}
}
This is the write operation I want to allow/disallow (I have replaced the MACaddress variable for a hardcoded MACaddress):
function AddSibblings(newMainSib, newSlaveSib) {
var MACaddress = "111111111111";
var ownerID = "QXXds7d33ceyecc4inoe33p_3";
mainRef.child(MACaddress).set(this.newMainSib);
otherRef.child(MACaddress).set(this.newSlaveSib)
mainRef.child(MACaddress).child('ownerID').set(ownerID);
otherRef.child(MACaddress).child('ownerID').set(ownerID);
otherRef.child(MACaddress).child('onMainSibbling').set(MACaddress);
}
There is no way in Firebase security rules to find whether a specific value exists in a list of children. If you want to ensure that a value is unique, you should use that value as the key of a collection.
For example in your case you can easily change the data to use the MAC address as the key for both mainsiblings and slavesiblings:
"mainsiblings" : {
"111111111111" : {
"MACaddress" : "111111111111",
},
},
"slavesibbling" : {
"111111111111" : {
"onMainSibling" : "111111111111",
}
}
And
"mainsiblings" : {
"111111111111" : {
"MACaddress" : "111111111111",
},
"223344556677" : {
"MACaddress" : "223344556677",
}
},
"slavesibbling" : {
"111111111111" : {
"onMainSibling" : "111111111111",
},
"223344556677" : {
"onMainSibling" : "223344556677",
}
}
Since property names are by definition unique in JSON, there is no way to have duplicate MAC addresses in this data structure.
Also see:
Firebase android : make username unique
How do you prevent duplicate user properties in Firebase?
Firebase security rules to check unique value of a child #AskFirebase
unique property in Firebase
Enforcing unique usernames with Firebase simplelogin

Meteor React Subscribe to specific collection data

I am building a React - Meteor web app and I need to get access to a specific piece of data from a collection.
There are three main components, the company list, the project list and the task list. When I list all of the companies, I can select one and then display all of the project associated with that company. What I want to then do is click on a project and see all of the tasks which are associated with that project. My data structure is as follows (as you can see, projects are an array of objects):
{
"_id" : "aQnrkqMi6ugEvav4a",
"owner" : "7Gp49ZCtGC9oEx3jN",
"createdAt" : ISODate("2017-05-08T15:52:27.777Z"),
"data" : {
"name" : "lkhgb",
"contacts" : [
{
...
}
],
"projects" : [
{
...
},
{
"name" : "ljhvljhvblhkjvblhkj",
"id" : "258757206",
"tasks" : [
"task1",
"task2"
]
}
]
}
}
In my TaskList Component, I'm exporting it like so:
export default createContainer((props) => {
const {companyId} = props.match.params;
Meteor.subscribe('company');
return {project: Company.findOne(companyId)};
}, TaskList)
Where I'm pulling the companyId off the props. I am also pulling off the projectId from props, but when I query the collection it's just returning undefined. I have published the GitHub repo as live here - https://github.com/GlueDigiStu/ClientManager and would appreciate any help.
You did indeed subscribe to the "company" publication on the server.
Although that publication does not exist.
Meteor.publish('company', function (companyId) {
const publications = [];
publications.push(Company.find(
{
_id: companyId,
},
));
return publications;
});
This will publish the id you want from the server.
Then the subscription from the client would be:
Meteor.subscribe('company', props.companyId);
Let me know if you have any further questions.
If I may I'll also give you some suggestions. Having documents in documents in documents isn't really great for publications.
I would suggest to place projects and tasks into new collections and only subscribe to tasks once their project is selected.

Firebase multi location update, delete redundant data as part of update

I'm using firebase multi location updates to update a number of locations in my database.
One of the update paths contains a value that could be changed as part of the update therefore creating a new record at the new path location which is correct. This then leaves a redundant data at the original location that I need to delete as part of the update. Can someone give me a pointer on how to delete the redundant data as part of the update?
I am using the code below to create my update: -
var updatedUserDataContent = {
title: $scope.postToUpdate.title
commenter: $scope.postToUpdate.commenter
};
updatedUserData["Posts/" + $scope.postToUpdate.$id] = updatedUserDataContent;
updatedUserData["UserPosts/" + $scope.postToUpdate.commenter + "/" + $scope.postToUpdate.$id] = updatedUserDataContent;
The commenter could be changed by a user on the record before saving leaving the redundant data in the original commenter node. I have tried using the security rules but these stop data being created rather than deleted.
Edit 1
JSON as requested.
Before the update I have
{
"Not assigned" : {
"-KNfs3OhBmbb93w9VEW-" : {
"title": "vytg",
"commenter": "Not assigned"
}
}
}
After the update (after changing commenter from "Not assigned" to "User A") I have
{
"Not assigned": {
"-KNfs3OhBmbb93w9VEW-": {
"title": "vytg",
"commenter": "Not assigned"
}
},
"User A" : {
"-KNfs3OhBmbb93w9VEW-" : {
"title": "vytg",
"commenter": "User A"
}
}
}
but I want just
{
"User A" : {
"-KNfs3OhBmbb93w9VEW-" : {
"title": "vytg",
"commenter": "User A"
}
}
}
You're trying to move/rename a node, which is not an operation the database supports.
Since the new data is written correctly, all that is needed is that you also clear out the old node. Putting null in for that location will take care of that:
updatedUserData["Not assigned"] = null;

Creating a filter in the view using AngularFire/Firebase query

I am building an app in which the user inputs tastings, and those tastings are displayed in a few places - on the user's personal tastings page, and on a "latest tastings" style page.
On the tastings page, when a button is clicked, a "basicTastings()" function is called, and that's where I want to query the DB and for the view to update with only those tastings which have the "Type: Basic" (thee is also type: advanced).
In that function, my test console log is properly outputting that a key() is basic/advanced. Where I'm lost is in then pushing that object into the scope so that only those objects now go through the ng-repeat.
Finally - does it even make sense for me to be doing it this way? I was approaching it in this manner instead of through, say, a jQuery .click() filtering idea because I thought it was a better approach if I end up with large amounts of data in the DB.
My data is set up as:
{
"tastings" : {
"-JlUCGqbLssTeob7weQI" : {
"brewdate" : "2015-03-28T07:07:04.880Z",
"origin" : "Panama",
"overallrating" : 4.5,
"roaster" : "Verve",
"roastname" : "Los Lajones Honey",
"subdate" : 1427526458869,
"thoughts" : "Cup Characteristics: This Honey-Process coffee from Los Lajones is abdundantly juicy and filled with lovely stone fruit notes like Bing cherry and apricot. The body is like a perfectly tempered ganache.",
"type" : "basic",
"user" : "simplelogin:19"
}
},
"simplelogin:19" : {
"date" : 1427086513603,
"email" : "test#test.com",
"regUser" : "simplelogin:19",
"username" : "Tester"
}
}
}
My controller for listing the tastings is:
myApp.controller('TastingsListController',
function($scope, $firebaseArray, $rootScope, $location, Authentication, FIREBASE_URL) {
$scope.$emit('callForAuth'); // Emitter to initiate $onAuth function in Authentication service
var ref = new Firebase(FIREBASE_URL + '/tastings');
var tastingsInfo = $firebaseArray(ref);
$scope.filterBasic = function() {
ref.orderByChild("type").on("child_added", function(tasting) {
console.log(tasting.key() + ' is a ' + tasting.val().type + ' tasting.');
if(tasting.val().type === "basic") {
tastingsInfo.push(tasting);
console.log(tastingsInfo);
}
});
};
});
And finally the relevant ng-repeat and the button to trigger the function:
<md-button class="md-raised md-accent" ng-click="filterBasic()" flex>
Basic
</md-button>
</div>
</md-toolbar>
<md-content class="md-padding">
<div class="basiccontainer" ng-repeat="tasting in tastings | orderBy: 'brewdate'">
EDIT: The data I'm getting in my console.log occurs after each tasting that is "basic", which is where it should be happening, but it contains the entire array of objects, when I want it to contain the object only, and then I want to put that object into an array that I can then iterate on with ng-repeat.
Edit 2: I think I'm approaching this whole thing wrong:
I've been reading and re-reading, and I think I see now where my approach is, basically, exactly what the Firebase docs are saying not to do:
Regarding my data structure and filtering by user for their own tastings: Currently, I'm not indexing anything meaning that, as the data changes, I have to iterate through everything to see who owns what.
What I SHOULD be doing in my case is to, when a user submits a Tasting, to have a user index within the tasting, even if it's just going to ever have one creator, also also have the tasting's key indexed with the user, e.g.:
Tasting:
"tastings" : {
"-JlUCGqbLssTeob7weQI" : {
"brewdate" : "2015-03-28T07:07:04.880Z",
"origin" : "Panama",
"overallrating" : 4.5,
"roaster" : "Verve",
"roastname" : "Los Lajones Honey",
"subdate" : 1427526458869,
"thoughts" : "Cup Characteristics: This Honey-Process coffee from Los Lajones is abdundantly juicy and filled with lovely stone fruit notes like Bing cherry and apricot. The body is like a perfectly tempered ganache.",
"type" : "basic",
"user" : {
"simplelogin:18": true,
},
},
User:
"users" : {
"simplelogin:18" : {
"date" : 1427062799596,
"email" : "test#test.com",
"regUser" : "simplelogin:18",
"username" : "Tester",
"tastings" : {
"-JlUCGqbLssTeob7weQI": true,
"<Tasting 2 Key>": true,
...
},
},
This setup will allow me to actually set up a two-way relationship between a tasting and its creator. I can do this all through the same submit function for when the user presses the "Submit" button - send the data for the tasting, as well use that tasting's key over to the user's tastings index.
Regarding my user of the controller vs a service: What I'm doing right now is trying to do everything in the controller.
What I SHOULD be doing is to, from the controller, call a "listingsfilter" service (or whatever I decide to call it) when the function within my controller is called via the button press. That service itself should be doing the actual work of querying the Firebase for the listings, putting them into an array of some kind, and sending that back out to the controller, who then just feeds that data to the $scope through which the ng-repeat will iterate.

Categories

Resources