@mountainpass/addressr 2.1.0 → 2.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client/elasticsearch.js +3 -0
- package/lib/service/address-service.js +62 -11
- package/lib/src/waycharter-server.js +298 -30
- package/lib/version.js +1 -1
- package/package.json +1 -1
|
@@ -9,6 +9,8 @@ exports.fetchGnafFile = fetchGnafFile;
|
|
|
9
9
|
exports.getAddress = getAddress;
|
|
10
10
|
exports.getAddresses = getAddresses;
|
|
11
11
|
exports.getLocality = getLocality;
|
|
12
|
+
exports.getPostcode = getPostcode;
|
|
13
|
+
exports.getState = getState;
|
|
12
14
|
exports.loadGnaf = loadGnaf;
|
|
13
15
|
exports.mapAddressDetails = mapAddressDetails;
|
|
14
16
|
exports.searchForAddress = searchForAddress;
|
|
@@ -735,7 +737,8 @@ async function loadAddressDetails(file, expectedCount, context, {
|
|
|
735
737
|
sla,
|
|
736
738
|
ssla,
|
|
737
739
|
structured,
|
|
738
|
-
confidence: structured.structured.confidence
|
|
740
|
+
confidence: structured.structured.confidence,
|
|
741
|
+
locality_pid: row.LOCALITY_PID
|
|
739
742
|
});
|
|
740
743
|
}
|
|
741
744
|
if (indexingBody.length > 0) {
|
|
@@ -934,20 +937,29 @@ async function getLocality(pid) {
|
|
|
934
937
|
return resp;
|
|
935
938
|
}
|
|
936
939
|
async function searchForPostcode(searchString) {
|
|
940
|
+
const query = searchString && searchString.length > 0 ? {
|
|
941
|
+
bool: {
|
|
942
|
+
filter: [{
|
|
943
|
+
prefix: {
|
|
944
|
+
postcodes: searchString
|
|
945
|
+
}
|
|
946
|
+
}]
|
|
947
|
+
}
|
|
948
|
+
} : {
|
|
949
|
+
bool: {
|
|
950
|
+
filter: [{
|
|
951
|
+
exists: {
|
|
952
|
+
field: 'postcodes'
|
|
953
|
+
}
|
|
954
|
+
}]
|
|
955
|
+
}
|
|
956
|
+
};
|
|
937
957
|
const searchResp = await globalThis.esClient.search({
|
|
938
958
|
index: _elasticsearch.ES_LOCALITY_INDEX_NAME,
|
|
939
959
|
body: {
|
|
940
960
|
from: 0,
|
|
941
961
|
size: 0,
|
|
942
|
-
query
|
|
943
|
-
bool: {
|
|
944
|
-
filter: [{
|
|
945
|
-
prefix: {
|
|
946
|
-
postcodes: searchString
|
|
947
|
-
}
|
|
948
|
-
}]
|
|
949
|
-
}
|
|
950
|
-
},
|
|
962
|
+
query,
|
|
951
963
|
aggs: {
|
|
952
964
|
postcodes: {
|
|
953
965
|
terms: {
|
|
@@ -969,6 +981,43 @@ async function searchForPostcode(searchString) {
|
|
|
969
981
|
logger('postcode hits', JSON.stringify(searchResp.body.aggregations, undefined, 2));
|
|
970
982
|
return searchResp;
|
|
971
983
|
}
|
|
984
|
+
async function getPostcode(postcode) {
|
|
985
|
+
const searchResp = await globalThis.esClient.search({
|
|
986
|
+
index: _elasticsearch.ES_LOCALITY_INDEX_NAME,
|
|
987
|
+
body: {
|
|
988
|
+
size: 100,
|
|
989
|
+
query: {
|
|
990
|
+
term: {
|
|
991
|
+
postcodes: postcode
|
|
992
|
+
}
|
|
993
|
+
},
|
|
994
|
+
_source: ['locality_name', 'locality_pid']
|
|
995
|
+
}
|
|
996
|
+
});
|
|
997
|
+
return searchResp;
|
|
998
|
+
}
|
|
999
|
+
async function getState(abbreviation) {
|
|
1000
|
+
const searchResp = await globalThis.esClient.search({
|
|
1001
|
+
index: _elasticsearch.ES_LOCALITY_INDEX_NAME,
|
|
1002
|
+
body: {
|
|
1003
|
+
size: 0,
|
|
1004
|
+
query: {
|
|
1005
|
+
term: {
|
|
1006
|
+
state_abbreviation: abbreviation.toUpperCase()
|
|
1007
|
+
}
|
|
1008
|
+
},
|
|
1009
|
+
aggs: {
|
|
1010
|
+
state_name: {
|
|
1011
|
+
terms: {
|
|
1012
|
+
field: 'state_name',
|
|
1013
|
+
size: 1
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
});
|
|
1019
|
+
return searchResp;
|
|
1020
|
+
}
|
|
972
1021
|
async function searchForState(searchString) {
|
|
973
1022
|
const query = searchString ? {
|
|
974
1023
|
bool: {
|
|
@@ -1532,10 +1581,12 @@ async function getAddress(addressId) {
|
|
|
1532
1581
|
});
|
|
1533
1582
|
// TODO: store hash in address
|
|
1534
1583
|
const hash = _nodeCrypto.default.createHash('md5').update(JSON.stringify(json)).digest('hex');
|
|
1584
|
+
const localityPid = jsonX.body._source.locality_pid;
|
|
1535
1585
|
return {
|
|
1536
1586
|
link,
|
|
1537
1587
|
json,
|
|
1538
|
-
hash
|
|
1588
|
+
hash,
|
|
1589
|
+
localityPid
|
|
1539
1590
|
};
|
|
1540
1591
|
} catch (error_) {
|
|
1541
1592
|
error('error getting record from elastic search', error_);
|
|
@@ -47,10 +47,37 @@ function startRest2Server() {
|
|
|
47
47
|
const {
|
|
48
48
|
json,
|
|
49
49
|
hash,
|
|
50
|
-
statusCode
|
|
50
|
+
statusCode,
|
|
51
|
+
localityPid
|
|
51
52
|
} = await (0, _addressService.getAddress)(pid);
|
|
53
|
+
const links = [];
|
|
54
|
+
if (localityPid) {
|
|
55
|
+
links.push({
|
|
56
|
+
rel: 'related',
|
|
57
|
+
uri: `/localities/${localityPid}`,
|
|
58
|
+
title: json.structured?.locality?.name || 'Locality'
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
if (json.structured) {
|
|
62
|
+
const s = json.structured;
|
|
63
|
+
if (s.postcode) {
|
|
64
|
+
links.push({
|
|
65
|
+
rel: 'related',
|
|
66
|
+
uri: `/postcodes/${s.postcode}`,
|
|
67
|
+
title: `Postcode ${s.postcode}`
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
if (s.state && s.state.abbreviation) {
|
|
71
|
+
links.push({
|
|
72
|
+
rel: 'related',
|
|
73
|
+
uri: `/states/${s.state.abbreviation}`,
|
|
74
|
+
title: s.state.name
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
52
78
|
return {
|
|
53
79
|
body: json,
|
|
80
|
+
links,
|
|
54
81
|
headers: {
|
|
55
82
|
etag: `"${_version.version}-${hash}"`,
|
|
56
83
|
'cache-control': `public, max-age=${ONE_WEEK}`
|
|
@@ -114,9 +141,25 @@ function startRest2Server() {
|
|
|
114
141
|
}) => {
|
|
115
142
|
const resp = await (0, _addressService.getLocality)(pid);
|
|
116
143
|
const source = resp.body._source;
|
|
144
|
+
const links = [];
|
|
145
|
+
if (source.primary_postcode) {
|
|
146
|
+
links.push({
|
|
147
|
+
rel: 'related',
|
|
148
|
+
uri: `/postcodes/${source.primary_postcode}`,
|
|
149
|
+
title: `Postcode ${source.primary_postcode}`
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
if (source.state_abbreviation) {
|
|
153
|
+
links.push({
|
|
154
|
+
rel: 'related',
|
|
155
|
+
uri: `/states/${source.state_abbreviation}`,
|
|
156
|
+
title: source.state_name
|
|
157
|
+
});
|
|
158
|
+
}
|
|
117
159
|
const hash = _nodeCrypto.default.createHash('md5').update(JSON.stringify(source)).digest('hex');
|
|
118
160
|
return {
|
|
119
161
|
body: source,
|
|
162
|
+
links,
|
|
120
163
|
headers: {
|
|
121
164
|
etag: `"${_version.version}-${hash}"`,
|
|
122
165
|
'cache-control': `public, max-age=${ONE_WEEK}`
|
|
@@ -177,38 +220,56 @@ function startRest2Server() {
|
|
|
177
220
|
}]
|
|
178
221
|
});
|
|
179
222
|
const postcodesType = waycharter.registerCollection({
|
|
223
|
+
itemPath: '/:postcode',
|
|
224
|
+
itemLoader: async ({
|
|
225
|
+
postcode
|
|
226
|
+
}) => {
|
|
227
|
+
const result = await (0, _addressService.getPostcode)(postcode);
|
|
228
|
+
const hits = result.body.hits.hits;
|
|
229
|
+
const localities = hits.map(h => ({
|
|
230
|
+
name: h._source.locality_name
|
|
231
|
+
}));
|
|
232
|
+
const links = hits.map(h => ({
|
|
233
|
+
rel: 'related',
|
|
234
|
+
uri: `/localities/${h._source.locality_pid}`,
|
|
235
|
+
title: h._source.locality_name
|
|
236
|
+
}));
|
|
237
|
+
const body = {
|
|
238
|
+
postcode,
|
|
239
|
+
localities
|
|
240
|
+
};
|
|
241
|
+
const hash = _nodeCrypto.default.createHash('md5').update(JSON.stringify(body)).digest('hex');
|
|
242
|
+
return {
|
|
243
|
+
body,
|
|
244
|
+
links,
|
|
245
|
+
headers: {
|
|
246
|
+
etag: `"${_version.version}-${hash}"`,
|
|
247
|
+
'cache-control': `public, max-age=${ONE_WEEK}`
|
|
248
|
+
},
|
|
249
|
+
status: 200
|
|
250
|
+
};
|
|
251
|
+
},
|
|
180
252
|
collectionPath: '/postcodes',
|
|
181
253
|
collectionLoader: async ({
|
|
182
254
|
q
|
|
183
255
|
}) => {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
};
|
|
202
|
-
} else {
|
|
203
|
-
return {
|
|
204
|
-
body: [],
|
|
205
|
-
hasMore: false,
|
|
206
|
-
headers: {
|
|
207
|
-
etag: `"${_version.version}"`,
|
|
208
|
-
'cache-control': `public, max-age=${ONE_WEEK}`
|
|
209
|
-
}
|
|
210
|
-
};
|
|
211
|
-
}
|
|
256
|
+
const result = await (0, _addressService.searchForPostcode)(q || '');
|
|
257
|
+
const buckets = result.body.aggregations.postcodes.buckets;
|
|
258
|
+
const body = buckets.map(bucket => ({
|
|
259
|
+
postcode: bucket.key,
|
|
260
|
+
localities: bucket.localities.buckets.map(l => ({
|
|
261
|
+
name: l.key
|
|
262
|
+
}))
|
|
263
|
+
}));
|
|
264
|
+
const responseHash = _nodeCrypto.default.createHash('md5').update(JSON.stringify(body)).digest('hex');
|
|
265
|
+
return {
|
|
266
|
+
body,
|
|
267
|
+
hasMore: false,
|
|
268
|
+
headers: {
|
|
269
|
+
etag: `"${_version.version}-${responseHash}"`,
|
|
270
|
+
'cache-control': `public, max-age=${ONE_WEEK}`
|
|
271
|
+
}
|
|
272
|
+
};
|
|
212
273
|
},
|
|
213
274
|
filters: [{
|
|
214
275
|
rel: 'https://addressr.io/rels/postcode-search',
|
|
@@ -216,11 +277,31 @@ function startRest2Server() {
|
|
|
216
277
|
}]
|
|
217
278
|
});
|
|
218
279
|
const statesType = waycharter.registerCollection({
|
|
280
|
+
itemPath: '/:abbreviation',
|
|
281
|
+
itemLoader: async ({
|
|
282
|
+
abbreviation
|
|
283
|
+
}) => {
|
|
284
|
+
const result = await (0, _addressService.getState)(abbreviation);
|
|
285
|
+
const stateName = result.body.aggregations.state_name.buckets[0]?.key || abbreviation.toUpperCase();
|
|
286
|
+
const body = {
|
|
287
|
+
abbreviation: abbreviation.toUpperCase(),
|
|
288
|
+
name: stateName
|
|
289
|
+
};
|
|
290
|
+
const hash = _nodeCrypto.default.createHash('md5').update(JSON.stringify(body)).digest('hex');
|
|
291
|
+
return {
|
|
292
|
+
body,
|
|
293
|
+
headers: {
|
|
294
|
+
etag: `"${_version.version}-${hash}"`,
|
|
295
|
+
'cache-control': `public, max-age=${ONE_WEEK}`
|
|
296
|
+
},
|
|
297
|
+
status: 200
|
|
298
|
+
};
|
|
299
|
+
},
|
|
219
300
|
collectionPath: '/states',
|
|
220
301
|
collectionLoader: async ({
|
|
221
302
|
q
|
|
222
303
|
}) => {
|
|
223
|
-
const result = await (0, _addressService.searchForState)(q
|
|
304
|
+
const result = await (0, _addressService.searchForState)(q || undefined);
|
|
224
305
|
const buckets = result.body.aggregations.states.buckets;
|
|
225
306
|
const body = buckets.map(bucket => ({
|
|
226
307
|
abbreviation: bucket.key,
|
|
@@ -256,12 +337,199 @@ function startRest2Server() {
|
|
|
256
337
|
};
|
|
257
338
|
}
|
|
258
339
|
});
|
|
340
|
+
waycharter.registerResourceType({
|
|
341
|
+
path: '/api-docs',
|
|
342
|
+
loader: async () => {
|
|
343
|
+
const spec = {
|
|
344
|
+
openapi: '3.0.3',
|
|
345
|
+
info: {
|
|
346
|
+
title: 'Addressr by Mountain Pass',
|
|
347
|
+
description: 'Free Australian Address Validation, Search and Autocomplete. This OpenAPI spec is supplementary — the HATEOAS link-driven API is the authoritative contract.',
|
|
348
|
+
version: _version.version
|
|
349
|
+
},
|
|
350
|
+
paths: {
|
|
351
|
+
'/addresses': {
|
|
352
|
+
get: {
|
|
353
|
+
summary: 'Search Addresses',
|
|
354
|
+
operationId: 'searchAddresses',
|
|
355
|
+
parameters: [{
|
|
356
|
+
name: 'q',
|
|
357
|
+
in: 'query',
|
|
358
|
+
required: true,
|
|
359
|
+
schema: {
|
|
360
|
+
type: 'string',
|
|
361
|
+
minLength: 3
|
|
362
|
+
},
|
|
363
|
+
description: 'Address search query'
|
|
364
|
+
}],
|
|
365
|
+
responses: {
|
|
366
|
+
200: {
|
|
367
|
+
description: 'List of matching addresses'
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
},
|
|
372
|
+
'/addresses/{pid}': {
|
|
373
|
+
get: {
|
|
374
|
+
summary: 'Get Address',
|
|
375
|
+
operationId: 'getAddress',
|
|
376
|
+
parameters: [{
|
|
377
|
+
name: 'pid',
|
|
378
|
+
in: 'path',
|
|
379
|
+
required: true,
|
|
380
|
+
schema: {
|
|
381
|
+
type: 'string'
|
|
382
|
+
},
|
|
383
|
+
description: 'Address persistent identifier'
|
|
384
|
+
}],
|
|
385
|
+
responses: {
|
|
386
|
+
200: {
|
|
387
|
+
description: 'Address details with structured data'
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
},
|
|
392
|
+
'/localities': {
|
|
393
|
+
get: {
|
|
394
|
+
summary: 'Search Localities',
|
|
395
|
+
operationId: 'searchLocalities',
|
|
396
|
+
parameters: [{
|
|
397
|
+
name: 'q',
|
|
398
|
+
in: 'query',
|
|
399
|
+
required: true,
|
|
400
|
+
schema: {
|
|
401
|
+
type: 'string',
|
|
402
|
+
minLength: 2
|
|
403
|
+
},
|
|
404
|
+
description: 'Locality/suburb name search query'
|
|
405
|
+
}],
|
|
406
|
+
responses: {
|
|
407
|
+
200: {
|
|
408
|
+
description: 'List of matching localities'
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
},
|
|
413
|
+
'/localities/{pid}': {
|
|
414
|
+
get: {
|
|
415
|
+
summary: 'Get Locality',
|
|
416
|
+
operationId: 'getLocality',
|
|
417
|
+
parameters: [{
|
|
418
|
+
name: 'pid',
|
|
419
|
+
in: 'path',
|
|
420
|
+
required: true,
|
|
421
|
+
schema: {
|
|
422
|
+
type: 'string'
|
|
423
|
+
},
|
|
424
|
+
description: 'Locality persistent identifier'
|
|
425
|
+
}],
|
|
426
|
+
responses: {
|
|
427
|
+
200: {
|
|
428
|
+
description: 'Locality details'
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
},
|
|
433
|
+
'/postcodes': {
|
|
434
|
+
get: {
|
|
435
|
+
summary: 'Search Postcodes',
|
|
436
|
+
operationId: 'searchPostcodes',
|
|
437
|
+
parameters: [{
|
|
438
|
+
name: 'q',
|
|
439
|
+
in: 'query',
|
|
440
|
+
required: false,
|
|
441
|
+
schema: {
|
|
442
|
+
type: 'string'
|
|
443
|
+
},
|
|
444
|
+
description: 'Postcode prefix search query. Omit to list all postcodes.'
|
|
445
|
+
}],
|
|
446
|
+
responses: {
|
|
447
|
+
200: {
|
|
448
|
+
description: 'List of matching postcodes with associated localities'
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
},
|
|
453
|
+
'/postcodes/{postcode}': {
|
|
454
|
+
get: {
|
|
455
|
+
summary: 'Get Postcode',
|
|
456
|
+
operationId: 'getPostcode',
|
|
457
|
+
parameters: [{
|
|
458
|
+
name: 'postcode',
|
|
459
|
+
in: 'path',
|
|
460
|
+
required: true,
|
|
461
|
+
schema: {
|
|
462
|
+
type: 'string'
|
|
463
|
+
},
|
|
464
|
+
description: 'Australian postcode'
|
|
465
|
+
}],
|
|
466
|
+
responses: {
|
|
467
|
+
200: {
|
|
468
|
+
description: 'Postcode details with associated localities'
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
},
|
|
473
|
+
'/states': {
|
|
474
|
+
get: {
|
|
475
|
+
summary: 'Search States',
|
|
476
|
+
operationId: 'searchStates',
|
|
477
|
+
parameters: [{
|
|
478
|
+
name: 'q',
|
|
479
|
+
in: 'query',
|
|
480
|
+
required: false,
|
|
481
|
+
schema: {
|
|
482
|
+
type: 'string'
|
|
483
|
+
},
|
|
484
|
+
description: 'State name or abbreviation search. Omit to list all states.'
|
|
485
|
+
}],
|
|
486
|
+
responses: {
|
|
487
|
+
200: {
|
|
488
|
+
description: 'List of matching states and territories'
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
},
|
|
493
|
+
'/states/{abbreviation}': {
|
|
494
|
+
get: {
|
|
495
|
+
summary: 'Get State',
|
|
496
|
+
operationId: 'getState',
|
|
497
|
+
parameters: [{
|
|
498
|
+
name: 'abbreviation',
|
|
499
|
+
in: 'path',
|
|
500
|
+
required: true,
|
|
501
|
+
schema: {
|
|
502
|
+
type: 'string'
|
|
503
|
+
},
|
|
504
|
+
description: 'State/territory abbreviation (e.g., NSW, VIC)'
|
|
505
|
+
}],
|
|
506
|
+
responses: {
|
|
507
|
+
200: {
|
|
508
|
+
description: 'State/territory details'
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
};
|
|
515
|
+
return {
|
|
516
|
+
body: spec,
|
|
517
|
+
headers: {
|
|
518
|
+
'cache-control': `public, max-age=${ONE_WEEK}`,
|
|
519
|
+
'content-type': 'application/json'
|
|
520
|
+
}
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
});
|
|
259
524
|
waycharter.registerResourceType({
|
|
260
525
|
path: '/',
|
|
261
526
|
loader: async () => {
|
|
262
527
|
return {
|
|
263
528
|
body: {},
|
|
264
529
|
links: [...addressesType.additionalPaths, ...localitiesType.additionalPaths, ...postcodesType.additionalPaths, ...statesType.additionalPaths, {
|
|
530
|
+
rel: 'https://addressr.io/rels/api-docs',
|
|
531
|
+
uri: '/api-docs'
|
|
532
|
+
}, {
|
|
265
533
|
rel: 'https://addressr.io/rels/health',
|
|
266
534
|
uri: '/health'
|
|
267
535
|
}],
|
package/lib/version.js
CHANGED