Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cloud Function Cors Query Promise Javascript #154

Closed
NickCarducci opened this issue Jan 4, 2020 · 9 comments
Closed

Cloud Function Cors Query Promise Javascript #154

NickCarducci opened this issue Jan 4, 2020 · 9 comments
Labels
discussion question Further information is requested

Comments

@NickCarducci
Copy link

NickCarducci commented Jan 4, 2020

Hi Michael or community
This is a question to help others implement, and myself. No bug, of course.
[I've edited this with improvements I am sure of to keep short... getting closer...]

Anyway, here is a Google Cloud Function, It works until the query, req.body is fine with CORS
I have one item but can't get it from query when the res.send() is sent
[still working on this, no error is helpful yet, now testing how far down compiles...]
[now getting {"sendit":{"domain":{"domain":null,"_events":{},"_eventsCount":1,"members":[]}}}]

const functions = require("firebase-functions");
const admin = require("firebase-admin");
const GeoFirestore = require("geofirestore").GeoFirestore;
//const firestore = require('firebase/firestore');
const cors = require("cors")({
  origin: true,
  allowedHeaders: [
    "Access-Control-Allow-Origin",
    "Access-Control-Allow-Methods",
    "Content-Type",
    "Origin",
    "X-Requested-With",
    "Accept",
    "Access-Control-Allow-Headers"
  ],
  methods: ["POST", "OPTIONS"]
});
require('firebase/firestore');

//serviceAccount=yourServiceAccountKey.json
admin.initializeApp({
  credential: admin.credential.cert(serviceAccount),
  databaseURL: "your_firebaseio_domain"
})

const firestoreRef = admin.firestore();
const geofirestore = new GeoFirestore(firestoreRef);
const geocollection = geofirestore.collection("planner");

exports.chooseCity = functions.https.onRequest((req, res) => {
  // Google Cloud Function res.methods
  res.set("Access-Control-Allow-Headers", "Content-Type");
  res.set("Content-Type", "Application/JSON");
  // CORS-enabled req.methods, res.methods
  return cors(req, res, () => {
    res.set("Access-Control-Allow-Headers", "Content-Type");
    res.set("Content-Type", "Application/JSON");
    var origin = req.get("Origin");
    var allowedOrigins = [
      "https://yourdomain.tld"
    ];
    if (allowedOrigins.indexOf(origin) > -1) {
      // Origin Allowed!!
      res.set("Access-Control-Allow-Origin", origin);
      if (req.method === "OPTIONS") {
        // Method accepted for next request
        res.set("Access-Control-Allow-Methods", "POST");
        //SEND or end # option req.method
        return res.status(200).send({});
      } else {
        // After request req.method === 'OPTIONS'
        if (req.body) {
          const radius = req.body.distance;
          const center = new admin.firestore.GeoPoint(
            req.body.location[0],
            req.body.location[1]
          )

          const geoQuery = geocollection.near({ center, radius });

[Tried this]

          let results = []
// Remove documents when they fall out of the query
geoQuery.on('key_exited', ($key) => {
  const index = results.findIndex((place) => place.$key === $key);
  if (index >= 0) results.splice(index, 1);
});

// As documents come in, add the $key/id to them and push them into our results
geoQuery.on('key_entered', ($key, result) => {
  result.$key = $key;
  results.push(result);
});
          return res.status(200).send({results})

[Instead of this]

          geoQuery.get()
            .then(value => {
              // All GeoDocument returned by GeoQuery,
              //like the GeoDocument added above
              const sendit = value.docs
          res.status(200).send({sendit})

[End]

            })
            .catch(err => res.status(400).send(err))
        } else {
          res.status(200).send("no request body");
        }
      }
    } else {
      //Origin Bad!!
      //SEND or end
      return res.status(400).send("no access for this origin");
    }
  });
});

adding document, the geofirestore way, in a redux action

import { GeoFirestore } from "geofirestore";
export const createEvent = event => {
  return (dispatch, getState, { getFirebase, getFirestore }) => {
    const firestore = getFirestore();
    const geoFirestore = new GeoFirestore(firestore);
    const geocollection = geoFirestore.collection("planner");
    geocollection.add({
      name: "Geofirestore",
      score: 100,
      // The coordinates field must be a GeoPoint!
      coordinates: event.geopoint,
      title: event.title,
      body: event.body,
      chosenPhoto: event.chosenPhoto,
      date: event.date,
      createdAt: event.createdAt,
      updatedAt: event.updatedAt,
      address: event.address,
      location: event.location,
      city: event.city,
      etype: event.etype,
      geopoint: event.geopoint
    });
}}

Screen Shot 2020-01-04 at 10 59 29 AM

Screen Shot 2020-01-04 at 11 00 07 AM

@MichaelSolati
Copy link
Owner

Hey, so I want to make sure I understand the issue here... Are you looking for an example of how to provide an endpoint that can make a geoquery while using CORS?

@NickCarducci
Copy link
Author

NickCarducci commented Jan 16, 2020

CORS is required for a Google Cloud Function POST, so yes. I want to use Google Cloud Function to query geofirestore data... Oh let me be clear I have used the CORS before, this code works. the data is there... I've never queried data like this though, snapshots & such

@MichaelSolati
Copy link
Owner

So the CORS aspect works, but the query doesn't?

@MichaelSolati MichaelSolati added the question Further information is requested label Jan 16, 2020
@NickCarducci
Copy link
Author

For sure, near the query I commented [tried this] [instead of this] to show 2nd and 1st try using your query examples from other posts like these

@NickCarducci
Copy link
Author

NickCarducci commented Jan 20, 2020

So I'm just guessing but this may be because I need to have the instance run for longer than 8-10 minutes which is the longest a cloud function can run.
An alternative for Google users would be google app engine \ gcloud init \ gcloud app deploy --verbosity=debug, which I am just learning now to deploy my websocket server in another feature. maybe I can & should use that instead so I can use nodejs runtime. I forget why I stopped trying to load geofirestore directly to the front-end reactjs. I'll read-up again and update within a week

@MichaelSolati
Copy link
Owner

MichaelSolati commented Jan 21, 2020

Ok, so looking over your code I believe the issue has to do with how you're writing your then statements to resolve your promises. So here's an example function I wrote that looks similar to what you had (I just skipped the CORS aspect, but since you're comfortable writing that bit of code I'm sure you can re-add it).

const functions = require('firebase-functions');
const admin = require('firebase-admin');
const GeoFirestore = require('geofirestore').GeoFirestore;

admin.initializeApp();

const firestoreRef = admin.firestore();
const geofirestore = new GeoFirestore(firestoreRef);
const geocollection = geofirestore.collection('planner');

exports.chooseCity = functions.https.onRequest(async (request, response) => {
  if (request.method === 'OPTIONS') {
    response.set('Access-Control-Allow-Methods', 'POST');
    return response.status(200).send({});
  } else {
    const radius = request.body.distance;
    const center = new admin.firestore.GeoPoint(request.body.location[0], request.body.location[1]);

    // The data from a doc is returned as a function, so you just need to map and call the function
    const sendit = (await geocollection.near({ center, radius }).get()).docs.map((doc) => ({ ...doc, data: doc.data() }));

    return response.status(200).send({ sendit });
  }
 });

This should look similar to your code, I'm using async/await instead of getting into a promise soup of a nightmare.

The code is running on this endpoint https://us-central1-geofirestore-tests.cloudfunctions.net/chooseCity if you want to test it, it's just looking for a POST request with a application/json content type. Hopefully this helps!


Here's a sample request:

var request = require('request');
var options = {
  'method': 'POST',
  'url': 'https://us-central1-geofirestore-tests.cloudfunctions.net/chooseCity',
  'headers': {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({"distance":100,"location":[1,3]})

};
request(options, function (error, response) { 
  if (error) throw new Error(error);
  console.log(response.body);
});

It should return the following:

{
    "sendit": [
        {
            "exists": true,
            "id": "GtfRlw86OYRTawCmeBG1",
            "data": {
                "coordinates": {
                    "_latitude": 1,
                    "_longitude": 3
                }
            },
            "distance": 0
        },
        {
            "exists": true,
            "id": "OSttNfDZgaF8G1MzsSG4",
            "data": {
                "coordinates": {
                    "_latitude": 1,
                    "_longitude": 3
                }
            },
            "distance": 0
        },
        {
            "exists": true,
            "id": "dkk6QoHfojPdORitrFHn",
            "data": {
                "coordinates": {
                    "_latitude": 1,
                    "_longitude": 3
                }
            },
            "distance": 0
        },
        {
            "exists": true,
            "id": "krtqVQurpQnMXdPlA9MG",
            "data": {
                "coordinates": {
                    "_latitude": 1,
                    "_longitude": 3
                }
            },
            "distance": 0
        },
        {
            "exists": true,
            "id": "q01xXMOXLDkQiezUohnU",
            "data": {
                "coordinates": {
                    "_latitude": 1,
                    "_longitude": 3
                }
            },
            "distance": 0
        }
    ]
}

Updated this one last time to modify the sendit bit to call the doc data.

@NickCarducci
Copy link
Author

NickCarducci commented Jan 21, 2020

I'm trying to update firebase config for react-redux-firebase v3 & redux-firestore to test my functions I used a month ago before closing. Could also be an issue with using the same bucket I was messing about in with a websocket server... But I digress since you got a response & it works, we don't have to wait for anymore than the day so far to close this. Thanks for the work on this Michael, good job

@NickCarducci
Copy link
Author

Michael,

It works, but Can you also write a single request instead of the querySnapshot for us?

My reason: I am sure I’m not updating the geofirestore data every time I pan on the map, and the cloud function only runs when user changes city. I even made the variable radius static in the user-triggered function to test, but it still writes to the console every bit of panning on the map.
Thank you!

@MichaelSolati
Copy link
Owner

This is a single request, at least my sample. It uses the .get() method which returns a single query in the form of a Promise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
discussion question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants