Consider the following firebase structure:
{
"users": {
"h7iStd4QvAYWh9xPbsiFuByDd1j1" : {
"email" : "jobs#apple.com",
"name" : "Steve Jobs",
"rank" : 0
},
"wkA0DrJs9wdBlfAkJuKIrnhNzyq2" : {
"email" : "gates#microsoft.com",
"name" : "Bill Gates",
"rank" : 0
}
}
}
and this data retrieval:
const email = 'jobs#apple.com';
firebase.database().ref('users').equalTo(email, 'email').once('value')
.then((snapshot) => {
console.log(email, snapshot.key, snapshot.val());
// snapshot.val() always returns null
})
.catch((error) => {
console.error(error);
});
Why snapshot.val() returns null? What is the correct approach to fetch the whole object concerning Steve Jobs data, the child with h7iStd4QvAYWh9xPbsiFuByDd1j1 key?
Try this instead:
firebase.database().ref('users').orderByChild('email').equalTo(email).once('value')
Related
I need to combine results from two different documents in Mongo. I have a function like this:
async function getReviewsByUserId (req, res) {
const { userId } = req.params
const reviews = await Review.find({ userId }).lean() || []
return res.status(200).send(reviews.reverse())
}
The reviews array looks like this:
{
"_id" : ObjectId("1263b55ef2cdd3ebb0654d1dd"),
"launchId" : "7fb83b40-7c6f-4099-aaed-fe9d0dc03111",
"userId" : "1",
}
{
"_id" : ObjectId("6355565cf5ef2cddebb065584"),
"launchId" : "12b53940-136f-3399-aaed-fe9d0dc05473",
"userId" : "7fb83b40-7c6f-4099-aaed-fe9d0dc03112",
}
I need to use the launchId from each review, look up a launch object from my mongo database, and combine that with the correct object in the reviews array.
Example of what I mean:
async function getReviewsByUserId (req, res) {
const { userId } = req.params
const reviews = await Review.find({ userId }).lean() || []
const launches = await Launch.find(/* find all launches where launch._id is equal to reviews.launchId*/)
return res.status(200).send(launches.reverse())
}
So if launches data looks like this (and launches is also an array of ALL launches):
{
"_id" : "12b53940-136f-3399-aaed-fe9d0dc05473",
"name" : "The Park",
}
Then how can I merge this with the reviews payload where the launch._id == reviews.launchId so that the final data looks like this:
{
"_id" : ObjectId("1263b55ef2cdd3ebb0654d1dd"),
"launchId" : "7fb83b40-7c6f-4099-aaed-fe9d0dc03111",
"userId" : "1",
}
{
"_id" : ObjectId("6355565cf5ef2cddebb065584"),
"launchId" : "12b53940-136f-3399-aaed-fe9d0dc05473",
"userId" : "7fb83b40-7c6f-4099-aaed-fe9d0dc03112",
"launch": {
"_id" : "12b53940-136f-3399-aaed-fe9d0dc05473",
"name" : "The Park",
}
}
This could be achieved by using aggregate pipelines.
Filter reviews by userId in $match stage
$lookup for launches
$unwind the launch array to an object
The solution could be:
async function getReviewsByUserId(req, res) {
const { userId } = req.params;
const launches = await Review.aggregate([
{
$match: {
userId
}
},
{
$lookup: {
from: "launches",
localField: "launchId",
foreignField: "_id",
as: "launch"
}
},
{
$unwind: {
path: "$launch",
preserveNullAndEmptyArrays: true
}
}
]);
return res.status(200).send(launches.reverse());
}
You can map() your reviews, and for each item you can use find() on the launches array to check if the launch id matches the current review id.
If there is no match, just return the review unaltered; if you get a match, you add the launches property to the current review and return it.
function ObjectId(oid) {
return oid
}
const reviews = [{
"_id": ObjectId("1263b55ef2cdd3ebb0654d1dd"),
"launchId": "7fb83b40-7c6f-4099-aaed-fe9d0dc03111",
"userId": "1",
},
{
"_id": ObjectId("6355565cf5ef2cddebb065584"),
"launchId": "12b53940-136f-3399-aaed-fe9d0dc05473",
"userId": "7fb83b40-7c6f-4099-aaed-fe9d0dc03112",
}
]
const launches = [{
"_id": "12b53940-136f-3399-aaed-fe9d0dc05473",
"name": "The Park",
}]
const res = reviews.map(r => {
const found = launches.find(l => l._id === r.launchId)
if (found) {
r.launch = {
_id: found._id,
name: found.name
}
return r
} else {
return r
}
})
console.log(res)
When my trigger receives an update, I am trying to loop through another node via the admin sdk to iterate through the results from the snapshot with matching keys from the wildcard {itemId}. For some reason my forEach seems to not do anything and I don't know why. I have been trying to resolve this for quite some time now. I have never had a problem querying my firebase DB but now that I am trying to do so in cloud functions I have not been having any luck at all. I don't know if this is related to firebase-admin at all?
Here is my code, I left some comments to show you what I am trying to accomplish more clearly:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
// admin.initializeApp();
//is this enough or do I need to add more configuration???
admin.initializeApp(functions.config().firebase);
exports.itemUpdate = functions.database
.ref('/items/{itemId}')
.onUpdate((change, context) => {
const before = change.before.val();
const after = change.after.val();
console.log(after); //data I want to use to update query results
//if (before === after) {
//console.log('data didnt change')
//return null;
//}
admin.database().ref('users')
.orderByChild('likedItems')
.equalTo(context.params.itemId)
.once('value')
.then(snapshot => {
//I am able to log the snapshot, but the data is misleading???
console.log(snapshot);
//I am trying to look for the likedItems that match the itemId (wild card)
//from my query - Why wont anything log? Is is a problem with my admin?
snapshot.forEach((childSnapshot) => {
var key = childSnapshot.key;
var targetItem = context.params.itemId; //item(s) to update
var child = childSnapshot.child;
var childData = childSnapshot.val();
//attempt to update childData that match the targetItem (that was originally updated)
//targetItem returns undefined here but when i log it its not undefined?
return childData.likedItems.targetItem.update(after);
});
return;
}).catch(error =>{
console.log(error);
});
});
Here is a look at what my DB structure looks like for the items, and then the users likedItems in their node:
"items" : {
"Item1" : {
"title" : "title1",
"type" : "type1"
},
"Item2" : {
"title" : "title2",
"type" : "type2"
},
"Item3" : {
"title" : "title3",
"type" : "type3"
},
"Item4" : {
"title" : "title4",
"type" : "type4"
},
...
...
...
},
"users" : {
"EoAhYX3mYcRASr5oPD1eSZ979Vr1" : {
"followerCount" : 4,
"followingCount" : 2,
"likedItems" : {
"Item1" : {
"title" : "title1",
"type" : "type1"
},
"Item10" : {
"title" : "title10",
"type" : "type10"
},
"Item12" : {
"title" : "title12",
"type" : "type12"
}
},
"username" : "user1"
},
...
...
...
}
I appreciate all the help/guidence I can get!
Cheers.
This question already has answers here:
How do you update objects in a document's array (nested updating)
(4 answers)
Closed 5 years ago.
This is my Schema:
const UserSchema = new mongoose.Schema({
name: String,
chats: [{
lastMessage: String,
lastUpdate: Date
}]
});
Users Collection:
{
"_id" : ObjectId("59987ef42aafc45204ee8bc3"),
"name" : "Nico",
"chats" : [
{
"_id" : ObjectId("599e58265cf2799c07925488")
"lastMessage": "Test",
"lastUpdate": "Test"
},
{
"_id" : ObjectId("599e59218d4a52c7071f46df")
"lastMessage": "Hi",
"lastUpdate": "01/01/2017"
}
]
},
{
"_id" : ObjectId("59987ef42aafc45204ee8bc3"),
"name" : "Lucas",
"chats" : [
{
"_id" : ObjectId("599e59218d4a52c7071f46df")
"lastMessage": "Hi",
"lastUpdate": "01/01/2017"
}
]
}
I am trying to implement chat on my app. I am working on my function that will find and update (on each User document, on Chat array with ObjectID as equals being received by request) but i don't know whats the best approach and how to implement it.
The request to my endpoint will give me ChatID, UserID, and MessageBody.
So my two approaches are:
1) Find by UserID, and then findAndUpdate on chat array prop where _idChat is equals as ChatID.
2) Find in Collection where _idChat is equals as ChatID and then update (this should retrieve me Users that have any object with ChatID and then update it)
This is what i am trying to implement (but having nothing being updated):
static sendMessage(req: express.Request, res: express.Response) {
const message = req.body.message;
const idChat = req.body.idChat;
const userIds = [req.body.sender, req.body.receiver];
const updatePromises = [];
userIds.forEach( userId => {
updatePromises.push(
UserDao['updateChat']
(
userId, idChat,
{'$set': {'chats.$.lastMessage': message, 'chats.$.lastUpdate': new Date() }},
);
});
Promise.all(updatePromises)
.then(values => {
return res.status(200).json(values);
}).catch(reason => {
return res.status(400).json(reason);
});
userSchema.static('updateChat', (userId, chatId, query) => {
return new Promise((resolve, reject) => {
if (!_.isObject(query)) {
return reject(new TypeError('query is not a valid Object.'));
}
if (!_.isString(userId)) {
return reject(new TypeError('userId is not a valid String.'));
}
User
.update(
{_id: userId, 'chats._id': chatId},
{$set: query},
{upsert: true}
).exec((err, updated) => {
err ? reject(err) : resolve(updated);
});
});
});
Thanks for your help!
How about this?
User.update({
_id: request.userId,
chats: {
$elemMatch: {
_id: request.chatId
}
}
}, {
$set: {
'chats.$.lastMessage': request.messageBody,
'chats.$.lastUpdate': new Date()
},
},
function(err) {
// handle error
console.log(err);
}
)
You are going to update the exact chat that matches your criteria because you'll be using the $ operator on the $set command.
I'm trying to figure out how to map over an API call.
The scenario is that an API gets called that returns a promise.
This promise could include objects that are undefined, I want to skip these objects and only add working objects into a state called `searchResult``
Here are two failed attempts
1:
onSearch = (query) => {
if(query){
BooksAPI.search(query).then((searchResult) => {
if(typeof searchResult !== 'undefined') {
console.log(searchResult)
searchResult.map((books) => {
books.shelf = this.state.bookShelfMapping[books.id] ?
this.state.bookShelfMapping[books.id] : 'none'
})
// Check if users changes searchResult
if(this.state.searchResult !== searchResult) {
this.setState({searchResult})
}
} else {
continue
}
}).catch( e => console.log(e))
}
}
2:
onSearch = (query) => {
if(query){
BooksAPI.search(query).then((searchResult) => {
if (Array.isArray(searchResult)) {
searchResult.map((books) => {
books.shelf = this.state.bookShelfMapping[books.id] ?
this.state.bookShelfMapping[books.id] : 'none'
})
// Check if users changes searchResult
if(this.state.searchResult !== searchResult) {
this.setState({searchResult})
}
} else {
this.setState({searchResults: []})
}
}).catch( e => console.log(e))
EDIT ADDED A third try:
onSearch = debounce((query) => {
console.log("In onSearch()")
BooksAPI.search(query)
.then((searchResult) => {
console.log(searchResult)
console.log("In onSearch() first .then")
if(!Array.isArray(searchResult)) {
throw new Error('Bad response')
}
})
.then((searchResult) => {
console.log("In onSearch() second .then")
if (typeof searchResult !== 'undefined' && Array.isArray(searchResult)) {
console.log("In onSearch() second .then if statement")
searchResult.map((books) => {
books.shelf = this.state.bookShelfMapping[books.id] ?
this.state.bookShelfMapping[books.id] : 'none'
})
this.setState({searchResult})
}
})
.catch( e => {
console.log("in onSearch .catch")
console.log(e)
})
console.log(this.state.searchResult)
}, 300)
So as shown above a BooksAPI is called with a query ( retrieved from an input) this returns the promise I want to reject undefined objects from.
Example of working object from postman
{
"books": [
{
"title": "Taijiquan Classics",
"subtitle": "An Annotated Translation",
"authors": [
"Barbara Davis",
"Weiming Chen"
],
"publisher": "North Atlantic Books",
"publishedDate": "2004",
"description": "Along with Chinese art, medicine, and philosophy, taijiquan has left the confines of its original culture, and offers health, relaxation, and a method of self-defense to people around the globe. Using the early texts now known as The Taijiquan Classics which have served as a touchstone for t’ai chi practitioners for 150 years, this book explores the fundamental ideas and what they mean to practitioners, students, and scholars. It also incorporates newly discovered sources that address the history of taijiquan and newly translated commentaries by Chen Weiming.",
"industryIdentifiers": [
{
"type": "ISBN_10",
"identifier": "1556434316"
},
{
"type": "ISBN_13",
"identifier": "9781556434310"
}
],
"readingModes": {
"text": false,
"image": false
},
"pageCount": 212,
"printType": "BOOK",
"categories": [
"Health & Fitness"
],
"maturityRating": "NOT_MATURE",
"allowAnonLogging": false,
"contentVersion": "0.0.1.0.preview.0",
"panelizationSummary": {
"containsEpubBubbles": false,
"containsImageBubbles": false
},
"imageLinks": {
"smallThumbnail": "http://books.google.com/books/content?id=vOoexFHEKXgC&printsec=frontcover&img=1&zoom=5&edge=curl&source=gbs_api",
"thumbnail": "http://books.google.com/books/content?id=vOoexFHEKXgC&printsec=frontcover&img=1&zoom=1&edge=curl&source=gbs_api"
},
"language": "en",
"previewLink": "http://books.google.com/books?id=vOoexFHEKXgC&printsec=frontcover&dq=classics&hl=&cd=1&source=gbs_api",
"infoLink": "http://books.google.com/books?id=vOoexFHEKXgC&dq=classics&hl=&source=gbs_api",
"canonicalVolumeLink": "https://books.google.com/books/about/Taijiquan_Classics.html?hl=&id=vOoexFHEKXgC",
"id": "vOoexFHEKXgC",
"shelf": "none"
}
]
}
Example from rejected
{
"books": {
"error": "empty query",
"items": []
}
}
Edit : -----
BooksAPI.search(query)
.then((searchResult) => {
console.log(searchResult)
Gives me a response first of (first millisecounds)
[]
length: 0
__proto__: Array(0)
Followed by a
(20) [Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object]
0
:
Object
allowAnonLogging
:
false
authors
:
Array(2)
averageRating
:
4
canonicalVolumeLink
:
"https://books.google.com/books/about/Best_Android_Apps.html?hl=&id=bUybAgAAQBAJ"
categories
:
Array(1)
contentVersion
:
"preview-1.0.0"
description
:
"Contains descriptions of over two hundred recommended applications and games for android/mobile devices, including apps for business, communication, lifestyle, entertainment, utility/tool, and reference."
id
:
"bUybAgAAQBAJ"
imageLinks
:
Object
industryIdentifiers
:
Array(2)
infoLink
:
"http://books.google.com/books?id=bUybAgAAQBAJ&dq=android&hl=&source=gbs_api"
language
:
"en"
maturityRating
:
"NOT_MATURE"
pageCount
:
240
previewLink
:
"http://books.google.com/books?id=bUybAgAAQBAJ&dq=android&hl=&cd=1&source=gbs_api"
printType
:
"BOOK"
publishedDate
:
"2010-04-27"
publisher
:
""O'Reilly Media, Inc.""
ratingsCount
:
3
readingModes
:
Object
shelf
:
"currentlyReading"
subtitle
:
"The Guide for Discriminating Downloaders"
title
:
"Best Android Apps"
__proto__
:
Object
Would appreciate some input on how to proceed.
Cheers!
For this purpos I ended up with a solution like this.
onSearch = debounce((query) => {
BooksAPI.search(query).then((searchResults) => {
if (!searchResults || searchResults.error){
this.setState({searchResult : []})
return searchResults
} else if (Array.isArray(searchResults)) {
searchResults.map((book) => {
book.shelf = this.state.bookShelfMapping[book.id] ?
this.state.bookShelfMapping[book.id] : 'none'
return searchResults
})
if (this.state.searchResults !== searchResults) {
this.setState({searchResult : searchResults})
}
} })
.catch(e => console.log(e))
}, 300)
One thing that I missed before was that the searchResult could return both as undefinedand as a .error
I have the following data where querying for the value of user/(userID)/(topic) returns null:
{
"topic" : {
"cafe" : {
"-KWoHbBXzWlD8aHBjg6Z" : {
"count" : 0,
"id" : "00qMXXXeYkbCbmjajoe4XZeIPuo1",
"text" : "A new message",
"time" : "2016-11-17T21:56:26.036Z"
},
"-KWpBzT83S_RB3wVwZ2u" : {
"count" : 3,
"id" : "zTSpSTqyRcaJf0MlYl15gBnvYdj2",
"text" : "Hello",
"time" : "2016-11-18T02:11:29.818Z"
}
},
"pomodoro" : {
"-KWoJhC9V1mLznt7jGwF" : {
"count" : 3,
"id" : "00qMXXXeYkbCbmjajoe4XZeIPuo1",
"text" : "Tomato! #tomato",
"time" : "2016-11-17T22:05:34.933Z"
}
}
},
"user" : {
"00qMXXXeYkbCbmjajoe4XZeIPuo1" : {
"pomodoro" : "2016-11-18T14:20:32.800Z"
},
"zTSpSTqyRcaJf0MlYl15gBnvYdj2" : {
"cafe" : "2016-11-18T14:24:32.968Z"
}
}
}
Here is the relevant code:
// inside the constructor
this.database = firebase.database().ref()
//relevant function
databaseListenerOn = () => {
// does not work correctly
let userPath = `user/${this.props.userID}/${this.state.topic}`
this.database.child(userPath).on('value', data => {
this.setState({time: data.val()})
console.log(data.key)
console.log(data.val())
})
//works correctly
let messagePath = `topic/${this.state.topic}`
this.database.child(messagePath).on('value', data => {
let messages = []
data.forEach(message => {
messages.push({
count: message.val().count,
id: message.val().id,
key: message.key,
text: message.val().text,
time: message.val().time
})
})
this.setState({messages: this.state.messages.cloneWithRows(messages)})
})
}
logging data.key correctly returns 'cafe' or 'pomodoro'. However, data.val() returns null and I am unable to access the string timestamp.
Using similar code, I am able to successfully access other data where data.val() returns objects so I'm confused as to what I could be doing wrong. I have looked at similar issues on this site and the Google Group, but have not found an answer for my particular situation.
The issue was that I didn't wait for Firebase to return the userID before running my query:
// this.props.userID is undefined
let userPath = `user/${this.props.userID}/${this.state.topic}`
The solution was to call this.databaseListenerOn() within onAuthStateChanged to ensure that it's called once I have the userID.