For ease of serialization/extensibility, maybe have an addressJSONB field with some uniform/minimal subset of fields to store, something like: {city,state,country}, making queries like undercurrent_dev=# select '{"city": null, "state": "NY"}' @> '{"state": "NY"}'::jsonb; possible. Would allow us to transmit a single Address field in the API, with some validation/transformation at the edge but nothing too crazy; unsure what to do about querying -- take ?address=someJsonString, or components?
This is a bit more flexible than storing in separate columns, and easier to work with on both the Haskell and JS sides (one datum/type,) but obviously quite a bit more wasteful as far as the database is concerned. As far as performance querying, the GIN index with jsonb_path_ops should do nicely, just like for emotions.
I like the Algolia API because it provides simple JSON (only when using their places.js) like:
{
"query": "manhattan",
"suggestion": {
"name": "Manhattan",
"administrative": "New York",
"county": "New York County",
"country": "United States of America",
"countryCode": "us",
"type": "city",
"latlng": {
"lat": 40.7834,
"lng": -73.9663
},
"highlight": {
"name": "<em>Manhattan</em>",
"administrative": "New York",
"country": "United States of America",
"county": "<em>Manhattan</em> (New York County)"
},
"value": "Manhattan, New York, United States of America"
}
}
NOTE: Algolia has both an API (with JS clients and regular REST endpoints,) and the places.js abstraction. We should settle on one of those, as the responses are different -- if we can make the places.js work, that'd be fantastic.
In either case the JS would have to do some manner of extraction -- alternatively, we could build a simple proxy in the backend? (Algolia at least permits that) -- in either case, we'd want city, administrative and country -- and the backend would be doing the parsing in the FromJSON instance at the edge (i.e. the frontend should feel free to just dump the raw JSON from the service back over to us.)
Do we need to store location data with dreams in addition to users? You could have a dream in a particular place (when traveling.)
Do we need to split into City, State and Country, and perhaps even further/more general? Maybe it can be a JSON representation or something? Perhaps "location" (address) can be a separate, "polymorphic" table for both users and dreams -- we'd have fewer unique ones if we split them that way, and we can index the FK.
If we start with just address, we can do a bunch of "grouping" analytics queries.
When creating a dream, we can receive Maybes for its location, and default to the user location if not present. Since many dreams/users can share this coarse-grained "address"/"location," we'll have a smallish table that we can insert... on conflict do nothing on.
If the app provides lat/long, even if we're not using PostGIS from day 1, we could store it alongside the "text" address for later analytics, in a separate, optional geog column? (Text or straight up GEOGRAPHY)
For location data, can the app provide lat/lon data? If it can, we could save those (alongside a normalized "text" representation) and do interesting geospatial queries with them![1][2] If not, we could instead install some sort of IP database for traditional geolocation. If we go with PostGIS (which heroku supports,) we can do fancy queries like "find dreams near this dream/user's location." or even "given a well known 'center' for a city/country," find all dreams within? -- though in that case it may be faster to just look at the text representation?
Maybe as a first approach, we just do the following
Receive raw lat/lng from the app, and we do a coarse reverse geocoding in the server upon creation of user/dream, and store the results. We could use the google API or this library for openstreetmap -- notice that it depends on wreq and lens, which I would probably also use for the google integration anyway.
Due to privacy concerns, users may elect to not allow location, and instead enter their location manually? In that case, maybe the app should be doing the reverse geocoding and sending the results our way? This simplifies things for the server.
As a compromise, the app could send either the lat/lng or the manually input data -- or just send us a place_id after autocompleting the location?
In fact, if we use that autocomplete service in the app, we could expect the place id (and as a fallback, manually input data,) and do the right thing in the server (use the places API to request more info on the place, and store said info, always assuming we're in the realm of Maybe) -- storing the place id (or lat/lng) in the user or dream, and the "normalized coarse address" in a separate table for efficiency in lookups.
The main drive here is querying. The simplest thing we could do is have city, state and country tables and FKs from dream and user to each of those. And then queries can be simple/quick searches such as where city = 42 and country = 66. We're wasting a bit of width, but gaining much in terms of querying. We can always also store a rawer geo column that we could use for any fancy geocoding (e.g. "all dreams within 100 miles of this one")
The app should be simple: send me lat/lng (from automatic location) plus one single "location" field that the server can validate. Both are optional. If both are provided obviously lat/lng takes precedence, we can use geolocation to go back and forth and save accordingly.
Should we store a normalized (queryable) JSONB column (of the Address type) and a "raw" address column? That's heavy and probably belongs elsewhere.
Should we copy the user's address to the dream's address? Or always join and coalesce? I'm leaning towards copying, for ease of querying and also because it may reflect historical reality better (the user may have moved, and their old dreams from an unknown location may not actually be from their current location)
For ease of serialization/extensibility, maybe have an
address
JSONB
field with some uniform/minimal subset of fields to store, something like:{city,state,country}
, making queries likeundercurrent_dev=# select '{"city": null, "state": "NY"}' @> '{"state": "NY"}'::jsonb;
possible. Would allow us to transmit a singleAddress
field in the API, with some validation/transformation at the edge but nothing too crazy; unsure what to do about querying -- take?address=someJsonString
, or components?This is a bit more flexible than storing in separate columns, and easier to work with on both the Haskell and JS sides (one datum/type,) but obviously quite a bit more wasteful as far as the database is concerned. As far as performance querying, the
GIN
index withjsonb_path_ops
should do nicely, just like foremotions
.Notes for the App
The app could use the Place Autocomplete API by Google, but I personally like the apparently simpler alternative presented by Algolia Places API
I like the Algolia API because it provides simple JSON (only when using their
places.js
) like:vs. the big ol' object that google returns. Also, algolia is cheaper -- $0.4 per 1000 requests than google autocomplete -- $17 per 1000 requests.
NOTE: Algolia has both an API (with JS clients and regular REST endpoints,) and the
places.js
abstraction. We should settle on one of those, as the responses are different -- if we can make theplaces.js
work, that'd be fantastic.places.js
library to look up a city: https://community.algolia.com/places/examples.html#city-searchAngular InstantSearch
library that works with Angular. There seems to be a widget for location searches (here's a rather contrived demo by them)Notes for the backend
city
,administrative
andcountry
-- and the backend would be doing the parsing in theFromJSON
instance at the edge (i.e. the frontend should feel free to just dump the raw JSON from the service back over to us.)dream
s in addition to users? You could have a dream in a particular place (when traveling.)City
,State
andCountry
, and perhaps even further/more general? Maybe it can be a JSON representation or something? Perhaps "location" (address) can be a separate, "polymorphic" table for both users and dreams -- we'd have fewer unique ones if we split them that way, and we can index the FK.address
, we can do a bunch of "grouping" analytics queries.Maybe
s for its location, and default to the user location if not present. Since many dreams/users can share this coarse-grained "address"/"location," we'll have a smallish table that we caninsert... on conflict do nothing
on.geog
column? (Text or straight upGEOGRAPHY
)Maybe as a first approach, we just do the following
wreq
andlens
, which I would probably also use for the google integration anyway.Maybe
) -- storing the place id (or lat/lng) in theuser
ordream
, and the "normalized coarse address" in a separate table for efficiency in lookups.city
,state
andcountry
tables and FKs fromdream
anduser
to each of those. And then queries can be simple/quick searches such aswhere city = 42 and country = 66
. We're wasting a bit of width, but gaining much in terms of querying. We can always also store a rawergeo
column that we could use for any fancy geocoding (e.g. "all dreams within 100 miles of this one")Address
type) and a "raw" address column? That's heavy and probably belongs elsewhere.[1] https://postgis.net/workshops/postgis-intro/geography.html [2] https://postgis.net/docs/ST_DWithin.html