firestore < query mixed with orderBy Query - javascript

I am using firestore to query documents with compound queries, my code is:
let query = firestore.collection( 'market' )
let res = []
let newChkPt = false
// states
query = query.where('deListTime', '==', false)
query = query.where('tradeTime' , '==', false)
query = query.where('expirationTime', '>', Date.now())
// FIFO ordering
query = query.orderBy('originationTime', 'desc')
query = query.limit(3)
if (chkPt) {
await query
.startAfter(chkPt)
.get()
.then(snap => {
snap.forEach(doc => {
res.push(doc.data());
newChkPt = doc.data()['originationTime']
})
})
.catch(e => { console.log(e); return false})
} else {
await query
.get()
.then(snap => {
snap.forEach(doc => {
res.push(doc.data());
newChkPt = doc.data()['originationTime']
})
})
.catch(e => { console.log(e); return false})
}
In the console I have every combination composite query indices possible specified amongst the fields deListTime, tradeTime, expirationTime, and originationTime. And yet this compound query I specified refuse to fetch data as intended. If I comment out
query = query.orderBy('originationTime', 'desc')
I get the data, and if I comment the '>' out whilst leaving everything else un-commmented:
query = query.where('expirationTime', '>', now)
I also get the desired data. Is it the > that's messing it up?
The indexes:

Are you initializing 'now' somewhere? Did you mean Date.now()?

Related

Getting total Number of User Items in Firebase Realtime database in javascript

I have been trying to get amount of users that have same "referralId" in my firebase Realtime database,
users {
AY35bkc5cbkufik8gfe3vjbmgr {
email: james #gmail.com
verify: none
referralid: AY35ja35
}
B16fty9jDchfNgdx7Fxnjc5nxf5v {
email: mobilewisdom #gmail.com
verify: none
referralid: AY35ja35
}
}
How can I use JavaScript to count the amount of account with referralid:AY35ja35
I tried:
const query = ref.orderByChild('referralid').equalTo('AY35ja35');
query.get().then((results) => {
console.log(Object.keys(results.val()).length);
results.forEach((snapshot) => {
console.log(snapshot.key, snapshot.val());
});
});
But am getting this error in my console:
Uncaught TypeError: query.get is not a function
In v8/namespaced syntax that'd be:
const ref = firebase.database().ref('users');
const query = ref.orderByChild('referralid').equalTo('AY35ja35');
const results = await query.get();
console.log(Object.keys(results.val()).length);
results.forEach((snapshot) => {
console.log(snapshot.key, snapshot.val());
});
Or if you're in an environment where await doesn't work:
const ref = firebase.database().ref('users');
const query = ref.orderByChild('referralid').equalTo('AY35ja35');
query.get().then((results) => {
console.log(Object.keys(results.val()).length);
results.forEach((snapshot) => {
console.log(snapshot.key, snapshot.val());
});
});
In v9/modular syntax the equivalent would be:
const ref = ref(getDatabase(), 'users');
const query = query(ref, orderByChild('referralid'), equalTo('AY35ja35'));
const results = await get(query);
console.log(Object.keys(results.val()).length);
results.forEach((snapshot) => {
console.log(snapshot.key, snapshot.val());
});
Or if you're in an environment where await doesn't work:
const ref = ref(getDatabase(), 'users');
const query = query(ref, orderByChild('referralid'), equalTo('AY35ja35'));
get(query).then((results) => {
console.log(Object.keys(results.val()).length);
results.forEach((snapshot) => {
console.log(snapshot.key, snapshot.val());
});
});
Firstly you will have to run a query to get the data from firebase using either firebase package, axios or fetch
Your data is fetched in form of Object containing different objects. You can either user for .. in .. loop to iterate through the object.
var count = 0
for (const user in users) {
if (user.referralid==="AY35ja35") {
count++
}
}
Or use Object.values(object) function to get array of users. Now you can use a JavaScript array function such as map or filter.
by using map. you can do it like this:
var count=0
user.map(user=>{if(user.referralid==="AY35ja35"){count++})
or by using filter
const matchedUsers = user.filter(user=>(user.referralid==="AY35ja35"))
const count = matchedUsers.length
Hope this helps :)

Firebase firestore Pagination is returning duplicate results

I'm creating a social feed where I want to have infinite scrolling, using firebase pagination but the query is returning the same each time even when I have a lot of different data in my firestore database.
This is my initial query:
const getThreads = async () => {
try {
setLoading(true);
const ref = firestore()
.collection('Discover')
.orderBy('rank', 'desc')
.limit(10);
let docSnaps = await ref.get();
if (docSnaps.empty !== true) {
let docData = docSnaps.docs.map(document => {
return {
data: document.data(),
id: document.id,
};
});
setLastVisible(docData[docData.length - 1].id);
setThreads(docData); //Setting the data to display in UI
}
setLoading(false);
} catch (e) {
setLoading(false);
console.log(e);
Alert.alert(
'Oops! Looks like something went wrong',
'Please try again later',
[{text: 'OK'}],
);
}
};
As you can see each post/thread is being ordered by a rank field. And I'm setting the last visible as the documentId which is being used in the below query to get more posts/threads
async function getMoreThreads() {
try {
console.log('Getting More threads');
if (lastVisible !== null) {
setRefreshing(true);
const ref = firestore()
.collection('Discover')
.orderBy('rank', 'desc')
.startAfter(lastVisible)
.limit(10);
let docSnaps = await ref.get();
if (docSnaps.empty !== true) {
let docData = docSnaps.docs.map(document => {
return {
data: document.data(),
id: document.id,
};
});
console.log('DocData', docData.length);
setLastVisible(docData[docData.length - 1].id);
setThreads([...threads, ...docData]);
}
setRefreshing(false);
}
} catch (e) {
console.log('Error getting more', e);
Alert.alert(
'Oops! Looks like somthing went wrong',
'Please try again later',
[{text: 'OK'}],
);
}
}
My hypothesis of why this is happening is because I'm using documentIds to paginate and my document Ids are numeric long integer strings like this
1002103360646823936,1259597720752291841, 974895869194571776, etc.
Help would be very much appreciated.
Your hypothesis is correct. The field that you are using to paginate with startAfter(...) should match the field you are using in the orderBy(...) method - in this case, your startAfter(...) method is assuming that you are passing it a rank value.
You can pass in the DocumentSnapshot object in your startAfter(...) method instead:
const ref = firestore()
.collection('Discover')
.orderBy('rank', 'desc')
.startAfter(documentSnapshot) // document snapshot of the last element
.limit(10);

How to querying and aggregate multiple collection in Firebase

I need to retrieve a monthly work orders collection by project and list of users (in the month I could have some project's OdL hours not included in the users list and the users could have some OdL of other projects).
The OdL model contains the strings for project (projectCode) and user (userId).
I send to the service the dates in Timestamp format, the code of the project and an array with users list.
this.loadGantt(from: Timestamp, to: Timestamp, projectCode: string, users: string[]){...}
I tried to execute single queries, one for project code and one for single user.
These are the queries of the service:
this.docs = this.afs.collection<OdlModel>('odl', ref => {
return ref.where('projectCode', '==', project)
.where('date', '>=', from)
.where('date', '<=', to)
.orderBy('date', 'asc');
})
.snapshotChanges().pipe(map(coll => {
return coll.map(doc => ({ id: doc.payload.doc.id, ...doc.payload.doc.data()}));
}));
return this.docs;
this.docs = this.afs.collection<OdlModel>('odl', ref => {
return ref.where('userId', '==', user)
.where('date', '>=', from)
.where('date', '<=', to)
.orderBy('date', 'asc');
})
.snapshotChanges().pipe(map(coll => {
return coll.map(doc => ({ id: doc.payload.doc.id, ...doc.payload.doc.data()}));
}));
return this.docs;
This is the solution that I found:
loadGantt() {
const momentDate: Moment = moment(new Date());
this.firstDay = momentDate.startOf('month').toDate();
this.lastDay = momentDate.endOf('month').toDate();
const from = firebase.firestore.Timestamp.fromDate(this.firstDay);
const to = firebase.firestore.Timestamp.fromDate(this.lastDay);
this.odl = [];
const odlArray = [];
const a$ = this.odlService.getProjectOdlCollection(from, to, 'IGNC0954-IMI-19');
odlArray.push(a$);
this.user.forEach(u => {
const b$ = this.odlService.getUserOdlCollection(from, to, u);
odlArray.push(b$);
});
const result$ = combineLatest([odlArray]);
result$.subscribe(res => {
res.map(r => {
r.subscribe(odl => {
odl.forEach(o => {
const index = this.odl.findIndex(x => x.id === o.id);
if (index === -1) {
this.odl.push(o);
}
});
});
});
});
}
How to I get all data in a single Observable object?
Below is a snippet which shows how to combine two or more Firestore collections from AngularFire into a single Observable object.
Link here
Data Structure
const usersRef = this.afs.collection('users');
const fooPosts = usersRef.doc('userFoo').collection('posts').valueChanges();
const barPosts = usersRef.doc('userBar').collection('posts').valueChanges();
Solution
import { combineLatest } from 'rxjs/observable/combineLatest';
const combinedList = combineLatest<any[]>(fooPosts, barPosts).pipe(
map(arr => arr.reduce((acc, cur) => acc.concat(cur) ) ),
)
Also this example may also help with your issue.
Let me know if this was helpful.

How to use promise and loop over mongoose collection

I'm making chat inside my website. To store data I use Chat, User, Messages collections.
I want results to be in Array containing:
[{
username (another one, not me)
last update
last message
}]
In Chat model I have only chatid and array of two members, so I need to loop through User collection to get user name using user id from it. I want to save in array all names (in future I would also like to loop through messages to get latest messages for each chatid). Issue is that when I return chatsList it is empty. I think I need somehow to use Promise, but I'm not completely sure how it should work.
Chat.find({ members: userId })
.then(chats => {
let chatsList = [];
chats.forEach((chat, i) => {
let guestId = chat.members[1 - chat.members.indexOf(userId)];
User.findOne({ _id: guestId })
.then(guest => {
let chatObj = {};
name = guest.name;
chatsList.push(name);
console.log("chatsList", chatsList)
})
.catch(err => console.log("guest err =>", err))
})
return res.json(chatsList)
})
.catch(err => {
errors.books = "There are no chats for this user";
res.status(400).json(errors);
})
Indeed, Promise.all is what you are looking for:
Chat.find({ members: userId })
.then(chats => {
let userPromises = [];
chats.forEach((chat, i) => {
let guestId = chat.members[1 - chat.members.indexOf(userId)];
userPromises.push(User.findOne({ _id: guestId }));
});
return Promise.all(userPromises).then(guests => {
let chatsList = [];
guests.forEach(guest => {
chatsList.push(guest.name);
});
return res.json(chatsList);
});
});
});
although it would probably be better to do a single call to DB with a list of ids ($in query). Something like this:
Chat.find({ members: userId })
.then(chats => {
let ids = [];
chats.forEach((chat, i) => {
let guestId = chat.members[1 - chat.members.indexOf(userId)];
ids.push(guestId);
});
return User.find({_id: {$in: ids}}).then(guests => {
let chatsList = [];
guests.forEach(guest => {
chatsList.push(guest.name);
});
return res.json(chatsList);
});
});
});
You may want to additionally validate if every id had a corresponding guest.
You are running into concurrency issues. For example, running chats.forEach, and inside forEach running User.findOne().then: The return statement is already executed before the User.findOne() promise has resolved. That's why your list is empty.
You could get more readable and working code by using async/await:
async function getChatList() {
const chats = await Chat.find({members: userId});
const chatsList = [];
for (const chat of chats) {
let guestId = chat.members[1 - chat.members.indexOf(userId)];
const guest = await User.findOne({_id: guestId});
chatsList.push(guest.name);
}
return chatsList;
}
Then the code to actually send the chat list back to the user:
try {
return res.json(await getChatList());
} catch (err) {
// handle errors;
}
You can try this:
Chat.find({ members: userId }).then(chats => {
let guestHashMap = {};
chats.forEach(chat => {
let guestId = chat.members.filter(id => id != userId)[0];
// depending on if your ID is of type ObjectId('asdada')
// change it to guestHashMap[guestId.toString()] = true;
guestHashMap[guestId] = true;
})
return Promise.all(
// it is going to return unique guests
Object.keys(guestHashMap)
.map(guestId => {
// depending on if your ID is of type ObjectId('asdada')
// change it to User.findOne({ _id: guestHashMap[guestId] })
return User.findOne({ _id: guestId })
}))
})
.then(chats => {
console.log(chats.map(chat => chat.name))
res.json(chats.map(chat => chat.name))
})
.catch(err => {
errors.books = "There are no chats for this user";
res.status(400).json(errors);
})

Firestore .startAt is not working properly

infiniteHandler($state) {
var next = db
.collection("posts")
.orderBy("timestamp", "desc")
.startAfter(this.lastVisible)
.limit(3)
next.get().then(documentSnapshots => {
//Get the last visible document
// this.lastVisible =
// documentSnapshots.docs[documentSnapshots.docs.length - 1]
if (documentSnapshots.docs.length == 0) $state.complete()
else {
this.$store.commit(
"modules/posts/updateLastVisible",
documentSnapshots.docs[documentSnapshots.docs.length - 1].data()
.timestamp
)
}
documentSnapshots.forEach(doc => {
var post = doc.data()
post.docID = doc.id
this.$store.commit("modules/posts/pushPost", post)
})
$state.loaded()
})
}
This is my infinite loading handler which fetches new DB Entries once the end of the list is reached. Working fine so far.
This is my first fetch when the page gets loaded
async fetch({ store }){
if (store.state.modules.posts.posts.length < 5) {
let posts = []
await db
.collection("posts")
.orderBy("timestamp", "desc")
.limit(3)
.get()
.then(querySnapshot => {
store.commit(
"modules/posts/updateLastVisible",
querySnapshot.docs[2].data().timestamp
)
querySnapshot.forEach(doc => {
var x = doc.data()
x.docID = doc.id
posts.push(x)
})
})
store.commit("modules/posts/fetchedPosts", posts)
}
}
Basicly the problem is that I get the first 3 entries which I fetch on the page load again when I am fetching in my infinite Loading handler, which leads to the entries being displayed twice, this should not happen because this.lastVisible has the timestamp of the 3rd Element that I fetch on load, so those should be ignored.
After those elements everything is working fine with the .startAfter but the first 3 getting loaded again makes no sense.
I checked the store with the devtools and everything is working fine, this.lastVisible has the correct value when the infiniteLoading Handler is called the first time.
Bounty Edit:
Okay so I still have the problem I tried to play around with it a bit more to find the issue but its still occuring... I will set a bounty now and I hope anyone is able to help.
You do not actually need the first time fetch. The infiniteHandler will be called on its own when it gets mounted. In case if it does not call then you can try using the function
this.$refs.infiniteLoading.attemptLoad(); // 'infiniteLoading' is the component's ref property
That will actually invoke the infiniteHandler function for you.
EDIT: To check if one of the function is currently running. On the handler part
infiniteHandler($state) {
//Check if its currently loading
this.$nextTick(()=>{
if (this.isDocSnapShotLoading){
return;
}
});
//set as currently loading
this.isDocSnapShotLoading = true;
var next = db
.collection("posts")
.orderBy("timestamp", "desc")
.startAfter(this.lastVisible)
.limit(3)
next.get().then(documentSnapshots => {
//Get the last visible document
// this.lastVisible =
// documentSnapshots.docs[documentSnapshots.docs.length - 1]
if (documentSnapshots.docs.length == 0) $state.complete()
else {
this.$store.commit(
"modules/posts/updateLastVisible",
documentSnapshots.docs[documentSnapshots.docs.length - 1].data()
.timestamp
)
}
documentSnapshots.forEach(doc => {
var post = doc.data()
post.docID = doc.id
this.$store.commit("modules/posts/pushPost", post)
})
$state.loaded()
//set completed loading
this.isDocSnapShotLoading = false;
})
}
On the fetch part
async fetch({ store }){
if (store.state.modules.posts.posts.length < 5) {
//check if currently loading
this.$nextTick(()=>{
if (this.isDocSnapShotLoading){
return;
}
});
//set as currently loading
this.isDocSnapShotLoading = true;
let posts = []
await db
.collection("posts")
.orderBy("timestamp", "desc")
.limit(3)
.get()
.then(querySnapshot => {
store.commit(
"modules/posts/updateLastVisible",
querySnapshot.docs[2].data().timestamp
)
querySnapshot.forEach(doc => {
var x = doc.data()
x.docID = doc.id
posts.push(x)
})
//set as completed loading.
this.isDocSnapShotLoading = false;
})
store.commit("modules/posts/fetchedPosts", posts)
}
}
If you wanto be ignore first 3 posts in infiniteHandler then, you can make one post array where you store post id and check whether post id is already loaded or not. I know this should be solved using query but as temporary solution I hope it will work for you.
infiniteHandler($state) {
var next = db
.collection("posts")
.orderBy("timestamp", "desc")
.startAfter(this.lastVisible)
.limit(3)
next.get().then(documentSnapshots => {
//Get the last visible document
// this.lastVisible =
// documentSnapshots.docs[documentSnapshots.docs.length - 1]
if (documentSnapshots.docs.length == 0) $state.complete()
else {
this.$store.commit(
"modules/posts/updateLastVisible",
documentSnapshots.docs[documentSnapshots.docs.length - 1].data()
.timestamp
)
}
documentSnapshots.forEach(doc => {
var check = this.postIdArray.indexOf(doc.id);
if(check == -1){
var post = doc.data()
post.docID = doc.id
this.$store.commit("modules/posts/pushPost", post);
this.postIdArray[] = doc.id;
}
})
$state.loaded()
})
}
async fetch({ store }){
this.postIdArray = [];
if (store.state.modules.posts.posts.length < 5) {
let posts = []
await db
.collection("posts")
.orderBy("timestamp", "desc")
.limit(3)
.get()
.then(querySnapshot => {
store.commit(
"modules/posts/updateLastVisible",
querySnapshot.docs[2].data().timestamp
)
querySnapshot.forEach(doc => {
var x = doc.data()
x.docID = doc.id
this.postIdArray[] = doc.id;
posts.push(x)
})
})
store.commit("modules/posts/fetchedPosts", posts)
}
}
Ok so I found a temporary solution which works for now but is still not pretty:
documentSnapshots.forEach(doc => {
if (
doc.id !== this.posts[0].docID &&
doc.id !== this.posts[1].docID &&
doc.id !== this.posts[2].docID
) {
var post = doc.data()
post.docID = doc.id
this.$store.commit("modules/posts/pushPost", post)
}
})
I also try to make this more efficient with different solutions, thanks so far for your help.

Categories

Resources