@mountainpass/addressr 2.0.3 → 2.1.0
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/ci/build.js +1 -1
- package/lib/ci/pipeline.js +4 -4
- package/lib/client/elasticsearch.js +138 -3
- package/lib/controllers/Addresses.js +11 -11
- package/lib/controllers/Default.js +5 -5
- package/lib/deploy/create-deployment-archive.js +5 -5
- package/lib/eslint.config.js +42 -11
- package/lib/loader.js +6 -6
- package/lib/server.js +12 -12
- package/lib/service/DefaultService.js +8 -4
- package/lib/service/address-service.js +205 -21
- package/lib/src/server2.js +13 -13
- package/lib/src/{waycharterServer.js → waycharter-server.js} +135 -1
- package/lib/swagger.js +8 -8
- package/lib/utils/stream-down.js +10 -12
- package/lib/version.js +1 -1
- package/package.json +1 -1
- package/scripts/check-version.js +1 -1
- package/lib/.claude/skills/c4/scripts/c4-generate.js +0 -82
- package/lib/.claude/skills/c4/scripts/c4-lib.js +0 -250
- package/lib/.claude/skills/c4-check/scripts/c4-check.js +0 -74
- package/lib/.claude/skills/wardley/owm-to-svg.js +0 -191
- /package/lib/service/{printVersion.js → print-version.js} +0 -0
- /package/lib/service/{setLinkOptions.js → set-link-options.js} +0 -0
|
@@ -8,9 +8,13 @@ exports.dropIndex = dropIndex;
|
|
|
8
8
|
exports.fetchGnafFile = fetchGnafFile;
|
|
9
9
|
exports.getAddress = getAddress;
|
|
10
10
|
exports.getAddresses = getAddresses;
|
|
11
|
+
exports.getLocality = getLocality;
|
|
11
12
|
exports.loadGnaf = loadGnaf;
|
|
12
13
|
exports.mapAddressDetails = mapAddressDetails;
|
|
13
14
|
exports.searchForAddress = searchForAddress;
|
|
15
|
+
exports.searchForLocality = searchForLocality;
|
|
16
|
+
exports.searchForPostcode = searchForPostcode;
|
|
17
|
+
exports.searchForState = searchForState;
|
|
14
18
|
exports.setAddresses = setAddresses;
|
|
15
19
|
exports.unzipFile = unzipFile;
|
|
16
20
|
var _debug = _interopRequireDefault(require("debug"));
|
|
@@ -23,7 +27,7 @@ var _nodeStream = _interopRequireDefault(require("node:stream"));
|
|
|
23
27
|
var _unzipStream = _interopRequireDefault(require("unzip-stream"));
|
|
24
28
|
var _elasticsearch = require("../client/elasticsearch");
|
|
25
29
|
var _streamDown = _interopRequireDefault(require("../utils/stream-down"));
|
|
26
|
-
var _setLinkOptions = require("./
|
|
30
|
+
var _setLinkOptions = require("./set-link-options");
|
|
27
31
|
var _keyv = require("keyv");
|
|
28
32
|
var _keyvFile = require("keyv-file");
|
|
29
33
|
var _nodeCrypto = _interopRequireDefault(require("node:crypto"));
|
|
@@ -54,10 +58,10 @@ const ONE_DAY_MS = 1000 * ONE_DAY_S;
|
|
|
54
58
|
const THIRTY_DAYS_MS = ONE_DAY_MS * 30;
|
|
55
59
|
const ES_INDEX_NAME = process.env.ES_INDEX_NAME || 'addressr';
|
|
56
60
|
async function dropIndex() {
|
|
57
|
-
await (0, _elasticsearch.dropIndex)(
|
|
61
|
+
await (0, _elasticsearch.dropIndex)(globalThis.esClient);
|
|
58
62
|
}
|
|
59
63
|
async function clearAddresses() {
|
|
60
|
-
await (0, _elasticsearch.initIndex)(
|
|
64
|
+
await (0, _elasticsearch.initIndex)(globalThis.esClient, true);
|
|
61
65
|
}
|
|
62
66
|
async function setAddresses(addr) {
|
|
63
67
|
await clearAddresses();
|
|
@@ -458,7 +462,7 @@ function mapGeo(geoSite, context, geoDefault) {
|
|
|
458
462
|
})
|
|
459
463
|
};
|
|
460
464
|
}) : [];
|
|
461
|
-
const
|
|
465
|
+
const defaults = geoDefault && !foundDefault ? geoDefault.map(geo => {
|
|
462
466
|
return {
|
|
463
467
|
default: true,
|
|
464
468
|
...(geo.GEOCODE_TYPE_CODE !== '' && {
|
|
@@ -475,7 +479,7 @@ function mapGeo(geoSite, context, geoDefault) {
|
|
|
475
479
|
})
|
|
476
480
|
};
|
|
477
481
|
}) : [];
|
|
478
|
-
return sites
|
|
482
|
+
return [...sites, ...defaults];
|
|
479
483
|
}
|
|
480
484
|
function mapToSla(fla) {
|
|
481
485
|
return fla.join(', ');
|
|
@@ -706,6 +710,13 @@ async function loadAddressDetails(file, expectedCount, context, {
|
|
|
706
710
|
}
|
|
707
711
|
const indexingBody = [];
|
|
708
712
|
for (const row of chunk.data) {
|
|
713
|
+
// Accumulate postcodes per locality for the locality index
|
|
714
|
+
if (context.postcodesByLocality && row.LOCALITY_PID && row.POSTCODE) {
|
|
715
|
+
if (!context.postcodesByLocality[row.LOCALITY_PID]) {
|
|
716
|
+
context.postcodesByLocality[row.LOCALITY_PID] = new Set();
|
|
717
|
+
}
|
|
718
|
+
context.postcodesByLocality[row.LOCALITY_PID].add(row.POSTCODE);
|
|
719
|
+
}
|
|
709
720
|
const item = mapAddressDetails(row, context, actualCount, expectedCount);
|
|
710
721
|
items.push(item);
|
|
711
722
|
actualCount += 1;
|
|
@@ -819,7 +830,7 @@ async function loadAddressDetails(file, expectedCount, context, {
|
|
|
819
830
|
}
|
|
820
831
|
async function searchForAddress(searchString, p, pageSize = PAGE_SIZE) {
|
|
821
832
|
// const searchString = '657 The Entrance Road'; //'2/25 TOTTERDE'; // 'UNT 2, BELCONNEN';
|
|
822
|
-
const searchResp = await
|
|
833
|
+
const searchResp = await globalThis.esClient.search({
|
|
823
834
|
index: ES_INDEX_NAME,
|
|
824
835
|
body: {
|
|
825
836
|
from: (p - 1 || 0) * pageSize,
|
|
@@ -875,6 +886,132 @@ async function searchForAddress(searchString, p, pageSize = PAGE_SIZE) {
|
|
|
875
886
|
logger('hits', JSON.stringify(searchResp.body.hits, undefined, 2));
|
|
876
887
|
return searchResp;
|
|
877
888
|
}
|
|
889
|
+
async function searchForLocality(searchString, p, pageSize = PAGE_SIZE) {
|
|
890
|
+
const searchResp = await globalThis.esClient.search({
|
|
891
|
+
index: _elasticsearch.ES_LOCALITY_INDEX_NAME,
|
|
892
|
+
body: {
|
|
893
|
+
from: (p - 1 || 0) * pageSize,
|
|
894
|
+
size: pageSize,
|
|
895
|
+
query: {
|
|
896
|
+
bool: {
|
|
897
|
+
...(searchString && {
|
|
898
|
+
should: [{
|
|
899
|
+
multi_match: {
|
|
900
|
+
fields: ['locality_name'],
|
|
901
|
+
query: searchString,
|
|
902
|
+
fuzziness: 'AUTO',
|
|
903
|
+
type: 'bool_prefix',
|
|
904
|
+
lenient: true,
|
|
905
|
+
operator: 'AND'
|
|
906
|
+
}
|
|
907
|
+
}, {
|
|
908
|
+
multi_match: {
|
|
909
|
+
fields: ['locality_name'],
|
|
910
|
+
query: searchString,
|
|
911
|
+
type: 'phrase_prefix',
|
|
912
|
+
lenient: true,
|
|
913
|
+
operator: 'AND'
|
|
914
|
+
}
|
|
915
|
+
}]
|
|
916
|
+
})
|
|
917
|
+
}
|
|
918
|
+
},
|
|
919
|
+
sort: ['_score', {
|
|
920
|
+
'locality_name.raw': {
|
|
921
|
+
order: 'asc'
|
|
922
|
+
}
|
|
923
|
+
}]
|
|
924
|
+
}
|
|
925
|
+
});
|
|
926
|
+
logger('locality hits', JSON.stringify(searchResp.body.hits, undefined, 2));
|
|
927
|
+
return searchResp;
|
|
928
|
+
}
|
|
929
|
+
async function getLocality(pid) {
|
|
930
|
+
const resp = await globalThis.esClient.get({
|
|
931
|
+
index: _elasticsearch.ES_LOCALITY_INDEX_NAME,
|
|
932
|
+
id: `/localities/${pid}`
|
|
933
|
+
});
|
|
934
|
+
return resp;
|
|
935
|
+
}
|
|
936
|
+
async function searchForPostcode(searchString) {
|
|
937
|
+
const searchResp = await globalThis.esClient.search({
|
|
938
|
+
index: _elasticsearch.ES_LOCALITY_INDEX_NAME,
|
|
939
|
+
body: {
|
|
940
|
+
from: 0,
|
|
941
|
+
size: 0,
|
|
942
|
+
query: {
|
|
943
|
+
bool: {
|
|
944
|
+
filter: [{
|
|
945
|
+
prefix: {
|
|
946
|
+
postcodes: searchString
|
|
947
|
+
}
|
|
948
|
+
}]
|
|
949
|
+
}
|
|
950
|
+
},
|
|
951
|
+
aggs: {
|
|
952
|
+
postcodes: {
|
|
953
|
+
terms: {
|
|
954
|
+
field: 'postcodes',
|
|
955
|
+
size: 20
|
|
956
|
+
},
|
|
957
|
+
aggs: {
|
|
958
|
+
localities: {
|
|
959
|
+
terms: {
|
|
960
|
+
field: 'locality_name.raw',
|
|
961
|
+
size: 100
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
});
|
|
969
|
+
logger('postcode hits', JSON.stringify(searchResp.body.aggregations, undefined, 2));
|
|
970
|
+
return searchResp;
|
|
971
|
+
}
|
|
972
|
+
async function searchForState(searchString) {
|
|
973
|
+
const query = searchString ? {
|
|
974
|
+
bool: {
|
|
975
|
+
should: [{
|
|
976
|
+
prefix: {
|
|
977
|
+
state_abbreviation: searchString.toUpperCase()
|
|
978
|
+
}
|
|
979
|
+
}, {
|
|
980
|
+
wildcard: {
|
|
981
|
+
state_name: `*${searchString.toUpperCase()}*`
|
|
982
|
+
}
|
|
983
|
+
}]
|
|
984
|
+
}
|
|
985
|
+
} : {
|
|
986
|
+
match_all: {}
|
|
987
|
+
};
|
|
988
|
+
const searchResp = await globalThis.esClient.search({
|
|
989
|
+
index: _elasticsearch.ES_LOCALITY_INDEX_NAME,
|
|
990
|
+
body: {
|
|
991
|
+
from: 0,
|
|
992
|
+
size: 0,
|
|
993
|
+
query,
|
|
994
|
+
aggs: {
|
|
995
|
+
states: {
|
|
996
|
+
terms: {
|
|
997
|
+
field: 'state_abbreviation',
|
|
998
|
+
size: 20
|
|
999
|
+
},
|
|
1000
|
+
aggs: {
|
|
1001
|
+
state_name: {
|
|
1002
|
+
terms: {
|
|
1003
|
+
field: 'state_name',
|
|
1004
|
+
size: 1
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
});
|
|
1012
|
+
logger('state hits', JSON.stringify(searchResp.body.aggregations, undefined, 2));
|
|
1013
|
+
return searchResp;
|
|
1014
|
+
}
|
|
878
1015
|
async function sendIndexRequest(indexingBody, initialBackoff = Number.parseInt(process.env.ADDRESSR_INDEX_BACKOFF || '30000'), {
|
|
879
1016
|
refresh = false
|
|
880
1017
|
} = {}) {
|
|
@@ -882,7 +1019,7 @@ async function sendIndexRequest(indexingBody, initialBackoff = Number.parseInt(p
|
|
|
882
1019
|
// eslint-disable-next-line no-constant-condition
|
|
883
1020
|
for (let count = 0; true; count++) {
|
|
884
1021
|
try {
|
|
885
|
-
const resp = await
|
|
1022
|
+
const resp = await globalThis.esClient.bulk({
|
|
886
1023
|
refresh,
|
|
887
1024
|
body: indexingBody,
|
|
888
1025
|
timeout: process.env.ADDRESSR_INDEX_TIMEOUT || '300s'
|
|
@@ -962,17 +1099,17 @@ function buildSynonyms(context) {
|
|
|
962
1099
|
const {
|
|
963
1100
|
readdir
|
|
964
1101
|
} = require('node:fs').promises;
|
|
965
|
-
async function getFiles(
|
|
966
|
-
const
|
|
967
|
-
logger(`reading ${
|
|
968
|
-
const dirents = await readdir(
|
|
1102
|
+
async function getFiles(currentDirectory, baseDirectory) {
|
|
1103
|
+
const directory = _nodePath.default.resolve(baseDirectory, currentDirectory);
|
|
1104
|
+
logger(`reading ${directory} (${currentDirectory} in ${baseDirectory})`);
|
|
1105
|
+
const dirents = await readdir(directory, {
|
|
969
1106
|
withFileTypes: true
|
|
970
1107
|
});
|
|
971
1108
|
const files = await Promise.all(dirents.map(dirent => {
|
|
972
|
-
const
|
|
973
|
-
return dirent.isDirectory() ? getFiles(
|
|
1109
|
+
const result = `${currentDirectory}/${dirent.name}`;
|
|
1110
|
+
return dirent.isDirectory() ? getFiles(result, baseDirectory) : result;
|
|
974
1111
|
}));
|
|
975
|
-
return
|
|
1112
|
+
return files.flat();
|
|
976
1113
|
}
|
|
977
1114
|
function countFileLines(filePath) {
|
|
978
1115
|
return new Promise((resolve, reject) => {
|
|
@@ -1024,11 +1161,13 @@ async function loadGnafData(directory, {
|
|
|
1024
1161
|
// throw new Error(`Cannot file '${countsFile}' or '${contentsFile}'`)
|
|
1025
1162
|
// }
|
|
1026
1163
|
}
|
|
1027
|
-
const loadContext = {
|
|
1164
|
+
const loadContext = {
|
|
1165
|
+
postcodesByLocality: {}
|
|
1166
|
+
};
|
|
1028
1167
|
await loadAuthFiles(files, directory, loadContext, filesCounts);
|
|
1029
1168
|
// loadContext now contains all the auth files, so we can build the synonyms
|
|
1030
1169
|
const synonyms = buildSynonyms(loadContext);
|
|
1031
|
-
await (0, _elasticsearch.initIndex)(
|
|
1170
|
+
await (0, _elasticsearch.initIndex)(globalThis.esClient, process.env.ES_CLEAR_INDEX || false, synonyms);
|
|
1032
1171
|
const addressDetailFiles = files.filter(f => f.match(/ADDRESS_DETAIL/) && f.match(/\/Standard\//));
|
|
1033
1172
|
logger('addressDetailFiles', addressDetailFiles);
|
|
1034
1173
|
for (const detailFile of addressDetailFiles) {
|
|
@@ -1074,6 +1213,51 @@ async function loadGnafData(directory, {
|
|
|
1074
1213
|
});
|
|
1075
1214
|
}
|
|
1076
1215
|
}
|
|
1216
|
+
|
|
1217
|
+
// Phase 2: Index localities (separate post-load phase)
|
|
1218
|
+
// Failures here must not affect address loading above
|
|
1219
|
+
try {
|
|
1220
|
+
await (0, _elasticsearch.initLocalityIndex)(globalThis.esClient, process.env.ES_CLEAR_INDEX || false, synonyms);
|
|
1221
|
+
const localityIndexingBody = [];
|
|
1222
|
+
for (const detailFile of addressDetailFiles) {
|
|
1223
|
+
const state = _nodePath.default.basename(detailFile, _nodePath.default.extname(detailFile)).replace(/_.*/, '');
|
|
1224
|
+
if (COVERED_STATES.length === 0 || COVERED_STATES.includes(state)) {
|
|
1225
|
+
const stateName = await loadState(files, directory, state);
|
|
1226
|
+
const localities = await loadLocality(files, directory, state);
|
|
1227
|
+
for (const l of localities) {
|
|
1228
|
+
if (l.LOCALITY_NAME === '') continue;
|
|
1229
|
+
localityIndexingBody.push({
|
|
1230
|
+
index: {
|
|
1231
|
+
_index: _elasticsearch.ES_LOCALITY_INDEX_NAME,
|
|
1232
|
+
_id: `/localities/${l.LOCALITY_PID}`
|
|
1233
|
+
}
|
|
1234
|
+
});
|
|
1235
|
+
// Derive postcodes: prefer accumulated from ADDRESS_DETAIL,
|
|
1236
|
+
// fall back to PRIMARY_POSTCODE from LOCALITY table
|
|
1237
|
+
const accumulatedPostcodes = loadContext.postcodesByLocality[l.LOCALITY_PID];
|
|
1238
|
+
const postcodes = accumulatedPostcodes ? [...accumulatedPostcodes] : l.PRIMARY_POSTCODE ? [l.PRIMARY_POSTCODE] : [];
|
|
1239
|
+
localityIndexingBody.push({
|
|
1240
|
+
locality_name: l.LOCALITY_NAME,
|
|
1241
|
+
locality_class_code: l.LOCALITY_CLASS_CODE,
|
|
1242
|
+
locality_class_name: localityClassCodeToName(l.LOCALITY_CLASS_CODE, loadContext),
|
|
1243
|
+
primary_postcode: postcodes[0] || '',
|
|
1244
|
+
postcodes,
|
|
1245
|
+
state_abbreviation: state,
|
|
1246
|
+
state_name: stateName,
|
|
1247
|
+
locality_pid: l.LOCALITY_PID
|
|
1248
|
+
});
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
if (localityIndexingBody.length > 0) {
|
|
1253
|
+
await sendIndexRequest(localityIndexingBody, undefined, {
|
|
1254
|
+
refresh: true
|
|
1255
|
+
});
|
|
1256
|
+
}
|
|
1257
|
+
logger('Locality indexing complete');
|
|
1258
|
+
} catch (error_) {
|
|
1259
|
+
error('Locality indexing failed (address loading unaffected):', error_);
|
|
1260
|
+
}
|
|
1077
1261
|
}
|
|
1078
1262
|
async function fileExists(countsFile) {
|
|
1079
1263
|
try {
|
|
@@ -1306,14 +1490,14 @@ async function loadGnaf({
|
|
|
1306
1490
|
if (contents.length === 0) {
|
|
1307
1491
|
throw new Error(`Data dir '${unzipped}' is empty`);
|
|
1308
1492
|
}
|
|
1309
|
-
const
|
|
1493
|
+
const gnafDirectory = await (0, _glob.glob)('**/G-NAF/', {
|
|
1310
1494
|
cwd: unzipped
|
|
1311
1495
|
});
|
|
1312
|
-
console.log(
|
|
1313
|
-
if (
|
|
1496
|
+
console.log(gnafDirectory);
|
|
1497
|
+
if (gnafDirectory.length === 0) {
|
|
1314
1498
|
throw new Error(`Cannot find 'G-NAF' directory in Data dir '${unzipped}'`);
|
|
1315
1499
|
}
|
|
1316
|
-
mainDirectory = _nodePath.default.dirname(`${unzipped}/${
|
|
1500
|
+
mainDirectory = _nodePath.default.dirname(`${unzipped}/${gnafDirectory[0].slice(0, -1)}`);
|
|
1317
1501
|
}
|
|
1318
1502
|
logger('Main Data dir', mainDirectory);
|
|
1319
1503
|
await loadGnafData(mainDirectory, {
|
|
@@ -1330,7 +1514,7 @@ async function loadGnaf({
|
|
|
1330
1514
|
**/
|
|
1331
1515
|
async function getAddress(addressId) {
|
|
1332
1516
|
try {
|
|
1333
|
-
const jsonX = await
|
|
1517
|
+
const jsonX = await globalThis.esClient.get({
|
|
1334
1518
|
index: ES_INDEX_NAME,
|
|
1335
1519
|
id: `/addresses/${addressId}`
|
|
1336
1520
|
});
|
package/lib/src/server2.js
CHANGED
|
@@ -2,20 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
var _debug = _interopRequireDefault(require("debug"));
|
|
4
4
|
var _elasticsearch = require("../client/elasticsearch");
|
|
5
|
-
var _printVersion = require("../service/
|
|
6
|
-
var _waycharterServer = require("./
|
|
5
|
+
var _printVersion = require("../service/print-version");
|
|
6
|
+
var _waycharterServer = require("./waycharter-server");
|
|
7
7
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
8
8
|
const logger = (0, _debug.default)('api');
|
|
9
|
-
(0, _waycharterServer.startRest2Server)().then(() => {
|
|
9
|
+
(0, _waycharterServer.startRest2Server)().then(async () => {
|
|
10
10
|
logger('connecting es client');
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
11
|
+
const esClient = await (0, _elasticsearch.esConnect)();
|
|
12
|
+
globalThis.esClient = esClient;
|
|
13
|
+
logger('es client connected');
|
|
14
|
+
console.log('=======================');
|
|
15
|
+
console.log('Addressr - API Server 2');
|
|
16
|
+
console.log('=======================');
|
|
17
|
+
(0, _printVersion.printVersion)();
|
|
18
|
+
}).catch(error => {
|
|
19
|
+
console.error('Failed to start server:', error);
|
|
20
|
+
throw error;
|
|
21
21
|
});
|
|
@@ -107,6 +107,140 @@ function startRest2Server() {
|
|
|
107
107
|
parameters: ['q']
|
|
108
108
|
}]
|
|
109
109
|
});
|
|
110
|
+
const localitiesType = waycharter.registerCollection({
|
|
111
|
+
itemPath: '/:pid',
|
|
112
|
+
itemLoader: async ({
|
|
113
|
+
pid
|
|
114
|
+
}) => {
|
|
115
|
+
const resp = await (0, _addressService.getLocality)(pid);
|
|
116
|
+
const source = resp.body._source;
|
|
117
|
+
const hash = _nodeCrypto.default.createHash('md5').update(JSON.stringify(source)).digest('hex');
|
|
118
|
+
return {
|
|
119
|
+
body: source,
|
|
120
|
+
headers: {
|
|
121
|
+
etag: `"${_version.version}-${hash}"`,
|
|
122
|
+
'cache-control': `public, max-age=${ONE_WEEK}`
|
|
123
|
+
},
|
|
124
|
+
status: 200
|
|
125
|
+
};
|
|
126
|
+
},
|
|
127
|
+
collectionPath: '/localities',
|
|
128
|
+
collectionLoader: async ({
|
|
129
|
+
page,
|
|
130
|
+
q
|
|
131
|
+
}) => {
|
|
132
|
+
if (q && q.length > 1) {
|
|
133
|
+
const foundLocalities = await (0, _addressService.searchForLocality)(q, page + 1, PAGE_SIZE);
|
|
134
|
+
const body = foundLocalities.body.hits.hits.map(h => {
|
|
135
|
+
return {
|
|
136
|
+
name: h._source.locality_name,
|
|
137
|
+
state: {
|
|
138
|
+
name: h._source.state_name,
|
|
139
|
+
abbreviation: h._source.state_abbreviation
|
|
140
|
+
},
|
|
141
|
+
...(h._source.locality_class_code && {
|
|
142
|
+
class: {
|
|
143
|
+
code: h._source.locality_class_code,
|
|
144
|
+
name: h._source.locality_class_name
|
|
145
|
+
}
|
|
146
|
+
}),
|
|
147
|
+
...(h._source.primary_postcode && {
|
|
148
|
+
postcode: h._source.primary_postcode
|
|
149
|
+
}),
|
|
150
|
+
score: h._score,
|
|
151
|
+
pid: h._id.replace('/localities/', '')
|
|
152
|
+
};
|
|
153
|
+
});
|
|
154
|
+
const responseHash = _nodeCrypto.default.createHash('md5').update(JSON.stringify(body)).digest('hex');
|
|
155
|
+
return {
|
|
156
|
+
body,
|
|
157
|
+
hasMore: page < foundLocalities.body.hits.total.value / PAGE_SIZE - 1,
|
|
158
|
+
headers: {
|
|
159
|
+
etag: `"${_version.version}-${responseHash}"`,
|
|
160
|
+
'cache-control': `public, max-age=${ONE_WEEK}`
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
} else {
|
|
164
|
+
return {
|
|
165
|
+
body: [],
|
|
166
|
+
hasMore: false,
|
|
167
|
+
headers: {
|
|
168
|
+
etag: `"${_version.version}"`,
|
|
169
|
+
'cache-control': `public, max-age=${ONE_WEEK}`
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
filters: [{
|
|
175
|
+
rel: 'https://addressr.io/rels/locality-search',
|
|
176
|
+
parameters: ['q']
|
|
177
|
+
}]
|
|
178
|
+
});
|
|
179
|
+
const postcodesType = waycharter.registerCollection({
|
|
180
|
+
collectionPath: '/postcodes',
|
|
181
|
+
collectionLoader: async ({
|
|
182
|
+
q
|
|
183
|
+
}) => {
|
|
184
|
+
if (q && q.length > 2) {
|
|
185
|
+
const result = await (0, _addressService.searchForPostcode)(q);
|
|
186
|
+
const buckets = result.body.aggregations.postcodes.buckets;
|
|
187
|
+
const body = buckets.map(bucket => ({
|
|
188
|
+
postcode: bucket.key,
|
|
189
|
+
localities: bucket.localities.buckets.map(l => ({
|
|
190
|
+
name: l.key
|
|
191
|
+
}))
|
|
192
|
+
}));
|
|
193
|
+
const responseHash = _nodeCrypto.default.createHash('md5').update(JSON.stringify(body)).digest('hex');
|
|
194
|
+
return {
|
|
195
|
+
body,
|
|
196
|
+
hasMore: false,
|
|
197
|
+
headers: {
|
|
198
|
+
etag: `"${_version.version}-${responseHash}"`,
|
|
199
|
+
'cache-control': `public, max-age=${ONE_WEEK}`
|
|
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
|
+
}
|
|
212
|
+
},
|
|
213
|
+
filters: [{
|
|
214
|
+
rel: 'https://addressr.io/rels/postcode-search',
|
|
215
|
+
parameters: ['q']
|
|
216
|
+
}]
|
|
217
|
+
});
|
|
218
|
+
const statesType = waycharter.registerCollection({
|
|
219
|
+
collectionPath: '/states',
|
|
220
|
+
collectionLoader: async ({
|
|
221
|
+
q
|
|
222
|
+
}) => {
|
|
223
|
+
const result = await (0, _addressService.searchForState)(q && q.length > 1 ? q : undefined);
|
|
224
|
+
const buckets = result.body.aggregations.states.buckets;
|
|
225
|
+
const body = buckets.map(bucket => ({
|
|
226
|
+
abbreviation: bucket.key,
|
|
227
|
+
name: bucket.state_name.buckets[0]?.key || bucket.key
|
|
228
|
+
}));
|
|
229
|
+
const responseHash = _nodeCrypto.default.createHash('md5').update(JSON.stringify(body)).digest('hex');
|
|
230
|
+
return {
|
|
231
|
+
body,
|
|
232
|
+
hasMore: false,
|
|
233
|
+
headers: {
|
|
234
|
+
etag: `"${_version.version}-${responseHash}"`,
|
|
235
|
+
'cache-control': `public, max-age=${ONE_WEEK}`
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
},
|
|
239
|
+
filters: [{
|
|
240
|
+
rel: 'https://addressr.io/rels/state-search',
|
|
241
|
+
parameters: ['q']
|
|
242
|
+
}]
|
|
243
|
+
});
|
|
110
244
|
waycharter.registerResourceType({
|
|
111
245
|
path: '/health',
|
|
112
246
|
loader: async () => {
|
|
@@ -127,7 +261,7 @@ function startRest2Server() {
|
|
|
127
261
|
loader: async () => {
|
|
128
262
|
return {
|
|
129
263
|
body: {},
|
|
130
|
-
links: [...addressesType.additionalPaths, {
|
|
264
|
+
links: [...addressesType.additionalPaths, ...localitiesType.additionalPaths, ...postcodesType.additionalPaths, ...statesType.additionalPaths, {
|
|
131
265
|
rel: 'https://addressr.io/rels/health',
|
|
132
266
|
uri: '/health'
|
|
133
267
|
}],
|
package/lib/swagger.js
CHANGED
|
@@ -5,7 +5,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.startServer = startServer;
|
|
7
7
|
exports.stopServer = stopServer;
|
|
8
|
-
exports.
|
|
8
|
+
exports.swaggerDocument = void 0;
|
|
9
9
|
exports.swaggerInit = swaggerInit;
|
|
10
10
|
var _debug = _interopRequireDefault(require("debug"));
|
|
11
11
|
var _express = _interopRequireDefault(require("express"));
|
|
@@ -32,12 +32,12 @@ var options = {
|
|
|
32
32
|
|
|
33
33
|
// The Swagger document (require it, build it programmatically, fetch it from a URL, ...)
|
|
34
34
|
var spec = (0, _nodeFs.readFileSync)(_nodePath.default.join(__dirname, 'api/swagger.yaml'), 'utf8');
|
|
35
|
-
var
|
|
36
|
-
|
|
35
|
+
var swaggerDocument = exports.swaggerDocument = (0, _jsYaml.load)(spec);
|
|
36
|
+
globalThis.swaggerDocument = swaggerDocument;
|
|
37
37
|
function swaggerInit() {
|
|
38
38
|
// Initialize the Swagger middleware
|
|
39
39
|
return new Promise(resolve => {
|
|
40
|
-
(0, _swaggerTools.initializeMiddleware)(
|
|
40
|
+
(0, _swaggerTools.initializeMiddleware)(swaggerDocument, function (middleware) {
|
|
41
41
|
// Interpret Swagger resources and attach metadata to request - must be first in swagger-tools middleware chain
|
|
42
42
|
const metaData = middleware.swaggerMetadata();
|
|
43
43
|
app.use(metaData);
|
|
@@ -55,7 +55,7 @@ function swaggerInit() {
|
|
|
55
55
|
// apiDocs: '/api-docs',
|
|
56
56
|
// swaggerUi: '/docs',
|
|
57
57
|
}));
|
|
58
|
-
app.use(function (error_, request,
|
|
58
|
+
app.use(function (error_, request, response, next) {
|
|
59
59
|
if (error_.failedValidation) {
|
|
60
60
|
// handle validation errror
|
|
61
61
|
const rehydratedError = Object.assign({}, error_);
|
|
@@ -70,13 +70,13 @@ function swaggerInit() {
|
|
|
70
70
|
delete rehydratedError.results;
|
|
71
71
|
}
|
|
72
72
|
error('error!!!', error_.message, JSON.stringify(rehydratedError, undefined, 2));
|
|
73
|
-
|
|
73
|
+
response.status(error_.code === 'SCHEMA_VALIDATION_FAILED' ? '500' : '400').json(rehydratedError);
|
|
74
74
|
} else {
|
|
75
75
|
next();
|
|
76
76
|
}
|
|
77
77
|
});
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
globalThis.swaggerApp = app;
|
|
79
|
+
globalThis.swaggerMiddleware = middleware;
|
|
80
80
|
resolve({
|
|
81
81
|
app,
|
|
82
82
|
middleware
|
package/lib/utils/stream-down.js
CHANGED
|
@@ -2,21 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
var _progress = _interopRequireDefault(require("progress"));
|
|
4
4
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
5
|
-
const {
|
|
6
|
-
parse
|
|
7
|
-
} = require('node:url');
|
|
8
5
|
const http = require('node:https');
|
|
9
6
|
const fs = require('node:fs');
|
|
10
7
|
const pathUtil = require('node:path');
|
|
11
|
-
module.exports = function (url, path, size) {
|
|
12
|
-
const uri =
|
|
8
|
+
module.exports = function streamDown(url, path, size) {
|
|
9
|
+
const uri = new URL(url);
|
|
13
10
|
if (!path) {
|
|
14
|
-
path = pathUtil.basename(uri.
|
|
11
|
+
path = pathUtil.basename(uri.pathname);
|
|
15
12
|
}
|
|
16
|
-
const file = fs.createWriteStream(path);
|
|
13
|
+
const file = fs.createWriteStream(path); // eslint-disable-line security/detect-non-literal-fs-filename -- path is internal
|
|
14
|
+
|
|
17
15
|
return new Promise(function (resolve, reject) {
|
|
18
|
-
http.get(uri.
|
|
19
|
-
const length =
|
|
16
|
+
http.get(uri.toString()).on('response', function (response) {
|
|
17
|
+
const length = response.headers['content-length'] ? Number.parseInt(response.headers['content-length'], 10) : size;
|
|
20
18
|
// let downloaded = 0;
|
|
21
19
|
// let percent = 0;
|
|
22
20
|
var bar = new _progress.default(' downloading [:bar] :rate/bps :percent :etas', {
|
|
@@ -25,7 +23,7 @@ module.exports = function (url, path, size) {
|
|
|
25
23
|
width: 20,
|
|
26
24
|
total: length
|
|
27
25
|
});
|
|
28
|
-
|
|
26
|
+
response.on('data', function (chunk) {
|
|
29
27
|
file.write(chunk);
|
|
30
28
|
// downloaded += chunk.length;
|
|
31
29
|
//percent = ((100.0 * downloaded) / len).toFixed(2);
|
|
@@ -39,8 +37,8 @@ module.exports = function (url, path, size) {
|
|
|
39
37
|
// );
|
|
40
38
|
}).on('end', function () {
|
|
41
39
|
file.end();
|
|
42
|
-
console.log(`\n${uri.
|
|
43
|
-
resolve(
|
|
40
|
+
console.log(`\n${uri.pathname} downloaded to: ${path}`);
|
|
41
|
+
resolve(response);
|
|
44
42
|
}).on('error', function (error) {
|
|
45
43
|
reject(error);
|
|
46
44
|
});
|
package/lib/version.js
CHANGED
package/package.json
CHANGED
package/scripts/check-version.js
CHANGED
|
@@ -7,5 +7,5 @@ if (!satisfies(process.version, version)) {
|
|
|
7
7
|
console.log(
|
|
8
8
|
`Required node version ${version} not satisfied with current version ${process.version}.`,
|
|
9
9
|
);
|
|
10
|
-
process.exit(1);
|
|
10
|
+
process.exit(1); // eslint-disable-line no-process-exit, n/no-process-exit -- postinstall version check
|
|
11
11
|
}
|