Geocode address with collection hooks on the server


#1

Hello everyone,
I am building a webapp that requires me to geocode an address into coordinates on the server.
After the offer is inserted into the collection, I have setup a collection-hook, that calls a method (offers.geocode) that should geocode the address into coordinates and insert them then into the collection. Unfortunately I cannot get it to work. Can someone help me? :slight_smile:
My schema:

/* eslint-disable consistent-return */

import { Mongo } from "meteor/mongo";
import SimpleSchema from "simpl-schema";

const Offers = new Mongo.Collection("offers");

Offers.allow({
  insert: () => false,
  update: () => false,
  remove: () => false,
});

Offers.deny({
  insert: () => true,
  update: () => true,
  remove: () => true,
});

Offers.schema = new SimpleSchema({
  address: Object,
  // [..]
  "address.street": {
    type: String,
    label: "Street and house number",
  },
  "address.city": {
    type: String,
    label: "City",
  },
  "address.zip": {
    type: Number,
    label: "Postal code",
    regEx: SimpleSchema.RegEx.ZipCode,
  },
  "address.country": {
    type: String,
    label: "Country",
  },
  "address.lat": {
    type: Number,
    label: "Latitude",
    optional: true,
  },
  "address.lon": {
    type: Number,
    label: "Longitude",
    optional: true,
  },
});

Offers.attachSchema(Offers.schema);

export default Offers;

The collection hook:

import Offers from "../Offers";

Offers.after.insert(((userId, offer) => {
  if (!offer.address.lat) {
    Meteor.call("offers.geocode", offer, offer.address);
  }
}));

My method looks like this:

import Meteor from "meteor/meteor";
import NodeGeocoder from "node-geocoder";
import { Offers } from "../Offers";

export default function (offer, address) {
  const geocoder = new NodeGeocoder({
    provider: "google",
    httpAdapter: "https",
    apiKey: Meteor.settings.private.GoogleMapsGeoCodingApiKey,
  });

  const result = geocoder.geocode(address.street + address.zip + address.city + address.country + address.state );
  Offers.update({
    _id: offer._id,
  }, {
    $set: {
      "offer.address.lat": result[0].latitude,
      "offer.address.lon": result[0].longitude,
    },
  }, {
    validate: false,
  });
}


#2

Have you checked that the method is called and returns the right value (sprinkle a few console.log()s around)?

The non-callback form of method calls on the server run “synchronously”, so should work as expected.


#3

Why you don’t try to fetch lat/lng befor insert? Insert + method call + fetch + update > fetch + insert

And looks like you wrong defined your method…


#4

I have defined my methods like this in another file. That makes it easier for me to write tests, so this shouldn’t be the actual problem :sweat_smile:

import { Meteor } from "meteor/meteor";
import rateLimit from "../../../modules/rate-limit";
import insert from "./insert";
import update from "./update";
import remove from "./remove";
import geocode from "./geocode";

Meteor.methods({
  "offers.insert": function offersInsert(offer) { // eslint-disable-line
    return insert(offer, this.userId);
  },
  "offers.update": function offersUpdate(offer) { // eslint-disable-line
    return update(offer);
  },
  "offers.remove": function offersRemove(offerId) { // eslint-disable-line
    return remove(offerId);
  },
  "offers.geocode": function offersGeocode(offer, address) { // eslint-disable-line
    return geocode(offer, address);
  },
});

rateLimit({
  methods: [
    "offers.insert",
    "offers.update",
    "offers.remove",
  ],
  limit: 5,
  timeRange: 1000,
});

Would you recommend to use the collection hooks for this (Offers.before.insert) or do you know, if Meteor provides something akin out of the box? :slight_smile:


#5

I’m prefer do not use hooks at all. I do it manually.
Or I do it even on client side (-:


#6

What do you insert manually? Coordinates? :slight_smile:
Client side is not an option for me…