I would like to retrieve the subcollections by making my request with geofirestore, like so:
The id of each PRODUCTS corresponds to that of the user who created new products (for the moment there is only one).
That's my code right now:
import firestore from '#react-native-firebase/firestore';
import * as geofirestore from 'geofirestore';
const firestoreApp = firestore();
const GeoFirestore = geofirestore.initializeApp(firestoreApp);
const geocollection = GeoFirestore.collection('PRODUCTS');
const query = geocollection.limit(30).near({
center: new firestore.GeoPoint(coords.latitude, coords.longitude),
radius: 1000,
});
try {
query.onSnapshot((querySnapshot) => {
const productsQueried = querySnapshot.docs.reduce(
(result, documentSnapshot) => {
console.log(documentSnapshot);
if (documentSnapshot.id !== user.uid) {
result.push(documentSnapshot.data());
}
return result;
},
[]
);
setListOfProducts(productsQueried);
console.log(productsQueried);
setLoading(false);
});
} catch (error) {
console.log(error);
}
Of course, I can only find the geocollection, but cannot access the 'USER_PRODUCTS' collection inside.
{exists: true, id: "OUJ6r3aF9nVfgtfkQRES7kpYCko1", distance: 0, data: ƒ}
The final goal is to retrieve a list of products for each close customer and then sort so as not to retrieve that of the current user.
Do I necessarily have to make a second request (can I do it in one?) Or do I have to change the way I save the product lists of different users in firestore?
Related
ERROR
ERROR TypeError: (0, _database.where) is not a function. (In '(0, _database.where)('email', '==', email)', '(0, _database.where)' is undefined)
In here I am generating a uniqe perant in the database using uid and update some values in it, I am trying to do some filitration based on the email to know if users exist then update the values if not then generate a new user
import { ref, get, set, query, where} from 'firebase/database';
useEffect(() => {
const writeToDatabase = () => {
if (location && location.coords && UserDataFromGoogleAuth) {
const usersRef = ref(database, 'users');
const email = UserDataFromGoogleAuth.email;
if (email) {
const query = query(usersRef, where('email', '==', email));
get(query).then((snapshot) => {
const uuid = snapshot.exists() ? Object.keys(snapshot.val())[0] : uid();
const userRef = ref(database, `/users/${uuid}`);
const userData = {
id: uuid,
name: UserDataFromGoogleAuth.displayName,
email: email,
includedKids: 0,
isSubscribed: false,
long: location.coords.longitude,
lat: location.coords.latitude,
online: props.online,
profilePicture: UserDataFromGoogleAuth.photoURL,
};
set(userRef, userData);
}).catch(error => {
console.log(error);
});
}
}
};
writeToDatabase();
}, [UserDataFromGoogleAuth, location, props.online]);
database structure:
Database> users> {uid foreach user}> {email}
The where method is part of the Cloud Firestore API (firebase/firestore).
There is no direct equivalent in the Realtime Database API (firebase/database) that allows using a similar shorthand.
Instead, you invoke one of the many QueryConstraint returning methods:
endAt(), endBefore(), startAt(), startAfter(), limitToFirst(), limitToLast(), orderByChild(), orderByChild(), orderByKey(), orderByPriority(), orderByValue() or equalTo(). Take a look at QueryConstraint for links to the API reference for these methods and read over the documentation for Realtime Database: Sorting and filtering data.
The equivalent of
// firestore
const query = query(usersColRef, where('email', '==', email));
is
// database
const query = query(usersRef, orderByChild('email'), equalTo(email));
I'm trying to update a property value of a single document by sending a request to my NextJs API via fetch.
// Update items in state when the pending time in queue has passed, set allowed: true
items.map((item) => {
const itemDate = new Date(item.added)
const timeDiff = currentDate.getTime() - itemDate.getTime()
const dateDiff = timeDiff / (1000 * 3600 * 24)
if (dateDiff >= 7) {
const updateItem = async () => {
try {
// Data to update
const updatedItem = {
_id: item._id,
name: item.name,
price: item.price,
allowed: true
}
console.log(updatedItem)
await fetch ('/api/impulses', {
method: 'PATCH',
body: JSON.stringify(updatedItem)
})
} catch (error) {
alert('Failed to update items status')
}
}
updateItem()
}
})
API receives the data object as a whole and I am able to parse every piece of data I need for updating the MongoDb document from the req.body. However, when trying to use the item's _id (which is generated by MongoDb and values as _id: ObjectId('xx1234567890xx')) to filter the document I need to update, it seems to treat the ID differently.
I've tried to mess around with the format, forcing the object that gets sent to the API to include just the things I want to be updating (and the _id, of course) but still...
const jsonData = JSON.parse(req.body)
const { _id } = jsonData
// Fields to update
const { name, price, allowed } = jsonData
const data = {
name,
price,
allowed
}
const filter = {
_id: _id
}
const update = {
$set: data
}
const options = {
upsert: false
}
console.log("_id: ", filter) // returns { _id: 'the-correct-id-of-this-document' }
And finally, updating thedb.collection and returning responses:
await db.collection('impulses').updateOne(filter, update, options)
return res.json({
message: 'Impulse updated successfully',
success: true
})
So, as the _id clearly matches the document's id, I cannot figure out why it doesn't get updated? To confirm that this isn't an issue with database user privileges, if I set upsert: true in options, this creates a new document with the same _id and the updated data the request included...
Only difference to the other documents created through a submit form is that the id is in the following format: _id: 'xx1234567890xx' - so, comparing that to an ID with the ObjectId on front, it doesn't cause a conflict but I really don't get this behavior... After noticing this, I've also tried to include the 'ObjectId' in the ID reference in various ways, but it seemed like initiating a new ObjectId did exactly that - generate a new object ID which no longer referenced the original document.
Any ideas?
You compare an ObjectId object from _id with a string, this does not work.
Create proper filter object, e.g.
const filter = { _id: ObjectId(_id) }
or the other way around:
const filter = { $expr: {$eq: [{$toString: "$_id"}, _id] } }
but this will prevent to use the index on _id, so the first solution should be preferred.
I am building a social media app (very simple) an I want to store user's activity in firestore database. I have a collection of "users" and I keep user's id, user's username, user's profile pic there. But I dont think that user's activity should be stored there as well (correct me if I am wrong?)
So I created a new collection called UserActivity where I store user's activity. I wanted to store if a user has been searching on a profile so I do the following:
const logUserSearch = async (term) => {
await firebase
.firestore()
.collection("userActivity")
.doc(firebase.auth().currentUser.uid)
.collection("userSearch")
.add({
term: term,
date: firebase.firestore.FieldValue.serverTimestamp(),
})
};
I think the above query solves the problem with user's search term's. However I want to store if a user has visited a profile. So here is my question: what is the correct way to store if a user visited a profile? Should I add new subcollection "profileVisit", something like that:
const logProfileVisit = async (searchTerm, profileId) => {
await firebase
.firestore()
.collection("userActivity")
.doc(firebase.auth().currentUser.uid)
.collection("profileVisit")
.doc(profileId)
.add({
source: searchTerm,
date: firebase.firestore.FieldValue.serverTimestamp(),
})
};
But then how do I calculate which are the most "popular" profiles? Should I create my database in another way, like this:
const logProfileVisit = async (searchTerm, profileId) => {
await firebase
.firestore()
.collection("userActivity")
.doc(profileId)
.collection("profileVisit")
.add({
user: firebase.auth().currentUser.uid
source: searchTerm,
date: firebase.firestore.FieldValue.serverTimestamp(),
})
};
So that I can easily calculate which are the most "popular" profiles? What about the user case where I need to calculate "top 10 fan profiles" or something similar? I.e. How do I calculate who visited your profile most often?
A root level collection "userActivity" (or a sub-collection) should be enough. You can store the activity type as a field instead of sub-collections as shown below:
users -> {userId} -> userActivity -> {logId}
(col) (doc) (col) (doc)
But then how do I calculate which are the most "popular" profiles?
You can store a number field in that profile's document and whenever the logProfileVisit is called, increment that:
const usersCol = firebase.firestore().collection("users")
const logProfileVisit = async (searchTerm, profileId) => {
await Promise.all([
usersCol
.doc(currentUserId)
.collection("userActivity")
.add({
source: searchTerm,
date: firebase.firestore.FieldValue.serverTimestamp(),
type: "profileVisit"
}),
usersCol
.doc(profileUserId)
.update({
profileViews: firebase.firestore.FieldValue.increment(1),
})
])
};
You can also use batch writes while updating these fields so either both the operations fail or pass.
You can also use firestore -> audit logs -> pub/sub sink -> cloud function -> firestore.
I explain it a little more at https://justin.poehnelt.com/posts/tracking-firestore-user-activity/. Below is the function that listens to the Pub/Sub sink and writes back to Firestore.
import * as firebaseAdmin from "firebase-admin";
import * as functions from "firebase-functions";
export default functions.pubsub
.topic("firestore-activity")
.onPublish(async (message) => {
const { data } = message;
const { timestamp, protoPayload } = JSON.parse(
Buffer.from(data, "base64").toString()
);
const uid =
protoPayload.authenticationInfo.thirdPartyPrincipal.payload.user_id;
const writes = protoPayload.request.writes;
const activityRef = firebaseAdmin
.firestore()
.collection("users")
.doc(uid)
.collection("activity");
await Promise.all(
// eslint-disable-next-line #typescript-eslint/no-explicit-any
writes.map((write: any) => {
activityRef.add({ write, timestamp });
})
);
});
I then have a collection that looks like the following.
My Firestore sub collection names are of the format 'subcollection_name_yyyymmdd'. Whenever new documents are added, they are identified through the 'yyyymmdd' part of the subcollection name. I need to take Firestore exports for these subcollections incrementally on the 'yyyymmdd' values. Below is my cloud function taking full firestore export at the moment. Is there a way I can parameterize the 'collectionIds:' to take the subcollection names by passing the yyyymmdd part as a variable/parameter?
eg: something like collectionIds: ['subcollection_name_{$date}']?
const firestore = require('#google-cloud/firestore');
const client = new firestore.v1.FirestoreAdminClient();
const bucket = 'gs://BUCKET_NAME'
exports.scheduledFirestoreBackup = (event, context) => {
const databaseName = client.databasePath(
// process.env.GCLOUD_PROJECT,
"PROJECT_ID",
'(default)'
);
return client
.exportDocuments({
name: databaseName,
outputUriPrefix: bucket,
collectionIds: ['subcollection_name'],
})
.then(responses => {
const response = responses[0];
console.log(`Operation Name: ${response['name']}`);
return response;
})
.catch(err => {
console.error(err);
});
};
So i have 2 collections
1 collection is 'users'. There i have documents (objects) with property 'profile', that contains string. It's id of profile, that is stored in other collection 'roles' as document.
So i'm trying to get this data, but without success. Is there exist join method or something like that? Or i must use promise for getting data from collection roles, and then assign it with agent?
async componentDidMount() {
firebase
.firestore()
.collection('users')
.orderBy('lastName')
.onSnapshot(async snapshot => {
let changes = snapshot.docChanges()
const agents = this.state.agents
for (const change of changes) {
if (change.type === 'added') {
const agent = {
id: change.doc.id,
...change.doc.data()
}
await firebase
.firestore()
.collection('roles')
.doc(change.doc.data().profile).get().then( response => {
//when i get response i want to set for my object from above this val
agent['profile'] = response.data().profile
//after that i want to push my 'agent' object to array of 'agents'
agents.push(agent)
console.log(agent)
}
)
}
}
this.setState({
isLoading: false,
agents: agents
})
})
}
To do async operation on array of objects you can use promise.all, i have given a example below that is similar to your use case where multiple async operation has to be done
const all_past_deals = await Promise.all(past_deals.map(async (item, index) => {
const user = await Users.get_user_info(item.uid);
const dealDetails = await Deals.get_deal_by_ids(user.accepted_requests || []);
const test = await Users.get_user_info(dealDetails[0].uid);
return test
}))
}
This way you can get data from once api call and make other api call with the obtained data