Dynamic querying using firebase [duplicate] - javascript

This question already has answers here:
Query based on multiple where clauses in Firebase
(8 answers)
Closed 7 years ago.
I have this use case where I have to query based on the parameters sent out by the user in the form of an object. The user could send out multiple parameters to query. It is something similar to what "SELECT * FROM users WHERE FIRST_NAME = 'something' AND LAST_NAME = 'something'" is in SQL.
A sample object could be:
var object= {
email: "some...#google.com",
location: "San Jose, CA"
};
I have these fields (email & location) in my firebase data at some endpoint lets call it /users
So the users endpoint would look like:
{
"randomID1":{
email: "some...#google.com",
location: "San Jose, CA"
},
"randomID2":{
email: "anothe...#fb.com",
location: "Menlo Park, CA"
}
}
I have to use the above mentioned object and generate a query dynamically for firebase, here's what I have:
return $q(function(resolve, reject) {
ref.orderByChild("email");
for(var key in filterObject){
if(filterObject.hasOwnProperty(key)){
console.log("Key: ",key);
console.log("Value: ",filterObject[key]);
ref.equalTo(""+filterObject[key],""+key);
}
}
return ref.on("value", function (snapshot) {
resolve(snapshot.val());
}, function (errorObject) {
reject(errorObject);
});
});
This always returns me all the data and doesn't really filter anything. Can anyone provide suggestions here? I am new to firebase , sorry if this is a naive question.
Thanks

After some attempts, I found an answer.
Basically, Firebase does not allow to query on more than a single property so I query firebase on the first property and if there are any more properties to still query, I use underscore js to filter on those remaining properties.
Code looks like this:
return $q(function(resolve, reject) {
var query = ref ;
for(var key in filterObject){
if(filterObject.hasOwnProperty(key)){
query = query.orderByChild(key);
query = query.equalTo(filterObject[key]);
delete(filterObject[key]);
break; //break out after querying the first property in firebase
}
}
return query.on("value", function (snapshot) {
var objects = snapshot.val();
var result = _.filter(objects, function(obj){
return _.isMatch(obj, filterObject); //for all other objects I use underscorejs to filter
});
resolve(result);
}, function (errorObject) {
reject(errorObject);
});
});

Related

Realtime database update or create functionality

I have a user object in firebases realtime database. I am querying realtime database and when successful I am wanting to write some new data to the user object under the users node.
My desired outcome: When a user has not fetched the information before, a new field called 'lastViewed' under the user object is created and if the field has already been created then we update the timeViewed keys value. A user can have multiple objects in the array corresponding to the uuid of the fetched data.
Please see the user object below
This may not need to be an array if using .push()
-N0X8VLHTw3xgvD2vJs- : { // this is the users unique key
name: 'myName',
lastViewed: {
[
{
timeViewed: 1651558791, // this is the field to update if exists
datasUniqueKey: 'N17ZmwIsbqaVSGh93Q0' // if this value exists update timeViewed else we create the entry.
},
{
timeViewed: 1651558952,
datasUniqueKey: 'N17ZmwIsbqaVSad3gad'
},
]
}
}
Please see my attempt below.
const getData = database()
.ref(`data/${uniqueKeyFromData}`)
.on('value', snapshot => {
if (snapshot.exists()) {
database()
.ref(`users/${currentFirebaseUserKey}/lastViewed`) // currentFirebaseUserKey = N0X8VLHTw3xgvD2vJs
.once('value', childSnapshot => {
if (childSnapshot.exists()) {
// update
database()
.ref(
`users/${currentFirebaseUserKey}/lastViewed`,
)
.update({
timeViewed: new Date(), // new data will not give us the corresponding date format in the user object above but don't worry about that
fetchedDatasUniqueKey: uniqueKeyFromData,
});
} else {
// create
database()
.ref(
`users/${currentFirebaseUserKey}/lastViewed`,
)
// Push creates a unique key which might not be required so maybe set?
.push({
timeViewed: new Date(),
fetchedDatasUniqueKey: uniqueKeyFromData,
});
}
});
}
});
Where I think I am going wrong
Above I am not creating an array, if I use push I would get a unique key generated from firebase but then would have to use that key when updating, something like
`users/${currentFirebaseUserKey}/lastViewed/${lastViewedUniqueKey}`
So the user object would look like so
-N0X8VLHTw3xgvD2vJs- : { // this is the users unique key
name: 'myName',
lastViewed: {
-N17i2X2-rKYXywbJGmQ: { // this is lastViewedUniqueKey
timeViewed: 1651558791,
datasUniqueKey: 'N17ZmwIsbqaVSGh93Q0'
},
}
}
then check for snapshot.key in the if?, any help would be appreciated.
Since you don't want a list of data, but a single set of properties for ``, you should just call set instead of push:
database()
.ref(`users/${currentFirebaseUserKey}/lastViewed`)
.set({ // 👈
timeViewed: new Date(),
fetchedDatasUniqueKey: uniqueKeyFromData,
});
I also don't think you need the two different cases here for create vs update, as both seem to do the exact same thing. If you do need both cases though, consider using a transaction.

Obtain Dexie values without using toArray()

I have the following code:
(function () {
var gameDataLocalStorageName = "myTest3";
var defaultUserSettings = {
volumes: {
musicVolume: 0.3,
sfxVolume: 0.5,
voicesVolume: 1
}
};
var savedGames = [
{
screenshot: "data uri here",
day: "1",
month: "1",
year: "1",
time: "1",
gameData: {
fonts: [{
id: 123,
name: "Arial"
}],
globalSpeeches: {
anotherVal: "something"
}
}
}
];
console.log(gameDataLocalStorageName);
console.log(defaultUserSettings);
console.log(savedGames);
/* Create db START */
var db = new Dexie(gameDataLocalStorageName);
db.version(1).stores({
usersData: ""
});
db.usersData.put(defaultUserSettings, 'userSettings');
db.usersData.put(savedGames, 'savedGames');
}());
/* Create db END */
/* Recall db START */
setTimeout(function(){
var db2 = new Dexie("myTest3");
db2.version(1).stores({
usersData: "userSettings,savedGames"
});
db2.usersData.toArray().then(function (results) {
console.log("User settings is: ", results[1]);
console.log("Saved games is: ", results[0]);
});
}, 3000);
Which runs great. However how can I obtain the data again without having to render out as an array toArray(). Currently to obtain them I have to hardcode results[0] and results[1] which is also not in the same order as I entered them into the db.
Ideally I want to do something like:
db2.get('usersData.userSettings');
db2.get('usersData.savedGames');
The sample show you are changing primary key which is not supported:
The first declaration specifies a table "usersData" with outbound primary keys:
db.version(1).stores({
usersData: ""
});
Then in the setTimout callback, you redeclare it with:
db2.version(1).stores({
usersData: "userSettings,savedGames"
});
...which means you want an inbound primary key from the property "userSettings" and and index on property "savedGames".
There are three errors here:
You cannot change declaration without incrementing version number which is not done here.
You cannot change primary key on an existing database.
Promises are not catched so you do not see the errors.
It seems what you really intend is so use Dexie as a key/value store, which is perfectly ok but much simpler to do than the sample shows.
If you put() (or add()) a value using a certain key, you retrieve the same using get().
If so, try the following:
db.version(1).stores({
usersData: "",
});
And don't forget to catch promises or await and do try/catch.
(async ()=>{
await db.usersData.put(defaultUserSettings, 'userSettings')
await db.usersData.put(savedGames, 'savedGames');
// Get using key:
const userSettings = await db.usersData.get('userSettings');
console.log("User settings is: ", userSettings);
const savedGames = await db.usersData.get('savedGames');
console.log("User settings is: ", savedGames);
})().catch(console.error);
However, putting entire arrays as values in a key/value store is not very optimal.
Maybe only have two tables "userSettings" and "savedGames" where each saved game would be its own row? Will you support multiple users or just one single user? If multiple, you could add an index "userId" to your tables.
If so, try the following:
db.version(2).stores({
userSettings: "userId" // userId is primary key
savedGames: "++gameId, userId" // incremented id and userId is foreign key
});
(async ()=>{
await db.userSettings.put({...defaultUserSettings, userId: "fooUser"});
await db.savedGames.bulkPut(savedGames.map(game =>
({...game, userId: "fooUser"}));
// Get user settings:
const userSettings = await db.usersData.get('fooUser');
console.log("User settings is: ", userSettings);
const savedGames = await db.usersData.where({userId: "fooUser"}).toArray();
console.log("Saved games for fooUser are: ", savedGames);
})().catch(console.error);

Construct objects for client in Firebase Cloud Functions

I'm working on a simple registration system using Firebase as a backend. I am successfully authenticating users and writing to the database. I have an index of courses and users with the following structure:
{
courses: { // index all the courses available
key1: {
title: "Course 1",
desc: "This is a description string.",
date: { 2018-01-01 12:00:00Z }
members: {
user1: true
...
}
},
key2 { ... },
},
users: { // track individual user registrations
user1: {
key1: true,
...
},
user2: { ... }
}
}
I have a cloud function that watches for the user to add a course and it builds an array with the corresponding courseId that will look at the courses node to return the appropriate items.
exports.listenForUserClasses = functions.database.ref('users/{userId}')
.onWrite(event => {
var userCourses = [];
var ref = functions.database.ref('users/{userId}');
for(var i=0; i<ref.length; i++) {
userCourses.push(ref[i])
}
console.log(userCourses); // an array of ids under the user's node
});
So, my question has two parts:
How can I build the updated object when the page is loaded?
How do I return the function to the client script?
Question 1: From the client side you want to get the reference to the database path. Then you want to call the child_added event. Keep it in-memory, this will be called whenever one is add then you can update your UI.
var ref = db.ref("path/to/courses");
ref.on("child_added", function(snapshot, prevChildKey) {
var newClass = snapshot.val();
});
If you are completely refreshing the page then you can always grab the data again from the database path by using the value option and calling once
Questions 2: You don't. This is considered an asynchronous function. If you wanted a response from the function then you would setup an HTTP trigger and wait for the response from that function.

How to update MongoDB with no duplicates

I'm using Meteor framework with Blaze. How can I fetch data from an API and only insert new data in my MongoDB collection and not duplicates?
Fetching data from the API.
if (Meteor.isServer) {
Meteor.methods({
fetchApiData: function () {
this.unblock();
return Meteor.http.call('GET','http://jsonplaceholder.typicode.com/posts');},
Insert data into database:
populateDatabaseApi: function () {
Meteor.call('fetchApiData', function(error, result) {
myCollection.insert({
//upsert: true,
A: result.data.title,
B: result.data.userId,
C: result.data.id });
});
},
When using "myCollection.update" with "upsert: true" it does not insert new entries obviously. What is best practice to go about checking the API for data and inserting ONLY new entries with no duplicates and updating existing entries?
Thank you.
here's how i handle what i call reference data at startup. it's driven off of JSON data. you have to pick a field that serves as your "reference" for each JSON object, so you can see if it's already in the db.
_.each(ItemData.items, function(q) {
check(q, ItemsSchema);
Items.upsert({
item: q.item
}, {
$set: {
item: q.item,
}
}, function(error, result) {
if (error) {
let errMsg = 'Error while writing item data';
console.error(errMsg, error);
throw new Meteor.Error('500', errMsg);
}
});
});
i use an upsert to handle insert vs update.
I'm not familiar with your specific framework, so I can't help with syntax, but you should be able to find all documents with the same properties as the document you're trying to insert (there should be only one). If there is one, then save it using upsert. If there isn't, then the object you're saving is unique, and you should save a new one.
Using only "vanilla" Meteor, assuming your api object have unique ids and that you have the proper data access (ie, if item exist findOne would find it), I'd use :
populateDatabaseApi: function () {
Meteor.call('fetchApiData', function(error, result) {
var item = myCollection.findOne({A : result.data.id})
if(item){
//do nothing, this item already is in the db
}else{
myCollection.insert({
A: result.data.title,
B: result.data.userId,
C: result.data.id });
});
}
},

Like search into MongoDB with Monk library

I'm trying to do like search into mongodb with Javascript, but i havent yet figured it out how to do it. Any help is appreciated.
I have a search parameter called "search" at request body (req.body.search). I would like find all programs which name contains that search criteria. Ie. "harry" would return "Harry Potter", "Potter Harry", "How merry Harry Really is?" and other movies which contains harry in its name.
exports.programs = function(db, req) {
return function(req, res) {
var collection = db.get('programs');
collection.find({name: /req.body.search/ },{},function(e,docs){
res.json(docs);
});
};
};
I'm not familiar with the monk library but if the parameter in that find is a MongoDB Query Document this is the syntax you are looking for:
var query = {
name: {
$regex: req.body.search,
$options: 'i' //i: ignore case, m: multiline, etc
}
};
collection.find(query, {}, function(e,docs){});
Chekcout MongoDB $regex Documentaiton.

Categories