MichaelSolati / geofirestore-js

Location-based querying and filtering using Firebase Firestore.
https://geofirestore.com
MIT License
505 stars 58 forks source link

Checking if field's key-value pair already exists in the collection, without directly accessing d field #184

Closed NickCarducci closed 4 years ago

NickCarducci commented 4 years ago

Hi @MichaelSolati you mentioned the max radius of 8587 KM in the issue posted before this one. I just upgraded to v4 from v3 and was using

    firebase
      .firestore()
      .collection("clubs")
      .where("d.name", "==", this.state.name)
      .onSnapshot(querySnapshot => {
        if (querySnapshot.empty) {
          geofirestore.add({
            name: this.state.name
          });
        } else {
          querySnapshot.docs(doc => {
            if (doc.exists) {
              this.setState({ pleaseUseAnotherName: true });
            }
          });
        }
      });

now using

    const firestore = firebase.firestore();
    const GeoFirestore = geofirestore.initializeApp(firestore);
    const geocollection = GeoFirestore.collection("clubs");
        const center = new firebase.firestore.GeoPoint(
          predictions[0].center[0],
          predictions[0].center[1]
        );
        geocollection
          .near({ center, radius: 8587 })

the first snippet was working until recently, although I understand you don't want us to use your service like that. What should I do to check that that field's key-value pair ({name:this.state.name}) doesn't exist in the collection?

I just realized I could make it the document id... however I actually have another not-inequality .where()/unique field's key-value pair: .where("communityName","==",this.props.community.communityName), where geocollection.add({communityName:this.state.communityName}) is also unique

MichaelSolati commented 4 years ago

As a quick question, just to confirm, did you update/migrate your database structure?

NickCarducci commented 4 years ago

Thankfully I'm still developing. so I just cleared .collection("clubs"), logged out with

await firebase
   .auth()
   .setPersistence(firebase.auth.Auth.Persistence.SESSION);

firebase
   .auth()
   .signOut()
   .then(() => {
      console.log("logged out");
   })
   .catch(err => {
      console.log(err);
   });

, and queried

        const center = new firebase.firestore.GeoPoint(
          predictions[0].center[0],
          predictions[0].center[1]
        );
        console.log(center);
        geocollection
          .near({ center, radius: 8587 })
          .where("city", "==", predictions[0].place_name)
          .onSnapshot(querySnapshot => {
            if (querySnapshot.empty) {
              console.log("check");
              this.handleSubmitClub();
            } else {
              console.log("city has");
              querySnapshot.docs.forEach(doc => {
                if (doc.exists) {
                  const foo = doc.data();
                  foo.id = doc.id;
                  console.log("city has");
                  this.setState({
                    pleaseNewClubname: predictions[0].place_name,
                    submitPaused: false
                  });
                }
              });
            }
          });

neither console.log("check"); nor console.log("city has"); get printed to the console

MichaelSolati commented 4 years ago

It's probably not logging cause the query is throwing an error because the data isn't indexed based on the new data structure. onSnapshot takes a second argument of an error handler which would probably be triggered/fired, you should add that to your query and see what's going on:

geocollection
    .near({ center, radius: 8587 })
    .where("city", "==", predictions[0].place_name)
    .onSnapshot(querySnapshot => {
        if (querySnapshot.empty) {
            console.log("check");
            this.handleSubmitClub();
        } else {
            console.log("city has");
            querySnapshot.docs.forEach(doc => {
                if (doc.exists) {
                    const foo = doc.data();
                    foo.id = doc.id;
                    console.log("city has");
                    this.setState({
                        pleaseNewClubname: predictions[0].place_name,
                        submitPaused: false
                    });
                }
            });
        }
    }, e => console.log(e));
NickCarducci commented 4 years ago

That's a good Idea, I just get

Screen Shot 2020-07-04 at 6 04 54 PM Screen Shot 2020-07-04 at 6 05 47 PM

There are currently no clubs in the .collection("clubs") - it should respond to querySnapshot.empty

MichaelSolati commented 4 years ago

Your read rule doesn't make sense, which I realize is my fault because of the README. If anyone is allowed to read it just set allow read: if true. Ignore the read rule the my docs, I'll need to fix that.

NickCarducci commented 4 years ago

Firebase support told me to be comfortable withallow read: if request.auth.uid !== null && my domain whitelisted in firestore instead of cross-collection doc().data if I want the user to be logged in && only use my javascript.

MichaelSolati commented 4 years ago

Sure, I mean to say whatever read rule you want is the right way to go. But you can't do a read rule on the request.resource.data, so that's my mistake for adding it to the readme. Let me know once you remove it if you can get it to work.

Also allow read: if request.auth.uid !== null will only allow people who are signed in to read data, if that's your intention then go for it.

NickCarducci commented 4 years ago

Oh it isn't, the photo has my real security rules... my last comment was just an example but... Anyway I took the extra rules off of read and it works to read and create. update (aka write) isn't working with these rules... Can't understand how I didn't try without these new rules... Anyway, I'm sure on update (aka write) is the time when these rules are necessary? Look forward to updated readme, thanks!

MichaelSolati commented 4 years ago

I'd say on create and update (write) the rules should be applied. And you're asking that the user be signed in, and that the write should at least have a g field and a coordinates field that is a latlng, and that the g field has a geohash field that is a string and a geopoint field that is a latlng. If any of those conditions is not met, then it will fail.

What I think is possible is that when you're doing an update and updating a document you're using the update method, which only updates the field(s) you include. So if you make an update that doesn't update the geopoint then none of those fields would be updated, and so they wouldn't exist in the request.resource.data and fails the rules.

However I need to see how you're updating your docs.

NickCarducci commented 4 years ago

That's right, I like to

.add({}).then(res=> {
        const resp = res.id;
        geocollection.doc(resp).update({ id: resp })
})

so can I just go without these rules? the conflict for update function on write security rule is tough. Maybe in your docs explicitly enter those coordinates is latlng & g.geohash is string for write, and explicitly state update without the rules to allow for single field updates. Yes this works perfectly. Thanks again Michael.

Screen Shot 2020-07-12 at 8 01 08 PM
NickCarducci commented 4 years ago

My original question is how can I query the entire collection? I want clubs to be created around the world with a unique name & communityId. I will just make 5 queries since it costs only upon getting data 40000km(world)/8587km(max)=5


    const center = new firebase.firestore.GeoPoint(0, 0);
    const center1 = new firebase.firestore.GeoPoint(0, 72);
    const center2 = new firebase.firestore.GeoPoint(0, 144);
    const center3 = new firebase.firestore.GeoPoint(0, -144);
    const center4 = new firebase.firestore.GeoPoint(0, -72);
    let empty = [];
      [center, center1, center2, center3, center4].map(x => {
        geocollection
          .near({ center: x, radius: 8587 })
          .where("communityId", "==", communityId)
          .where("message", "==", this.state.message)
          .get()
          .then(querySnapshot => {
            if (querySnapshot.empty) {
              console.log("empty");
              empty.push("empty");
            } else {
              querySnapshot.docs.forEach(doc => {
                if (doc.exists) {
                  const foo = doc.data();
                  foo.id = doc.id;
                  console.log("comm has");
                  this.setState({
                    pleaseNewClubname: thisone.message,
                    submitPaused: false
                  });
                }
              });
            }
          });
        if (empty.length === 5) {
          return this.check(this.state.place_name);
        } else return null;
      });