From 856d9fd732b2669faa5d656feab60d2bca5ab1ba Mon Sep 17 00:00:00 2001 From: viatrix Date: Sun, 9 Aug 2020 22:52:48 +0300 Subject: [PATCH 1/3] Set preferredPlace based on locality from locale. Implements #89 --- lib/inaturalist_api.js | 37 +++++++++++++++++++++++++++++++++++-- lib/models/place.js | 17 +++++++++++++++++ package.json | 1 + schema/fixtures.js | 13 +++++++++++++ test/inaturalist_api.js | 34 ++++++++++++++++++++++++++++++++++ test/models/place.js | 21 +++++++++++++++++++++ 6 files changed, 121 insertions(+), 2 deletions(-) diff --git a/lib/inaturalist_api.js b/lib/inaturalist_api.js index 66576bee..62ff5dd5 100644 --- a/lib/inaturalist_api.js +++ b/lib/inaturalist_api.js @@ -1,3 +1,4 @@ +require( "intl" ); const _ = require( "lodash" ); const jwt = require( "jsonwebtoken" ); const fs = require( "fs" ); @@ -633,8 +634,40 @@ InaturalistAPI.lookupPlaceMiddleware = ( req, res, next ) => { }; InaturalistAPI.lookupPreferredPlaceMiddleware = ( req, res, next ) => { - InaturalistAPI.lookupInstanceMiddleware( req, res, next, - "preferred_place_id", Place.findByID, "preferredPlace" ); + if ( req.query.preferred_place_id ) { + return void InaturalistAPI.lookupInstanceMiddleware( req, res, next, + "preferred_place_id", Place.findByID, "preferredPlace" ); + } + // lookup by locality code from locale + let locale = req.userSession ? req.userSession.locale : null; + if ( req.query.locale ) { + ( { locale } = req.query ); + } + if ( locale ) { + let localeObj; + try { + localeObj = new Intl.Locale( locale ); + } catch ( err ) { + // continue if locale is invalid + return void next( ); + } + const localeRegionCode = localeObj.region; + if ( !localeRegionCode ) { + return void next( ); + } + // lookup the place by code and admin-level = country + return void Place.findByLocaleCode( localeRegionCode ).then( obj => { + // no error if none of the instances exist + // locale codes are not always same as country codes + if ( _.isEmpty( obj ) ) { + return void next( ); + } + req.inat = req.inat || { }; + req.inat.preferredPlace = obj; + return void next( ); + } ).catch( next ); + } + next( ); }; InaturalistAPI.lookupUnobservedByUserMiddleware = ( req, res, next ) => { diff --git a/lib/models/place.js b/lib/models/place.js index cd2b6830..bc484100 100644 --- a/lib/models/place.js +++ b/lib/models/place.js @@ -17,6 +17,23 @@ const Place = class Place extends Model { return response.hits.hits[0] ? response.hits.hits[0]._source : null; } + static async findByLocaleCode( code ) { + const { rows } = await pgClient.connection.query( "SELECT id, name, ancestry FROM " + + "places WHERE UPPER(code) = $1 AND admin_level = 0", [code] ); + if ( !rows.length ) { + return null; + } + const result = rows[0]; + // transform ancestry field to ancestor_place_ids + let ancestorPlaceIds = null; + if ( result.ancestry ) { + ancestorPlaceIds = result.ancestry ? result.ancestry.split( "/" ).map( str => parseInt( str, 10 ) ) : []; + ancestorPlaceIds.push( result.id ); + } + const response = { id: result.id, name: result.name, ancestor_place_ids: ancestorPlaceIds }; + return response; + } + static async assignToObject( object ) { const ids = _.keys( object ); if ( ids.length === 0 ) { return; } diff --git a/package.json b/package.json index f4aca105..205dd4aa 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "geoip-lite": "^1.3.4", "handlebars": "^4.5.3", "inaturalistjs": "github:inaturalist/inaturalistjs", + "intl": "^1.2.5", "jsonwebtoken": "^8.3.0", "lodash": "^4.17.15", "lodash.merge": "^4.6.2", diff --git a/schema/fixtures.js b/schema/fixtures.js index b521146b..b583e58f 100644 --- a/schema/fixtures.js +++ b/schema/fixtures.js @@ -1203,6 +1203,19 @@ "name": "a-place-in-a-place", "display_name": "A Place In A Place", "ancestry": "432/433" + }, + { + "id": 511, + "name": "locale-place", + "code": "LP", + "admin_level": 0, + "ancestry": "111" + }, + { + "id": 512, + "name": "locale-place-admin-level-1", + "code": "LPA", + "admin_level": 1 } ], "preferences": [ diff --git a/test/inaturalist_api.js b/test/inaturalist_api.js index c55dba82..f851a553 100644 --- a/test/inaturalist_api.js +++ b/test/inaturalist_api.js @@ -44,4 +44,38 @@ describe( "InaturalistAPI", ( ) => { } ); } ); } ); + + describe( "lookupPreferredPlaceMiddleware", ( ) => { + it( "looks up preferred_place by preferred_place_id", done => { + const req = { query: { preferred_place_id: 1, locale: "zh-LP" } }; + InaturalistAPI.lookupPreferredPlaceMiddleware( req, null, () => { + expect( req.inat.preferredPlace.id ).to.eq( 1 ); + done( ); + } ); + } ); + + it( "looks up preferred_place by locale", done => { + const req = { query: { locale: "zh-LP" } }; + InaturalistAPI.lookupPreferredPlaceMiddleware( req, null, () => { + expect( req.inat.preferredPlace.id ).to.eq( 511 ); + done( ); + } ); + } ); + + it( "does not set preferred_place if locale is malformed", done => { + const req = { query: { locale: "zh-LP-LP" } }; + InaturalistAPI.lookupPreferredPlaceMiddleware( req, null, () => { + expect( req.inat ).to.be.undefined; + done( ); + } ); + } ); + + it( "does not set preferred_place if country is not found", done => { + const req = { query: { locale: "zh-LPB" } }; + InaturalistAPI.lookupPreferredPlaceMiddleware( req, null, () => { + expect( req.inat ).to.be.undefined; + done( ); + } ); + } ); + } ); } ); diff --git a/test/models/place.js b/test/models/place.js index be4ec0d9..f4bef1b7 100644 --- a/test/models/place.js +++ b/test/models/place.js @@ -38,6 +38,27 @@ describe( "Place", ( ) => { } ); } ); + describe( "findByLocaleCode", ( ) => { + it( "returns a place given a code", async ( ) => { + const p = await Place.findByLocaleCode( "LP" ); + expect( p.id ).to.eq( 511 ); + expect( p.name ).to.eq( "locale-place" ); + expect( p.ancestor_place_ids.length ).to.eq( 2 ); + expect( p.ancestor_place_ids[0] ).to.eq( 111 ); + expect( p.ancestor_place_ids[1] ).to.eq( 511 ); + } ); + + it( "returns null if admin_level is not country", async ( ) => { + const p = await Place.findByLocaleCode( "LPA" ); + expect( p ).to.eq( null ); + } ); + + it( "returns null given an unknown code", async ( ) => { + const p = await Place.findByLocaleCode( "US" ); + expect( p ).to.eq( null ); + } ); + } ); + describe( "assignToObject", ( ) => { it( "assigns place instances to objects", done => { const o = { 1: { }, 123: { }, 432: { } }; From 3fb5be50ee70413296483e95568d3382650e7f06 Mon Sep 17 00:00:00 2001 From: viatrix Date: Tue, 11 Aug 2020 01:16:23 +0300 Subject: [PATCH 2/3] Add intl to package-lock.json. Implements #89 --- package-lock.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/package-lock.json b/package-lock.json index 26961316..a4e27c40 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2695,6 +2695,11 @@ } } }, + "intl": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/intl/-/intl-1.2.5.tgz", + "integrity": "sha1-giRKIZDE5Bn4Nx9ao02qNCDiq94=" + }, "invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", From 7ffebcc9adf324a528e37f1c470dad5330463b96 Mon Sep 17 00:00:00 2001 From: viatrix Date: Tue, 11 Aug 2020 15:41:30 +0300 Subject: [PATCH 3/3] Fixed tests to add flag inat.isV2. Implements #89 --- test/inaturalist_api.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/inaturalist_api.js b/test/inaturalist_api.js index f851a553..7faca408 100644 --- a/test/inaturalist_api.js +++ b/test/inaturalist_api.js @@ -47,7 +47,7 @@ describe( "InaturalistAPI", ( ) => { describe( "lookupPreferredPlaceMiddleware", ( ) => { it( "looks up preferred_place by preferred_place_id", done => { - const req = { query: { preferred_place_id: 1, locale: "zh-LP" } }; + const req = { query: { preferred_place_id: 1, locale: "zh-LP"}, inat: {} }; InaturalistAPI.lookupPreferredPlaceMiddleware( req, null, () => { expect( req.inat.preferredPlace.id ).to.eq( 1 ); done( ); @@ -55,7 +55,7 @@ describe( "InaturalistAPI", ( ) => { } ); it( "looks up preferred_place by locale", done => { - const req = { query: { locale: "zh-LP" } }; + const req = { query: { locale: "zh-LP" }, inat: {} }; InaturalistAPI.lookupPreferredPlaceMiddleware( req, null, () => { expect( req.inat.preferredPlace.id ).to.eq( 511 ); done( ); @@ -63,9 +63,9 @@ describe( "InaturalistAPI", ( ) => { } ); it( "does not set preferred_place if locale is malformed", done => { - const req = { query: { locale: "zh-LP-LP" } }; + const req = { query: { locale: "zh-LP-LP" }, inat: {} }; InaturalistAPI.lookupPreferredPlaceMiddleware( req, null, () => { - expect( req.inat ).to.be.undefined; + expect( req.inat.preferredPlace ).to.be.undefined; done( ); } ); } );