From f230497e47a6262dd4b894eca282fc15e38b44fc Mon Sep 17 00:00:00 2001 From: Chris Blanchard Date: Mon, 26 Nov 2018 15:01:44 +0100 Subject: [PATCH 01/36] Cleanup models that inherit from attribute base --- app/models/ccg.js | 8 ++++---- app/models/constituency.js | 8 ++++---- app/models/county.js | 8 ++++---- app/models/district.js | 8 ++++---- app/models/parish.js | 8 ++++---- app/models/ward.js | 8 ++++---- 6 files changed, 24 insertions(+), 24 deletions(-) diff --git a/app/models/ccg.js b/app/models/ccg.js index 4a2c488a..b4eeb0cb 100644 --- a/app/models/ccg.js +++ b/app/models/ccg.js @@ -2,14 +2,14 @@ const TABLE_NAME = "ccgs"; -const util = require("util"); -const path = require("path"); -const AttributeBase = require(path.join(__dirname, "attribute_base.js")); +const { inherits } = require("util"); +const AttributeBase = require("./attribute_base.js"); function Model() { AttributeBase.call(this, TABLE_NAME); } -util.inherits(Model, AttributeBase); +inherits(Model, AttributeBase); module.exports = new Model(); + diff --git a/app/models/constituency.js b/app/models/constituency.js index e7ba9189..a1abfcbd 100644 --- a/app/models/constituency.js +++ b/app/models/constituency.js @@ -2,14 +2,14 @@ const TABLE_NAME = "constituencies"; -const util = require("util"); -const path = require("path"); -const AttributeBase = require(path.join(__dirname, "attribute_base.js")); +const { inherits } = require("util"); +const AttributeBase = require("./attribute_base.js"); function Model() { AttributeBase.call(this, TABLE_NAME); } -util.inherits(Model, AttributeBase); +inherits(Model, AttributeBase); module.exports = new Model(); + diff --git a/app/models/county.js b/app/models/county.js index 28b7a9ef..6f8d2f7d 100644 --- a/app/models/county.js +++ b/app/models/county.js @@ -2,14 +2,14 @@ const TABLE_NAME = "counties"; -const util = require("util"); -const path = require("path"); -const AttributeBase = require(path.join(__dirname, "attribute_base.js")); +const { inherits } = require("util"); +const AttributeBase = require("./attribute_base.js"); function Model() { AttributeBase.call(this, TABLE_NAME); } -util.inherits(Model, AttributeBase); +inherits(Model, AttributeBase); module.exports = new Model(); + diff --git a/app/models/district.js b/app/models/district.js index af6e769c..1dc8d14b 100644 --- a/app/models/district.js +++ b/app/models/district.js @@ -2,14 +2,14 @@ const TABLE_NAME = "districts"; -const util = require("util"); -const path = require("path"); -const AttributeBase = require(path.join(__dirname, "attribute_base.js")); +const { inherits } = require("util"); +const AttributeBase = require("./attribute_base.js"); function Model() { AttributeBase.call(this, TABLE_NAME); } -util.inherits(Model, AttributeBase); +inherits(Model, AttributeBase); module.exports = new Model(); + diff --git a/app/models/parish.js b/app/models/parish.js index 24cda3bb..6d51ee5c 100644 --- a/app/models/parish.js +++ b/app/models/parish.js @@ -2,14 +2,14 @@ const TABLE_NAME = "parishes"; -const util = require("util"); -const path = require("path"); -const AttributeBase = require(path.join(__dirname, "attribute_base.js")); +const { inherits } = require("util"); +const AttributeBase = require("./attribute_base.js"); function Model() { AttributeBase.call(this, TABLE_NAME); } -util.inherits(Model, AttributeBase); +inherits(Model, AttributeBase); module.exports = new Model(); + diff --git a/app/models/ward.js b/app/models/ward.js index 016d2174..e4eb38f4 100644 --- a/app/models/ward.js +++ b/app/models/ward.js @@ -2,14 +2,14 @@ const TABLE_NAME = "wards"; -const util = require("util"); -const path = require("path"); -const AttributeBase = require(path.join(__dirname, "attribute_base.js")); +const { inherits } = require("util"); +const AttributeBase = require("./attribute_base.js"); function Model() { AttributeBase.call(this, TABLE_NAME); } -util.inherits(Model, AttributeBase); +inherits(Model, AttributeBase); module.exports = new Model(); + From a98ad55fe728799a9008886a7dc91280bb4a338e Mon Sep 17 00:00:00 2001 From: Chris Blanchard Date: Tue, 27 Nov 2018 18:56:30 +0100 Subject: [PATCH 02/36] Merge test suites of models that inherit from attribute base --- tests/attribute_base.models.unit.js | 13 +++++ tests/ccg.unit.js | 72 ---------------------------- tests/constituency.unit.js | 72 ---------------------------- tests/county.unit.js | 69 -------------------------- tests/district.unit.js | 69 -------------------------- tests/helper/attribute_base.suite.js | 58 ++++++++++++++++++++++ tests/helper/index.js | 5 +- tests/nuts.unit.js | 72 ---------------------------- tests/parish.unit.js | 72 ---------------------------- tests/ward.unit.js | 69 -------------------------- 10 files changed, 75 insertions(+), 496 deletions(-) create mode 100644 tests/attribute_base.models.unit.js delete mode 100644 tests/ccg.unit.js delete mode 100644 tests/constituency.unit.js delete mode 100644 tests/county.unit.js delete mode 100644 tests/district.unit.js create mode 100644 tests/helper/attribute_base.suite.js delete mode 100644 tests/nuts.unit.js delete mode 100644 tests/parish.unit.js delete mode 100644 tests/ward.unit.js diff --git a/tests/attribute_base.models.unit.js b/tests/attribute_base.models.unit.js new file mode 100644 index 00000000..55163502 --- /dev/null +++ b/tests/attribute_base.models.unit.js @@ -0,0 +1,13 @@ +const helper = require("./helper/index.js"); + +[ + "Ccg", + "County", + "Constituency", + "District", + "Nuts", + "Parish", + "Ward", +].map(name => helper[name]) + .forEach(model => helper.AttributeBaseSuite.rigCoreSpecs(model)); + diff --git a/tests/ccg.unit.js b/tests/ccg.unit.js deleted file mode 100644 index 177e372f..00000000 --- a/tests/ccg.unit.js +++ /dev/null @@ -1,72 +0,0 @@ -var fs = require("fs"); -var path = require("path"); -var async = require("async"); -var assert = require("chai").assert; -var helper = require(__dirname + "/helper"); - -/* Modify model here */ -var Model = helper.Ccg; -/* Modify model here */ - -var data = JSON.parse(fs.readFileSync(path.join(helper.rootPath, "data/"+ Model.relation +".json"))); -var dataCount = 0; - -for (var d in data) { - if (data.hasOwnProperty(d)) { - dataCount += 1; - } -} - -describe(Model.relation + " model", function () { - before(function (done) { - this.timeout(0); - helper.clearPostcodeDb(function (error, result) { - if (error) return done(error); - helper.seedPostcodeDb(function (error, result) { - if (error) return done(error); - done(); - }); - }); - }); - - // Rebuild table after tests - after(function (done) { - this.timeout(0); - Model._setupTable(done); - }); - - describe("seedData", function () { - // Recreate clean table - before(function (done) { - Model._destroyRelation(function (error) { - if (error) return done(error); - Model._createRelation(done); - }); - }); - it ("loads correct data from data directory", function (done) { - this.timeout(0); - Model.seedData(function (error) { - if (error) return done(error); - Model._query("SELECT count(*) FROM " + Model.relation , function (error, result) { - if (error) return done(error); - assert.equal(result.rows[0].count, dataCount); - done(); - }); - }); - }); - }); - - describe("_setupTable", function (done) { - it ("creates a table, associated indexes and populates with data", function (done) { - this.timeout(0); - Model._setupTable(function (error) { - if (error) return done(error); - Model._query("SELECT count(*) FROM " + Model.relation , function (error, result) { - if (error) return done(error); - assert.equal(result.rows[0].count, dataCount); - done(); - }); - }); - }); - }); -}); \ No newline at end of file diff --git a/tests/constituency.unit.js b/tests/constituency.unit.js deleted file mode 100644 index c39d322a..00000000 --- a/tests/constituency.unit.js +++ /dev/null @@ -1,72 +0,0 @@ -var fs = require("fs"); -var path = require("path"); -var async = require("async"); -var assert = require("chai").assert; -var helper = require(__dirname + "/helper"); - -/* Modify model here */ -var Model = helper.Constituency; -/* Modify model here */ - -var data = JSON.parse(fs.readFileSync(path.join(helper.rootPath, "data/"+ Model.relation +".json"))); -var dataCount = 0; - -for (var d in data) { - if (data.hasOwnProperty(d)) { - dataCount += 1; - } -} - -describe(Model.relation + " model", function () { - before(function (done) { - this.timeout(0); - helper.clearPostcodeDb(function (error, result) { - if (error) return done(error); - helper.seedPostcodeDb(function (error, result) { - if (error) return done(error); - done(); - }); - }); - }); - - // Rebuild table after tests - after(function (done) { - this.timeout(0); - Model._setupTable(done); - }); - - describe("seedData", function () { - // Recreate clean table - before(function (done) { - Model._destroyRelation(function (error) { - if (error) return done(error); - Model._createRelation(done); - }); - }); - it ("loads correct data from data directory", function (done) { - this.timeout(0); - Model.seedData(function (error) { - if (error) return done(error); - Model._query("SELECT count(*) FROM " + Model.relation , function (error, result) { - if (error) return done(error); - assert.equal(result.rows[0].count, dataCount); - done(); - }); - }); - }); - }); - - describe("_setupTable", function (done) { - it ("creates a table, associated indexes and populates with data", function (done) { - this.timeout(0); - Model._setupTable(function (error) { - if (error) return done(error); - Model._query("SELECT count(*) FROM " + Model.relation , function (error, result) { - if (error) return done(error); - assert.equal(result.rows[0].count, dataCount); - done(); - }); - }); - }); - }); -}); \ No newline at end of file diff --git a/tests/county.unit.js b/tests/county.unit.js deleted file mode 100644 index f6be226f..00000000 --- a/tests/county.unit.js +++ /dev/null @@ -1,69 +0,0 @@ -var fs = require("fs"); -var path = require("path"); -var async = require("async"); -var assert = require("chai").assert; -var helper = require(__dirname + "/helper"); - -/* Modify model here */ -var Model = helper.County; -/* Modify model here */ - -var data = JSON.parse(fs.readFileSync(path.join(helper.rootPath, "data/"+ Model.relation +".json"))); -var dataCount = 0; - -for (var d in data) { - if (data.hasOwnProperty(d)) { - dataCount += 1; - } -} - -describe(Model.relation + " model", function () { - before(function (done) { - this.timeout(0); - helper.clearPostcodeDb(function (error, result) { - if (error) return done(error); - helper.seedPostcodeDb(function (error, result) { - if (error) return done(error); - done(); - }); - }); - }); - - // Rebuild table after tests - after(function (done) { - Model._setupTable(done); - }); - - describe("seedData", function () { - // Recreate clean table - before(function (done) { - Model._destroyRelation(function (error) { - if (error) return done(error); - Model._createRelation(done); - }); - }); - it ("loads correct data from data directory", function (done) { - Model.seedData(function (error) { - if (error) return done(error); - Model._query("SELECT count(*) FROM " + Model.relation , function (error, result) { - if (error) return done(error); - assert.equal(result.rows[0].count, dataCount); - done(); - }); - }); - }); - }); - - describe("_setupTable", function (done) { - it ("creates a table, associated indexes and populates with data", function (done) { - Model._setupTable(function (error) { - if (error) return done(error); - Model._query("SELECT count(*) FROM " + Model.relation , function (error, result) { - if (error) return done(error); - assert.equal(result.rows[0].count, dataCount); - done(); - }); - }); - }); - }); -}); \ No newline at end of file diff --git a/tests/district.unit.js b/tests/district.unit.js deleted file mode 100644 index 9de0a3a2..00000000 --- a/tests/district.unit.js +++ /dev/null @@ -1,69 +0,0 @@ -var fs = require("fs"); -var path = require("path"); -var async = require("async"); -var assert = require("chai").assert; -var helper = require(__dirname + "/helper"); - -/* Modify model here */ -var Model = helper.District; -/* Modify model here */ - -var data = JSON.parse(fs.readFileSync(path.join(helper.rootPath, "data/"+ Model.relation +".json"))); -var dataCount = 0; - -for (var d in data) { - if (data.hasOwnProperty(d)) { - dataCount += 1; - } -} - -describe(Model.relation + " model", function () { - before(function (done) { - this.timeout(0); - helper.clearPostcodeDb(function (error, result) { - if (error) return done(error); - helper.seedPostcodeDb(function (error, result) { - if (error) return done(error); - done(); - }); - }); - }); - - // Rebuild table after tests - after(function (done) { - Model._setupTable(done); - }); - - describe("seedData", function () { - // Recreate clean table - before(function (done) { - Model._destroyRelation(function (error) { - if (error) return done(error); - Model._createRelation(done); - }); - }); - it ("loads correct data from data directory", function (done) { - Model.seedData(function (error) { - if (error) return done(error); - Model._query("SELECT count(*) FROM " + Model.relation , function (error, result) { - if (error) return done(error); - assert.equal(result.rows[0].count, dataCount); - done(); - }); - }); - }); - }); - - describe("_setupTable", function (done) { - it ("creates a table, associated indexes and populates with data", function (done) { - Model._setupTable(function (error) { - if (error) return done(error); - Model._query("SELECT count(*) FROM " + Model.relation , function (error, result) { - if (error) return done(error); - assert.equal(result.rows[0].count, dataCount); - done(); - }); - }); - }); - }); -}); \ No newline at end of file diff --git a/tests/helper/attribute_base.suite.js b/tests/helper/attribute_base.suite.js new file mode 100644 index 00000000..62a3220d --- /dev/null +++ b/tests/helper/attribute_base.suite.js @@ -0,0 +1,58 @@ +"use strict" + +const { assert } = require("chai"); + +const getCount = o => Object.keys(o).length; +const loadData = model => require(`../../data/${model.relation}.json`); + +const rigCoreSpecs = model => { + const data = loadData(model); + const dataCount = getCount(data); + + describe(`${model.relation} model`, () => { + // Rebuild table after tests + after(function (done) { + this.timeout(0); + model._setupTable(done); + }); + + describe("seedData", () => { + // Recreate clean table + before(function (done) { + model._destroyRelation((error) => { + if (error) return done(error); + model._createRelation(done); + }); + }); + + it ("loads correct data from data directory", function (done) { + this.timeout(0); + model.seedData(function (error) { + if (error) return done(error); + model._query(`SELECT count(*) FROM ${model.relation}` , (error, result) => { + if (error) return done(error); + assert.equal(result.rows[0].count, dataCount); + done(); + }); + }); + }); + }); + + describe("_setupTable", done => { + it ("creates a table, associated indexes and populates with data", function (done) { + this.timeout(0); + model._setupTable(error => { + if (error) return done(error); + model._query(`SELECT count(*) FROM ${model.relation}` , (error, result) => { + if (error) return done(error); + assert.equal(result.rows[0].count, dataCount); + done(); + }); + }); + }); + }); + }); +}; + +module.exports = { rigCoreSpecs }; + diff --git a/tests/helper/index.js b/tests/helper/index.js index a12ded5b..f3c55e7d 100644 --- a/tests/helper/index.js +++ b/tests/helper/index.js @@ -12,6 +12,7 @@ const Base = require(path.join(rootPath, "app/models")); const config = require(path.join(rootPath + "/config/config"))(env); const seedPostcodePath = path.join(rootPath, "tests/seed/postcode.csv"); const seedPlacesPath = path.join(rootPath, "tests/seed/places/") +const AttributeBaseSuite = require("./attribute_base.suite.js"); // Load models const AttributeBase = require(path.join(rootPath, "app/models/attribute_base")); @@ -529,7 +530,6 @@ module.exports = { seedPostcodePath: seedPostcodePath, // Methods - allowsCORS: allowsCORS, clearTestDb: clearTestDb, removeDiacritics: require("./remove_diacritics"), @@ -564,6 +564,9 @@ module.exports = { listDatabaseIndexes: listDatabaseIndexes, locationWithNearbyPostcodes: locationWithNearbyPostcodes, + // Test suites + AttributeBaseSuite, + // Libs unaccent: require("../../app/lib/unaccent.js"), errors: require("../../app/lib/errors.js"), diff --git a/tests/nuts.unit.js b/tests/nuts.unit.js deleted file mode 100644 index af3ba910..00000000 --- a/tests/nuts.unit.js +++ /dev/null @@ -1,72 +0,0 @@ -var fs = require("fs"); -var path = require("path"); -var async = require("async"); -var assert = require("chai").assert; -var helper = require(__dirname + "/helper"); - -/* Modify model here */ -var Model = helper.Nuts; -/* Modify model here */ - -var data = JSON.parse(fs.readFileSync(path.join(helper.rootPath, "data/"+ Model.relation +".json"))); -var dataCount = 0; - -for (var d in data) { - if (data.hasOwnProperty(d)) { - dataCount += 1; - } -} - -describe(Model.relation + " model", function () { - before(function (done) { - this.timeout(0); - helper.clearPostcodeDb(function (error, result) { - if (error) return done(error); - helper.seedPostcodeDb(function (error, result) { - if (error) return done(error); - done(); - }); - }); - }); - - // Rebuild table after tests - after(function (done) { - this.timeout(0); - Model._setupTable(done); - }); - - describe("seedData", function () { - // Recreate clean table - before(function (done) { - Model._destroyRelation(function (error) { - if (error) return done(error); - Model._createRelation(done); - }); - }); - it ("loads correct data from data directory", function (done) { - this.timeout(0); - Model.seedData(function (error) { - if (error) return done(error); - Model._query("SELECT count(*) FROM " + Model.relation , function (error, result) { - if (error) return done(error); - assert.equal(result.rows[0].count, dataCount); - done(); - }); - }); - }); - }); - - describe("_setupTable", function (done) { - it ("creates a table, associated indexes and populates with data", function (done) { - this.timeout(0); - Model._setupTable(function (error) { - if (error) return done(error); - Model._query("SELECT count(*) FROM " + Model.relation , function (error, result) { - if (error) return done(error); - assert.equal(result.rows[0].count, dataCount); - done(); - }); - }); - }); - }); -}); \ No newline at end of file diff --git a/tests/parish.unit.js b/tests/parish.unit.js deleted file mode 100644 index 924a90b1..00000000 --- a/tests/parish.unit.js +++ /dev/null @@ -1,72 +0,0 @@ -var fs = require("fs"); -var path = require("path"); -var async = require("async"); -var assert = require("chai").assert; -var helper = require(__dirname + "/helper"); - -/* Modify model here */ -var Model = helper.Parish; -/* Modify model here */ - -var data = JSON.parse(fs.readFileSync(path.join(helper.rootPath, "data/"+ Model.relation +".json"))); -var dataCount = 0; - -for (var d in data) { - if (data.hasOwnProperty(d)) { - dataCount += 1; - } -} - -describe(Model.relation + " model", function () { - before(function (done) { - this.timeout(0); - helper.clearPostcodeDb(function (error, result) { - if (error) return done(error); - helper.seedPostcodeDb(function (error, result) { - if (error) return done(error); - done(); - }); - }); - }); - - // Rebuild table after tests - after(function (done) { - this.timeout(0); - Model._setupTable(done); - }); - - describe("seedData", function () { - // Recreate clean table - before(function (done) { - Model._destroyRelation(function (error) { - if (error) return done(error); - Model._createRelation(done); - }); - }); - it ("loads correct data from data directory", function (done) { - this.timeout(0); - Model.seedData(function (error) { - if (error) return done(error); - Model._query("SELECT count(*) FROM " + Model.relation , function (error, result) { - if (error) return done(error); - assert.equal(result.rows[0].count, dataCount); - done(); - }); - }); - }); - }); - - describe("_setupTable", function (done) { - it ("creates a table, associated indexes and populates with data", function (done) { - this.timeout(0); - Model._setupTable(function (error) { - if (error) return done(error); - Model._query("SELECT count(*) FROM " + Model.relation , function (error, result) { - if (error) return done(error); - assert.equal(result.rows[0].count, dataCount); - done(); - }); - }); - }); - }); -}); \ No newline at end of file diff --git a/tests/ward.unit.js b/tests/ward.unit.js deleted file mode 100644 index c2b35987..00000000 --- a/tests/ward.unit.js +++ /dev/null @@ -1,69 +0,0 @@ -"use strict"; - -const fs = require("fs"); -const path = require("path"); -const async = require("async"); -const assert = require("chai").assert; -const helper = require(`${__dirname}/helper`); - -const Model = helper.Ward; - -const data = JSON.parse(fs.readFileSync(path.join(helper.rootPath, "data/"+ Model.relation +".json"))); -let dataCount = 0; - -for (let d in data) { - if (data.hasOwnProperty(d)) { - dataCount += 1; - } -} - -describe(`${Model.relation} model`, () => { - before(function(done) { - this.timeout(0); - helper.clearPostcodeDb((error) => { - if (error) return done(error); - helper.seedPostcodeDb(done); - }); - }); - - // Rebuild table after tests - after(function(done) { - this.timeout(0); - Model._setupTable(done); - }); - - describe("seedData", () => { - // Recreate clean table - before(done => { - Model._destroyRelation(error => { - if (error) return done(error); - Model._createRelation(done); - }); - }); - it ("loads correct data from data directory", function(done) { - this.timeout(0); - Model.seedData(error => { - if (error) return done(error); - Model._query(`SELECT count(*) FROM ${Model.relation}` , (error, result) => { - if (error) return done(error); - assert.equal(result.rows[0].count, dataCount); - done(); - }); - }); - }); - }); - - describe("_setupTable", () => { - it ("creates a table, associated indexes and populates with data", function(done) { - this.timeout(0); - Model._setupTable(error => { - if (error) return done(error); - Model._query(`SELECT count(*) FROM ${Model.relation}`, (error, result) => { - if (error) return done(error); - assert.equal(result.rows[0].count, dataCount); - done(); - }); - }); - }); - }); -}); \ No newline at end of file From 105ea422eb76119f7f35b51646e88ed3ee109f49 Mon Sep 17 00:00:00 2001 From: Chris Blanchard Date: Wed, 28 Nov 2018 12:05:25 +0100 Subject: [PATCH 03/36] Implement ced extraction script --- data/scripts/ceds.js | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 data/scripts/ceds.js diff --git a/data/scripts/ceds.js b/data/scripts/ceds.js new file mode 100644 index 00000000..3ae01960 --- /dev/null +++ b/data/scripts/ceds.js @@ -0,0 +1,34 @@ +"use strict"; + +const { extract } = require("./index"); + +/** + * @module DataParser/ceds + * + * Writes ceds.json to stdout + */ + +const CODE_OFFSET = 0; +const VALUE_OFFSET = 1; + +const edRegex = /\s+ED$/; + +// Strips " ED" suffix from values +const stripSuffix = v => v.replace(edRegex, ""); + +const transform = row => { + const code = row[CODE_OFFSET]; + const value = row[VALUE_OFFSET]; + if (code === "CTY10CD") return []; // Escape if header + return [code, stripSuffix(value)]; +}; + +const configs = [ + { + file: "County Electoral Division names and codes EN as at 12_17.csv", + delimiter: ",", + transform + } +]; + +extract({ configs }); From 8adadd85d501d4151effd3500e18fa4d56f0516b Mon Sep 17 00:00:00 2001 From: Chris Blanchard Date: Thu, 29 Nov 2018 14:41:26 +0100 Subject: [PATCH 04/36] Distill CED datapoints from ONSPD Nov 2018 --- data/ceds.json | 1722 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1722 insertions(+) create mode 100644 data/ceds.json diff --git a/data/ceds.json b/data/ceds.json new file mode 100644 index 00000000..3ea72c21 --- /dev/null +++ b/data/ceds.json @@ -0,0 +1,1722 @@ +{ + "E58000001": "Abbey", + "E58000002": "Amersham and Chesham Bois", + "E58000003": "Aston Clinton and Bierton", + "E58000004": "Aylesbury East", + "E58000005": "Aylesbury North", + "E58000006": "Aylesbury North-West", + "E58000007": "Aylesbury South-East", + "E58000008": "Aylesbury South-West", + "E58000009": "Aylesbury West", + "E58000010": "Beaconsfield", + "E58000011": "Bernwood", + "E58000012": "Booker, Cressex and Castlefield", + "E58000013": "Buckingham East", + "E58000014": "Buckingham West", + "E58000015": "Chalfont St. Giles", + "E58000016": "Chalfont St. Peter", + "E58000017": "Chesham", + "E58000018": "Chess Valley", + "E58000019": "Chiltern Ridges", + "E58000020": "Chiltern Villages", + "E58000021": "Cliveden", + "E58000022": "Denham", + "E58000023": "Downley", + "E58000024": "Farnham Common and Burnham Beeches", + "E58000025": "Flackwell Heath, Little Marlow and Marlow South-East", + "E58000026": "Gerrards Cross", + "E58000027": "Great Brickhill", + "E58000028": "Great Missenden", + "E58000029": "Grendon Underwood", + "E58000030": "Hazlemere", + "E58000031": "Iver", + "E58000032": "Ivinghoe", + "E58000033": "Little Chalfont and Amersham Common", + "E58000034": "Marlow", + "E58000035": "Penn Wood and Old Amersham", + "E58000036": "Ridgeway East", + "E58000037": "Ridgeway West", + "E58000038": "Ryemead and Micklefield", + "E58000039": "Stoke Poges and Wexham", + "E58000040": "Stone and Waddesdon", + "E58000041": "Terriers and Amersham Hill", + "E58000042": "The Risboroughs", + "E58000043": "The Wooburns, Bourne End and Hedsor", + "E58000044": "Totteridge and Bowerdean", + "E58000045": "Tylers Green and Loudwater", + "E58000046": "Wendover, Halton and Stoke Mandeville", + "E58000047": "West Wycombe", + "E58000048": "Wing", + "E58000049": "Winslow", + "E58000050": "Abbey", + "E58000051": "Alconbury & Kimbolton", + "E58000052": "Arbury", + "E58000053": "Bar Hill", + "E58000054": "Brampton & Buckden", + "E58000055": "Burwell", + "E58000056": "Cambourne", + "E58000057": "Castle", + "E58000058": "Chatteris", + "E58000059": "Cherry Hinton", + "E58000060": "Chesterton", + "E58000061": "Cottenham & Willingham", + "E58000062": "Duxford", + "E58000063": "Ely North", + "E58000064": "Ely South", + "E58000065": "Fulbourn", + "E58000066": "Gamlingay", + "E58000067": "Godmanchester & Huntingdon South", + "E58000068": "Hardwick", + "E58000069": "Histon & Impington", + "E58000070": "Huntingdon North & Hartford", + "E58000071": "Huntingdon West", + "E58000072": "King's Hedges", + "E58000073": "Linton", + "E58000074": "Littleport", + "E58000075": "Longstanton, Northstowe & Over", + "E58000076": "March North & Waldersey", + "E58000077": "March South & Rural", + "E58000078": "Market", + "E58000079": "Melbourn & Bassingbourn", + "E58000080": "Newnham", + "E58000081": "Papworth & Swavesey", + "E58000082": "Petersfield", + "E58000083": "Queen Edith's", + "E58000084": "Ramsey & Bury", + "E58000085": "Roman Bank & Peckover", + "E58000086": "Romsey", + "E58000087": "Sawston & Shelford", + "E58000088": "Sawtry & Stilton", + "E58000089": "Soham North & Isleham", + "E58000090": "Soham South & Haddenham", + "E58000091": "Somersham & Earith", + "E58000092": "St. Ives North & Wyton", + "E58000093": "St. Ives South & Needingworth", + "E58000094": "St. Neots East & Gransden", + "E58000095": "St. Neots Eynesbury", + "E58000096": "St. Neots Priory Park & Little Paxton", + "E58000097": "St. Neots The Eatons", + "E58000098": "Sutton", + "E58000099": "The Hemingfords & Fenstanton", + "E58000100": "Trumpington", + "E58000101": "Warboys & The Stukeleys", + "E58000102": "Waterbeach", + "E58000103": "Whittlesey North", + "E58000104": "Whittlesey South", + "E58000105": "Wisbech East", + "E58000106": "Wisbech West", + "E58000107": "Woodditton", + "E58000108": "Yaxley & Farcet", + "E58000109": "Alston and East Fellside", + "E58000110": "Appleby", + "E58000111": "Aspatria", + "E58000112": "Belah", + "E58000113": "Belle Vue", + "E58000114": "Botcherby", + "E58000115": "Bothel and Wharrels", + "E58000116": "Brampton", + "E58000117": "Bransty", + "E58000118": "Cartmel", + "E58000119": "Castle", + "E58000120": "Cleator Moor East and Frizington", + "E58000121": "Cleator Moor West", + "E58000122": "Cockermouth North", + "E58000123": "Cockermouth South", + "E58000124": "Corby and Hayton", + "E58000125": "Currock", + "E58000126": "Dalston and Burgh", + "E58000127": "Dalton North", + "E58000128": "Dalton South", + "E58000129": "Dearham and Broughton", + "E58000130": "Denton Holme", + "E58000131": "Eden Lakes", + "E58000132": "Egremont", + "E58000133": "Egremont North and St. Bees", + "E58000134": "Gosforth", + "E58000135": "Grange", + "E58000136": "Greystoke and Hesket", + "E58000137": "Harraby North", + "E58000138": "Harraby South", + "E58000139": "Harrington", + "E58000140": "Hawcoat", + "E58000141": "High Furness", + "E58000142": "Hillcrest and Hensingham", + "E58000143": "Hindpool", + "E58000144": "Houghton and Irthington", + "E58000145": "Howgate", + "E58000146": "Kells and Sandwith", + "E58000147": "Kendal Castle", + "E58000148": "Kendal Highgate", + "E58000149": "Kendal Nether", + "E58000150": "Kendal South", + "E58000151": "Kendal Strickland and Fell", + "E58000152": "Kent Estuary", + "E58000153": "Keswick", + "E58000154": "Kirkby Stephen", + "E58000155": "Lakes", + "E58000156": "Longtown", + "E58000157": "Low Furness", + "E58000158": "Lower Kentdale", + "E58000159": "Lyth Valley", + "E58000160": "Maryport North", + "E58000161": "Maryport South", + "E58000162": "Millom", + "E58000163": "Millom Without", + "E58000164": "Mirehouse", + "E58000165": "Morton", + "E58000166": "Moss Bay and Moorclose", + "E58000167": "Newbarns and Parkside", + "E58000168": "Old Barrow", + "E58000169": "Ormsgill", + "E58000170": "Penrith East", + "E58000171": "Penrith North", + "E58000172": "Penrith Rural", + "E58000173": "Penrith West", + "E58000174": "Risedale", + "E58000175": "Roosecote", + "E58000176": "Seaton", + "E58000177": "Sedbergh and Kirkby Lonsdale", + "E58000178": "Solway Coast", + "E58000179": "St. John's and Great Clifton", + "E58000180": "St. Michael's", + "E58000181": "Stanwix Urban", + "E58000182": "Thursby", + "E58000183": "Ulverston East", + "E58000184": "Ulverston West", + "E58000185": "Upper Kent", + "E58000186": "Upperby", + "E58000187": "Walney North", + "E58000188": "Walney South", + "E58000189": "Wetheral", + "E58000190": "Wigton", + "E58000191": "Windermere", + "E58000192": "Yewdale", + "E58000193": "Alfreton and Somercotes", + "E58000194": "Alport and Derwent", + "E58000195": "Ashbourne", + "E58000196": "Aston", + "E58000197": "Bakewell", + "E58000198": "Barlborough and Clowne", + "E58000199": "Belper", + "E58000200": "Birdholme", + "E58000201": "Bolsover North", + "E58000202": "Bolsover South", + "E58000203": "Boythorpe and Brampton South", + "E58000204": "Breadsall and West Hallam", + "E58000205": "Breaston", + "E58000206": "Brimington", + "E58000207": "Buxton North and East", + "E58000208": "Buxton West", + "E58000209": "Chapel and Hope Valley", + "E58000210": "Clay Cross North", + "E58000211": "Clay Cross South", + "E58000212": "Derwent Valley", + "E58000213": "Dovedale", + "E58000214": "Dronfield East", + "E58000215": "Dronfield West and Walton", + "E58000216": "Duffield and Belper South", + "E58000217": "Eckington and Killamarsh", + "E58000218": "Etherow", + "E58000219": "Etwall and Repton", + "E58000220": "Glossop and Charlesworth", + "E58000221": "Greater Heanor", + "E58000222": "Heanor Central", + "E58000223": "Hilton", + "E58000224": "Horsley", + "E58000225": "Ilkeston East", + "E58000226": "Ilkeston South", + "E58000227": "Ilkeston West", + "E58000228": "Linton", + "E58000229": "Long Eaton", + "E58000230": "Loundsley Green and Newbold", + "E58000231": "Matlock", + "E58000232": "Melbourne", + "E58000233": "New Mills", + "E58000234": "Petersham", + "E58000235": "Ripley East and Codnor", + "E58000236": "Ripley West and Heage", + "E58000237": "Sandiacre", + "E58000238": "Sawley", + "E58000239": "Shirebrook and Pleasley", + "E58000240": "South Normanton and Pinxton", + "E58000241": "Spire", + "E58000242": "St. Mary's", + "E58000243": "Staveley", + "E58000244": "Staveley North and Whittington", + "E58000245": "Sutton", + "E58000246": "Swadlincote Central", + "E58000247": "Swadlincote North", + "E58000248": "Swadlincote South", + "E58000249": "Tibshelf", + "E58000250": "Walton and West", + "E58000251": "Whaley Bridge", + "E58000252": "Wingerworth and Shirland", + "E58000253": "Wirksworth", + "E58000254": "Alphington & Cowick", + "E58000255": "Ashburton & Buckfastleigh", + "E58000256": "Axminster", + "E58000257": "Barnstaple North", + "E58000258": "Barnstaple South", + "E58000259": "Bickleigh & Wembury", + "E58000260": "Bideford East", + "E58000261": "Bideford West & Hartland", + "E58000262": "Bovey Rural", + "E58000263": "Braunton Rural", + "E58000264": "Broadclyst", + "E58000265": "Chudleigh & Teign Valley", + "E58000266": "Chulmleigh & Landkey", + "E58000267": "Combe Martin Rural", + "E58000268": "Crediton", + "E58000269": "Creedy, Taw & Mid Exe", + "E58000270": "Cullompton & Bradninch", + "E58000271": "Dartmouth & Marldon", + "E58000272": "Dawlish", + "E58000273": "Duryard & Pennsylvania", + "E58000274": "Exminster & Haldon", + "E58000275": "Exmouth & Budleigh Salterton Coastal", + "E58000276": "Exmouth", + "E58000277": "Exwick & St. Thomas", + "E58000278": "Feniton & Honiton", + "E58000279": "Fremington Rural", + "E58000280": "Hatherleigh & Chagford", + "E58000281": "Heavitree & Whipton Barton", + "E58000282": "Holsworthy Rural", + "E58000283": "Ilfracombe", + "E58000284": "Ipplepen & The Kerswells", + "E58000285": "Ivybridge", + "E58000286": "Kingsbridge", + "E58000287": "Kingsteignton & Teign Estuary", + "E58000288": "Newton Abbot North", + "E58000289": "Newton Abbot South", + "E58000290": "Northam", + "E58000291": "Okehampton Rural", + "E58000292": "Otter Valley", + "E58000293": "Pinhoe & Mincinglake", + "E58000294": "Salcombe", + "E58000295": "Seaton & Colyton", + "E58000296": "Sidmouth", + "E58000297": "South Brent & Yealmpton", + "E58000298": "South Molton", + "E58000299": "St. David's & Haven Banks", + "E58000300": "St. Sidwells & St. James", + "E58000301": "Tavistock", + "E58000302": "Teignmouth", + "E58000303": "Tiverton East", + "E58000304": "Tiverton West", + "E58000305": "Torrington Rural", + "E58000306": "Totnes & Dartington", + "E58000307": "Wearside & Topsham", + "E58000308": "Whimple & Blackdown", + "E58000309": "Willand & Uffculme", + "E58000310": "Wonford & St. Loyes", + "E58000311": "Yelverton Rural", + "E58000312": "Beaminster", + "E58000313": "Blackmore Vale", + "E58000314": "Blandford Forum", + "E58000315": "Bridport", + "E58000316": "Broadwey", + "E58000317": "Burton Grange", + "E58000318": "Chickerell & Chesil Bank", + "E58000319": "Christchurch Central", + "E58000320": "Colehill East & Stapehill", + "E58000321": "Colehill West & Wimborne Minster", + "E58000322": "Commons", + "E58000323": "Corfe Mullen", + "E58000324": "Cranborne Chase", + "E58000325": "Dorchester", + "E58000326": "Ferndown", + "E58000327": "Gillingham", + "E58000328": "Hambledon", + "E58000329": "Linden Lea", + "E58000330": "Lodmoor", + "E58000331": "Lytchett Minster & Upton", + "E58000332": "Marshwood Vale", + "E58000333": "Moors", + "E58000334": "Mudeford & Highcliffe", + "E58000335": "North West Purbeck", + "E58000336": "Portland Harbour", + "E58000337": "Portland Tophill", + "E58000338": "Rodwell", + "E58000339": "Shaftesbury", + "E58000340": "Sherborne Rural", + "E58000341": "Sherborne Town", + "E58000342": "South Purbeck", + "E58000343": "Stalbridge & The Beacon", + "E58000344": "Swanage", + "E58000345": "Three Valleys", + "E58000346": "Verwood", + "E58000347": "Walkford", + "E58000348": "Wareham", + "E58000349": "Westham", + "E58000350": "Weymouth Town", + "E58000351": "Winterborne", + "E58000352": "Arlington, East Hoathly & Hellingly", + "E58000353": "Ashdown & Conquest", + "E58000354": "Baird & Ore", + "E58000355": "Battle & Crowhurst", + "E58000356": "Bexhill East", + "E58000357": "Bexhill North", + "E58000358": "Bexhill South", + "E58000359": "Bexhill West", + "E58000360": "Braybrooke & Castle", + "E58000361": "Brede Valley & Marsham", + "E58000362": "Central St. Leonards & Gensing", + "E58000363": "Chailey", + "E58000364": "Crowborough North & Jarvis Brook", + "E58000365": "Crowborough South & St. Johns", + "E58000366": "Devonshire", + "E58000367": "Forest Row & Groombridge", + "E58000368": "Hailsham Market", + "E58000369": "Hailsham New Town", + "E58000370": "Hampden Park", + "E58000371": "Heathfield & Mayfield", + "E58000372": "Hollington & Wishing Tree", + "E58000373": "Langney", + "E58000374": "Lewes", + "E58000375": "Maresfield & Buxted", + "E58000376": "Maze Hill & West St. Leonards", + "E58000377": "Meads", + "E58000378": "Newhaven & Bishopstone", + "E58000379": "Northern Rother", + "E58000380": "Old Hastings & Tressell", + "E58000381": "Old Town", + "E58000382": "Ouse Valley West & Downs", + "E58000383": "Peacehaven", + "E58000384": "Pevensey & Stone Cross", + "E58000385": "Polegate & Watermill", + "E58000386": "Ratton", + "E58000387": "Ringmer & Lewes Bridge", + "E58000388": "Rother North West", + "E58000389": "Rye & Eastern Rother", + "E58000390": "Seaford North", + "E58000391": "Seaford South", + "E58000392": "Sovereign", + "E58000393": "St. Anthony's", + "E58000394": "St. Helens & Silverhill", + "E58000395": "Telscombe", + "E58000396": "Uckfield North", + "E58000397": "Uckfield South with Framfield", + "E58000398": "Upperton", + "E58000399": "Wealden East", + "E58000400": "Wealden North East", + "E58000401": "Willingdon & South Downs", + "E58000402": "Abbey", + "E58000403": "Basildon Laindon Park and Fryerns", + "E58000404": "Basildon Pitsea", + "E58000405": "Basildon Westley Heights", + "E58000406": "Billericay and Burstead", + "E58000407": "Bocking", + "E58000408": "Braintree Eastern", + "E58000409": "Braintree Town", + "E58000410": "Brentwood Hutton", + "E58000411": "Brentwood North", + "E58000412": "Brentwood Rural", + "E58000413": "Brentwood South", + "E58000414": "Brightlingsea", + "E58000415": "Broomfield and Writtle", + "E58000416": "Buckhurst Hill and Loughton South", + "E58000417": "Canvey Island East", + "E58000418": "Canvey Island West", + "E58000419": "Chelmer", + "E58000420": "Chelmsford Central", + "E58000421": "Chelmsford North", + "E58000422": "Chelmsford West", + "E58000423": "Chigwell and Loughton Broadway", + "E58000424": "Clacton East", + "E58000425": "Clacton North", + "E58000426": "Clacton West", + "E58000427": "Constable", + "E58000428": "Drury", + "E58000429": "Dunmow", + "E58000430": "Epping and Theydon Bois", + "E58000431": "Frinton and Walton", + "E58000432": "Great Baddow", + "E58000433": "Hadleigh", + "E58000434": "Halstead", + "E58000435": "Harlow North", + "E58000436": "Harlow South East", + "E58000437": "Harlow West", + "E58000438": "Harwich", + "E58000439": "Hedingham", + "E58000440": "Heybridge and Tollesbury", + "E58000441": "Loughton Central", + "E58000442": "Maldon", + "E58000443": "Maypole", + "E58000444": "Mersea and Tiptree", + "E58000445": "Mile End and Highwoods", + "E58000446": "North Weald and Nazeing", + "E58000447": "Ongar and Rural", + "E58000448": "Parsons Heath and East Gates", + "E58000449": "Rayleigh North", + "E58000450": "Rayleigh South", + "E58000451": "Rochford North", + "E58000452": "Rochford South", + "E58000453": "Rochford West", + "E58000454": "Saffron Walden", + "E58000455": "South Benfleet", + "E58000456": "South Woodham Ferrers", + "E58000457": "Southminster", + "E58000458": "Springfield", + "E58000459": "Stansted", + "E58000460": "Stanway and Pyefleet", + "E58000461": "Stock", + "E58000462": "Tendring Rural East", + "E58000463": "Tendring Rural West", + "E58000464": "Thaxted", + "E58000465": "Three Fields with Great Notley", + "E58000466": "Thundersley", + "E58000467": "Waltham Abbey", + "E58000468": "Wickford Crouch", + "E58000469": "Witham Northern", + "E58000470": "Witham Southern", + "E58000471": "Wivenhoe St. Andrew", + "E58000472": "Abbey", + "E58000473": "All Saints and Oakley", + "E58000474": "Barnwood and Hucclecote", + "E58000475": "Barton and Tredworth", + "E58000476": "Battledown and Charlton Kings", + "E58000477": "Benhall and Up Hatherley", + "E58000478": "Bishop's Cleeve", + "E58000479": "Bisley and Painswick", + "E58000480": "Blakeney and Bream", + "E58000481": "Bourton-on-the-Water and Northleach", + "E58000482": "Brockworth", + "E58000483": "Cam Valley", + "E58000484": "Campden-Vale", + "E58000485": "Charlton Park and College", + "E58000486": "Churchdown", + "E58000487": "Cinderford", + "E58000488": "Cirencester Beeches", + "E58000489": "Cirencester Park", + "E58000490": "Coleford", + "E58000491": "Coney Hill and Matson", + "E58000492": "Drybrook and Lydbrook", + "E58000493": "Dursley", + "E58000494": "Fairford and Lechlade on Thames", + "E58000495": "Grange and Kingsway", + "E58000496": "Hardwicke and Severn", + "E58000497": "Hempsted and Westgate", + "E58000498": "Hesters Way and Springbank", + "E58000499": "Highnam", + "E58000500": "Kingsholm and Wotton", + "E58000501": "Lansdown and Park", + "E58000502": "Leckhampton and Warden Hill", + "E58000503": "Longlevens", + "E58000504": "Lydney", + "E58000505": "Minchinhampton", + "E58000506": "Mitcheldean", + "E58000507": "Nailsworth", + "E58000508": "Newent", + "E58000509": "Pittville and Prestbury", + "E58000510": "Quedgeley", + "E58000511": "Rodborough", + "E58000512": "Sedbury", + "E58000513": "South Cerney", + "E58000514": "St. Mark's and St. Peter's", + "E58000515": "St. Paul's and Swindon", + "E58000516": "Stonehouse", + "E58000517": "Stow-on-the-Wold", + "E58000518": "Stroud Central", + "E58000519": "Tetbury", + "E58000520": "Tewkesbury East", + "E58000521": "Tewkesbury", + "E58000522": "Tuffley", + "E58000523": "Winchcombe and Woodmancote", + "E58000524": "Wotton-under-Edge", + "E58000525": "Aldershot North", + "E58000526": "Aldershot South", + "E58000527": "Alton Rural", + "E58000528": "Alton Town", + "E58000529": "Andover North", + "E58000530": "Andover South", + "E58000531": "Andover West", + "E58000532": "Baddesley", + "E58000533": "Basingstoke Central", + "E58000534": "Basingstoke North", + "E58000535": "Basingstoke North West", + "E58000536": "Basingstoke South East", + "E58000537": "Basingstoke South West", + "E58000538": "Bishops Waltham", + "E58000539": "Bishopstoke & Fair Oak", + "E58000540": "Botley & Hedge End North", + "E58000541": "Bridgemary", + "E58000542": "Brockenhurst", + "E58000543": "Calleva", + "E58000544": "Candovers, Oakley & Overton", + "E58000545": "Catherington", + "E58000546": "Chandler's Ford", + "E58000547": "Church Crookham & Ewshot", + "E58000548": "Cowplain & Hart Plain", + "E58000549": "Dibden & Hythe", + "E58000550": "Eastleigh North", + "E58000551": "Eastleigh South", + "E58000552": "Emsworth & St. Faiths", + "E58000553": "Fareham Crofton", + "E58000554": "Fareham Portchester", + "E58000555": "Fareham Sarisbury", + "E58000556": "Fareham Titchfield", + "E58000557": "Fareham Town", + "E58000558": "Fareham Warsash", + "E58000559": "Farnborough North", + "E58000560": "Farnborough South", + "E58000561": "Farnborough West", + "E58000562": "Fleet Town", + "E58000563": "Hamble", + "E58000564": "Hardway", + "E58000565": "Hartley Wintney & Yateley West", + "E58000566": "Hayling Island", + "E58000567": "Hedge End & West End South", + "E58000568": "Itchen Valley", + "E58000569": "Lee", + "E58000570": "Leesland & Town", + "E58000571": "Liphook, Headley & Grayshott", + "E58000572": "Loddon", + "E58000573": "Lymington & Boldre", + "E58000574": "Lyndhurst & Fordingbridge", + "E58000575": "Meon Valley", + "E58000576": "New Milton", + "E58000577": "New Milton North, Milford & Hordle", + "E58000578": "North East Havant", + "E58000579": "North West Havant", + "E58000580": "Odiham & Hook", + "E58000581": "Petersfield Butser", + "E58000582": "Petersfield Hangers", + "E58000583": "Purbrook & Stakes South", + "E58000584": "Ringwood", + "E58000585": "Romsey Rural", + "E58000586": "Romsey Town", + "E58000587": "South Waterside", + "E58000588": "Tadley & Baughurst", + "E58000589": "Test Valley Central", + "E58000590": "Totton North & Netley Marsh", + "E58000591": "Totton South & Marchwood", + "E58000592": "Waterloo & Stakes North", + "E58000593": "West End & Horton Heath", + "E58000594": "Whitchurch & The Cleres", + "E58000595": "Whitehill, Bordon & Lindford", + "E58000596": "Winchester Downlands", + "E58000597": "Winchester Eastgate", + "E58000598": "Winchester Southern Parishes", + "E58000599": "Winchester Westgate", + "E58000600": "Yateley East & Blackwater", + "E58000601": "Abbots Langley", + "E58000602": "Baldock & Letchworth East", + "E58000603": "Bedwell", + "E58000604": "Berkhamsted", + "E58000605": "Bishops Stortford East", + "E58000606": "Bishops Stortford Rural", + "E58000607": "Bishops Stortford West", + "E58000608": "Borehamwood North", + "E58000609": "Borehamwood South", + "E58000610": "Bridgewater", + "E58000611": "Broadwater", + "E58000612": "Buntingford", + "E58000613": "Bushey North", + "E58000614": "Bushey South", + "E58000615": "Central Watford & Oxhey", + "E58000616": "Chells", + "E58000617": "Cheshunt Central", + "E58000618": "Colney Heath & Marshalswick", + "E58000619": "Croxley", + "E58000620": "Flamstead End & Turnford", + "E58000621": "Goffs Oak & Bury Green", + "E58000622": "Haldens", + "E58000623": "Handside & Peartree", + "E58000624": "Harpenden North East", + "E58000625": "Harpenden Rural", + "E58000626": "Harpenden South West", + "E58000627": "Hatfield East", + "E58000628": "Hatfield North", + "E58000629": "Hatfield Rural", + "E58000630": "Hatfield South", + "E58000631": "Hemel Hempstead East", + "E58000632": "Hemel Hempstead North East", + "E58000633": "Hemel Hempstead North West", + "E58000634": "Hemel Hempstead South East", + "E58000635": "Hemel Hempstead St. Pauls", + "E58000636": "Hemel Hempstead Town", + "E58000637": "Hertford All Saints", + "E58000638": "Hertford Rural", + "E58000639": "Hertford St. Andrews", + "E58000640": "Hitchin North", + "E58000641": "Hitchin Rural", + "E58000642": "Hitchin South", + "E58000643": "Hoddesdon North", + "E58000644": "Hoddesdon South", + "E58000645": "Kings Langley", + "E58000646": "Knebworth & Codicote", + "E58000647": "Letchworth North", + "E58000648": "Letchworth South", + "E58000649": "London Colney", + "E58000650": "Meriden Tudor", + "E58000651": "Nascot Park", + "E58000652": "North Watford", + "E58000653": "Old Stevenage", + "E58000654": "Potters Bar East", + "E58000655": "Potters Bar West & Shenley", + "E58000656": "Rickmansworth East & Oxhey Park", + "E58000657": "Rickmansworth West", + "E58000658": "Royston East & Ermine", + "E58000659": "Royston West & Rural", + "E58000660": "Sawbridgeworth", + "E58000661": "Shephall", + "E58000662": "South Oxhey & Eastbury", + "E58000663": "St. Albans Central", + "E58000664": "St. Albans East", + "E58000665": "St. Albans North", + "E58000666": "St. Albans South", + "E58000667": "St. Nicholas", + "E58000668": "St. Stephen's", + "E58000669": "Three Rivers Rural", + "E58000670": "Tring", + "E58000671": "Waltham Cross", + "E58000672": "Ware North", + "E58000673": "Ware South", + "E58000674": "Watling", + "E58000675": "Welwyn", + "E58000676": "Welwyn Garden City South", + "E58000677": "West Watford", + "E58000678": "Woodside Stanborough", + "E58000679": "Ashford Central", + "E58000680": "Ashford East", + "E58000681": "Ashford Rural East", + "E58000682": "Ashford Rural South", + "E58000683": "Ashford Rural West", + "E58000684": "Ashford South", + "E58000685": "Birchington & Rural", + "E58000686": "Broadstairs", + "E58000687": "Canterbury City North", + "E58000688": "Canterbury City South", + "E58000689": "Canterbury North", + "E58000690": "Canterbury South", + "E58000691": "Cheriton, Sandgate & Hythe East", + "E58000692": "Cliftonville", + "E58000693": "Cranbrook", + "E58000694": "Dartford East", + "E58000695": "Dartford North East", + "E58000696": "Dartford Rural", + "E58000697": "Dartford West", + "E58000698": "Deal & Walmer", + "E58000699": "Dover North", + "E58000700": "Dover Town", + "E58000701": "Dover West", + "E58000702": "Elham Valley", + "E58000703": "Faversham", + "E58000704": "Folkestone East", + "E58000705": "Folkestone West", + "E58000706": "Gravesend East", + "E58000707": "Gravesham Rural", + "E58000708": "Herne Bay East", + "E58000709": "Herne Village & Sturry", + "E58000710": "Hythe West", + "E58000711": "Maidstone Central", + "E58000712": "Maidstone North East", + "E58000713": "Maidstone Rural East", + "E58000714": "Maidstone Rural North", + "E58000715": "Maidstone Rural South", + "E58000716": "Maidstone Rural West", + "E58000717": "Maidstone South East", + "E58000718": "Maidstone South", + "E58000719": "Malling Central", + "E58000720": "Malling North East", + "E58000721": "Malling North", + "E58000722": "Malling Rural East", + "E58000723": "Malling West", + "E58000724": "Margate", + "E58000725": "Northfleet & Gravesend West", + "E58000726": "Ramsgate", + "E58000727": "Romney Marsh", + "E58000728": "Sandwich", + "E58000729": "Sevenoaks North & Darent Valley", + "E58000730": "Sevenoaks Rural North East", + "E58000731": "Sevenoaks Rural South", + "E58000732": "Sevenoaks Town", + "E58000733": "Sevenoaks West", + "E58000734": "Sheppey", + "E58000735": "Sittingbourne North", + "E58000736": "Sittingbourne South", + "E58000737": "Swale East", + "E58000738": "Swale West", + "E58000739": "Swanley", + "E58000740": "Swanscombe & Greenhithe", + "E58000741": "Tenterden", + "E58000742": "Tonbridge", + "E58000743": "Tunbridge Wells East", + "E58000744": "Tunbridge Wells North", + "E58000745": "Tunbridge Wells Rural", + "E58000746": "Tunbridge Wells South", + "E58000747": "Tunbridge Wells West", + "E58000748": "Whitstable East & Herne Bay West", + "E58000749": "Whitstable West", + "E58000750": "Wilmington", + "E58000751": "Accrington North", + "E58000752": "Accrington South", + "E58000753": "Accrington West & Oswaldtwistle Central", + "E58000754": "Brierfield & Nelson West", + "E58000755": "Burnley Central East", + "E58000756": "Burnley Central West", + "E58000757": "Burnley North East", + "E58000758": "Burnley Rural", + "E58000759": "Burnley South West", + "E58000760": "Burscough & Rufford", + "E58000761": "Chorley Central", + "E58000762": "Chorley North", + "E58000763": "Chorley Rural East", + "E58000764": "Chorley Rural West", + "E58000765": "Chorley South", + "E58000766": "Clayton with Whittle", + "E58000767": "Cleveleys East", + "E58000768": "Cleveleys South & Carleton", + "E58000769": "Clitheroe", + "E58000770": "Euxton, Buckshaw & Astley", + "E58000771": "Fleetwood East", + "E58000772": "Fleetwood West & Cleveleys West", + "E58000773": "Fylde East", + "E58000774": "Fylde South", + "E58000775": "Fylde West", + "E58000776": "Great Harwood, Rishton & Clayton-le-Moors", + "E58000777": "Heysham", + "E58000778": "Hoghton with Wheelton", + "E58000779": "Lancaster Central", + "E58000780": "Lancaster East", + "E58000781": "Lancaster Rural East", + "E58000782": "Lancaster Rural North", + "E58000783": "Lancaster South East", + "E58000784": "Leyland Central", + "E58000785": "Leyland South", + "E58000786": "Longridge with Bowland", + "E58000787": "Lostock Hall & Bamber Bridge", + "E58000788": "Lytham", + "E58000789": "Mid-Rossendale", + "E58000790": "Morecambe Central", + "E58000791": "Morecambe North", + "E58000792": "Morecambe South", + "E58000793": "Moss Side & Farington", + "E58000794": "Nelson East", + "E58000795": "Ormskirk", + "E58000796": "Oswaldtwistle", + "E58000797": "Padiham & Burnley West", + "E58000798": "Pendle Central", + "E58000799": "Pendle Hill", + "E58000800": "Pendle Rural", + "E58000801": "Penwortham East & Walton-le-Dale", + "E58000802": "Penwortham West", + "E58000803": "Poulton-le-Fylde", + "E58000804": "Preston Central East", + "E58000805": "Preston Central West", + "E58000806": "Preston City", + "E58000807": "Preston East", + "E58000808": "Preston North", + "E58000809": "Preston Rural", + "E58000810": "Preston South East", + "E58000811": "Preston South West", + "E58000812": "Preston West", + "E58000813": "Ribble Valley North East", + "E58000814": "Ribble Valley South West", + "E58000815": "Rossendale East", + "E58000816": "Rossendale South", + "E58000817": "Rossendale West", + "E58000818": "Skelmersdale Central", + "E58000819": "Skelmersdale East", + "E58000820": "Skelmersdale West", + "E58000821": "Skerton", + "E58000822": "South Ribble East", + "E58000823": "South Ribble West", + "E58000824": "St. Annes North", + "E58000825": "St. Annes South", + "E58000826": "Thornton & Hambleton", + "E58000827": "West Lancashire East", + "E58000828": "West Lancashire North", + "E58000829": "West Lancashire West", + "E58000830": "Whitworth & Bacup", + "E58000831": "Wyre Rural Central", + "E58000832": "Wyre Rural East", + "E58000833": "Ashby de la Zouch", + "E58000834": "Belvoir", + "E58000835": "Birstall", + "E58000836": "Blaby & Glen Parva", + "E58000837": "Bradgate", + "E58000838": "Braunstone", + "E58000839": "Broughton Astley", + "E58000840": "Bruntingthorpe", + "E58000841": "Burbage", + "E58000842": "Castle Donington & Kegworth", + "E58000843": "Coalville North", + "E58000844": "Coalville South", + "E58000845": "Cosby & Countesthorpe", + "E58000846": "De Montfort", + "E58000847": "Earl Shilton", + "E58000848": "East Wigston", + "E58000849": "Enderby & Lubbesthorpe", + "E58000850": "Forest & Measham", + "E58000851": "Gartree", + "E58000852": "Glenfields, Kirby Muxloe & Leicester Forests", + "E58000853": "Groby & Ratby", + "E58000854": "Hollycroft", + "E58000855": "Ibstock & Appleby", + "E58000856": "Launde", + "E58000857": "Loughborough East", + "E58000858": "Loughborough North", + "E58000859": "Loughborough North West", + "E58000860": "Loughborough South", + "E58000861": "Loughborough South West", + "E58000862": "Lutterworth", + "E58000863": "Mallory", + "E58000864": "Market Bosworth", + "E58000865": "Market Harborough East", + "E58000866": "Market Harborough West & Foxton", + "E58000867": "Markfield, Desford & Thornton", + "E58000868": "Melton East", + "E58000869": "Melton West", + "E58000870": "Melton Wolds", + "E58000871": "Narborough & Whetstone", + "E58000872": "North Wigston", + "E58000873": "Oadby", + "E58000874": "Quorn & Barrow", + "E58000875": "Rothley & Mountsorrel", + "E58000876": "Shepshed", + "E58000877": "Sileby & The Wolds", + "E58000878": "South & West Wigston", + "E58000879": "St. Marys", + "E58000880": "Stoney Stanton & Croft", + "E58000881": "Syston Fosse", + "E58000882": "Syston Ridgeway", + "E58000883": "Thurmaston Ridgemere", + "E58000884": "Valley", + "E58000885": "Whitwick", + "E58000886": "Alford & Sutton", + "E58000887": "Bardney & Cherry Willingham", + "E58000888": "Bassingham & Welbourn", + "E58000889": "Birchwood", + "E58000890": "Boston Coastal", + "E58000891": "Boston North", + "E58000892": "Boston Rural", + "E58000893": "Boston South", + "E58000894": "Boston West", + "E58000895": "Boultham", + "E58000896": "Bourne North and Morton", + "E58000897": "Bourne South and Thurlby", + "E58000898": "Carholme", + "E58000899": "Colsterworth Rural", + "E58000900": "Crowland", + "E58000901": "Deepings East", + "E58000902": "Deepings West and Rural", + "E58000903": "Donington Rural", + "E58000904": "Eagle & Hykeham West", + "E58000905": "Ermine & Cathedral", + "E58000906": "Folkingham Rural", + "E58000907": "Gainsborough Hill", + "E58000908": "Gainsborough Rural South", + "E58000909": "Gainsborough Trent", + "E58000910": "Grantham Barrowby", + "E58000911": "Grantham East", + "E58000912": "Grantham North", + "E58000913": "Grantham South", + "E58000914": "Grantham West", + "E58000915": "Hartsholme", + "E58000916": "Heckington", + "E58000917": "Holbeach", + "E58000918": "Holbeach Rural", + "E58000919": "Horncastle & the Keals", + "E58000920": "Hough", + "E58000921": "Hykeham Forum", + "E58000922": "Ingoldmells Rural", + "E58000923": "Louth North", + "E58000924": "Louth South", + "E58000925": "Louth Wolds", + "E58000926": "Mablethorpe", + "E58000927": "Market Rasen Wolds", + "E58000928": "Metheringham Rural", + "E58000929": "Nettleham & Saxilby", + "E58000930": "North Wolds", + "E58000931": "Park", + "E58000932": "Potterhanworth & Coleby", + "E58000933": "Ruskington", + "E58000934": "Saltfleet & the Cotes", + "E58000935": "Scotter Rural", + "E58000936": "Skegness North", + "E58000937": "Skegness South", + "E58000938": "Skirbeck", + "E58000939": "Sleaford", + "E58000940": "Sleaford Rural", + "E58000941": "Spalding East", + "E58000942": "Spalding Elloe", + "E58000943": "Spalding South", + "E58000944": "Spalding West", + "E58000945": "St. Giles", + "E58000946": "Stamford East", + "E58000947": "Stamford West", + "E58000948": "Swallow Beck & Witham", + "E58000949": "Tattershall Castle", + "E58000950": "The Suttons", + "E58000951": "Waddington & Hykeham East", + "E58000952": "Wainfleet", + "E58000953": "Washingborough", + "E58000954": "Welton Rural", + "E58000955": "Woodhall Spa & Wragby", + "E58000956": "Acle", + "E58000957": "Attleborough", + "E58000958": "Aylsham", + "E58000959": "Blofield and Brundall", + "E58000960": "Bowthorpe", + "E58000961": "Breydon", + "E58000962": "Caister-on-Sea", + "E58000963": "Catton Grove", + "E58000964": "Clavering", + "E58000965": "Clenchwarton and King's Lynn South", + "E58000966": "Costessey", + "E58000967": "Crome", + "E58000968": "Cromer", + "E58000969": "Dereham North", + "E58000970": "Dereham South", + "E58000971": "Dersingham", + "E58000972": "Diss and Roydon", + "E58000973": "Docking", + "E58000974": "Downham Market", + "E58000975": "Drayton and Horsford", + "E58000976": "East Depwade", + "E58000977": "East Flegg", + "E58000978": "Eaton", + "E58000979": "Elmham and Mattishall", + "E58000980": "Fakenham", + "E58000981": "Feltwell", + "E58000982": "Fincham", + "E58000983": "Forehoe", + "E58000984": "Freebridge Lynn", + "E58000985": "Gayton and Nar Valley", + "E58000986": "Gaywood North and Central", + "E58000987": "Gaywood South", + "E58000988": "Gorleston St. Andrews", + "E58000989": "Guiltcross", + "E58000990": "Hellesdon", + "E58000991": "Henstead", + "E58000992": "Hevingham and Spixworth", + "E58000993": "Hingham", + "E58000994": "Holt", + "E58000995": "Hoveton and Stalham", + "E58000996": "Humbleyard", + "E58000997": "King's Lynn North and Central", + "E58000998": "Lakenham", + "E58000999": "Loddon", + "E58001000": "Long Stratton", + "E58001001": "Lothingland", + "E58001002": "Magdalen", + "E58001003": "Mancroft", + "E58001004": "Marshland North", + "E58001005": "Marshland South", + "E58001006": "Melton Constable", + "E58001007": "Mile Cross", + "E58001008": "Mundesley", + "E58001009": "Necton and Launditch", + "E58001010": "Nelson", + "E58001011": "North Coast", + "E58001012": "North Walsham East", + "E58001013": "North Walsham West and Erpingham", + "E58001014": "Old Catton", + "E58001015": "Reepham", + "E58001016": "Sewell", + "E58001017": "Sheringham", + "E58001018": "South Smallburgh", + "E58001019": "Sprowston", + "E58001020": "Swaffham", + "E58001021": "Taverham", + "E58001022": "The Brecks", + "E58001023": "Thetford East", + "E58001024": "Thetford West", + "E58001025": "Thorpe Hamlet", + "E58001026": "Thorpe St. Andrew", + "E58001027": "Thorpe St. Andrew ED (DET)", + "E58001028": "Town Close", + "E58001029": "University", + "E58001030": "Watton", + "E58001031": "Wells", + "E58001032": "Wensum", + "E58001033": "West Depwade", + "E58001034": "West Flegg", + "E58001035": "Woodside", + "E58001036": "Wroxham", + "E58001037": "Wymondham", + "E58001038": "Yare and All Saints", + "E58001039": "Yarmouth Nelson and Southtown", + "E58001040": "Yarmouth North and Central", + "E58001041": "Ainsty", + "E58001042": "Airedale", + "E58001043": "Bedale", + "E58001044": "Boroughbridge", + "E58001045": "Castle", + "E58001046": "Catterick Bridge", + "E58001047": "Cawood and Saxton", + "E58001048": "Central Richmondshire", + "E58001049": "Easingwold", + "E58001050": "Eastfield and Osgodby", + "E58001051": "Escrick", + "E58001052": "Esk Valley", + "E58001053": "Falsgrave and Stepney", + "E58001054": "Filey", + "E58001055": "Great Ayton", + "E58001056": "Harrogate Bilton and Nidd Gorge", + "E58001057": "Harrogate Central", + "E58001058": "Harrogate Harlow", + "E58001059": "Harrogate Oatlands", + "E58001060": "Harrogate Saltergate", + "E58001061": "Harrogate Starbeck", + "E58001062": "Hertford and Cayton", + "E58001063": "Hovingham and Sheriff Hutton", + "E58001064": "Kirkbymoorside", + "E58001065": "Knaresborough", + "E58001066": "Lower Nidderdale and Bishop Monkton", + "E58001067": "Malton", + "E58001068": "Masham and Fountains", + "E58001069": "Mid Craven", + "E58001070": "Mid Selby", + "E58001071": "Middle Dales", + "E58001072": "Newby", + "E58001073": "North Craven", + "E58001074": "North Hambleton", + "E58001075": "Northallerton", + "E58001076": "Northstead", + "E58001077": "Norton", + "E58001078": "Osgoldcross", + "E58001079": "Pannal and Lower Wharfedale", + "E58001080": "Pateley Bridge", + "E58001081": "Pickering", + "E58001082": "Ribblesdale", + "E58001083": "Richmond", + "E58001084": "Richmondshire North", + "E58001085": "Ripon North", + "E58001086": "Ripon South", + "E58001087": "Romanby and Broomfield", + "E58001088": "Scalby and The Coast", + "E58001089": "Seamer and Derwent Valley", + "E58001090": "Selby Barlby", + "E58001091": "Selby Brayton", + "E58001092": "Sherburn in Elmet", + "E58001093": "Skipton East", + "E58001094": "Skipton West", + "E58001095": "South Craven", + "E58001096": "South Selby", + "E58001097": "Sowerby", + "E58001098": "Stillington", + "E58001099": "Stokesley", + "E58001100": "Swale", + "E58001101": "Tadcaster", + "E58001102": "Thirsk", + "E58001103": "Thornton Dale and the Wolds", + "E58001104": "Upper Dales", + "E58001105": "Weaponness and Ramshill", + "E58001106": "Whitby/Mayfield cum Mulgrave", + "E58001107": "Whitby/Streonshalh", + "E58001108": "Woodlands", + "E58001109": "Abington and Phippsville", + "E58001110": "Billing and Rectory Farm", + "E58001111": "Boothville and Parklands", + "E58001112": "Brackley", + "E58001113": "Braunston and Crick", + "E58001114": "Brickhill and Queensway", + "E58001115": "Brixworth", + "E58001116": "Bugbrooke", + "E58001117": "Burton and Broughton", + "E58001118": "Castle", + "E58001119": "Clover Hill", + "E58001120": "Corby Rural", + "E58001121": "Corby West", + "E58001122": "Croyland and Swanspool", + "E58001123": "Dallington Spencer", + "E58001124": "Daventry East", + "E58001125": "Daventry West", + "E58001126": "Deanshanger", + "E58001127": "Delapre and Rushmere", + "E58001128": "Desborough", + "E58001129": "Duston East", + "E58001130": "Duston West and St. Crispin", + "E58001131": "Earls Barton", + "E58001132": "East Hunsbury and Shelfleys", + "E58001133": "Finedon", + "E58001134": "Hackleton and Grange Park", + "E58001135": "Hatton Park", + "E58001136": "Headlands", + "E58001137": "Higham Ferrers", + "E58001138": "Irchester", + "E58001139": "Irthlingborough", + "E58001140": "Ise", + "E58001141": "Kingsthorpe North", + "E58001142": "Kingsthorpe South", + "E58001143": "Kingswood", + "E58001144": "Lloyds", + "E58001145": "Long Buckby", + "E58001146": "Middleton Cheney", + "E58001147": "Moulton", + "E58001148": "Nene Valley", + "E58001149": "Northall", + "E58001150": "Oakley", + "E58001151": "Oundle", + "E58001152": "Raunds", + "E58001153": "Riverside Park", + "E58001154": "Rothwell and Mawsley", + "E58001155": "Rushden Pemberton West", + "E58001156": "Rushden South", + "E58001157": "Silverstone", + "E58001158": "Sixfields", + "E58001159": "St. George", + "E58001160": "Talavera", + "E58001161": "Thrapston", + "E58001162": "Towcester and Roade", + "E58001163": "Wicksteed", + "E58001164": "Windmill", + "E58001165": "Woodford and Weedon", + "E58001166": "Arnold North", + "E58001167": "Arnold South", + "E58001168": "Ashfields", + "E58001169": "Balderton", + "E58001170": "Beeston Central & Rylands", + "E58001171": "Bingham East", + "E58001172": "Bingham West", + "E58001173": "Blidworth", + "E58001174": "Blyth & Harworth", + "E58001175": "Bramcote & Beeston North", + "E58001176": "Calverton", + "E58001177": "Carlton East", + "E58001178": "Carlton West", + "E58001179": "Collingham", + "E58001180": "Cotgrave", + "E58001181": "Eastwood", + "E58001182": "Farndon & Trent", + "E58001183": "Greasley & Brinsley", + "E58001184": "Hucknall North", + "E58001185": "Hucknall South", + "E58001186": "Hucknall West", + "E58001187": "Keyworth", + "E58001188": "Kirkby North", + "E58001189": "Kirkby South", + "E58001190": "Leake & Ruddington", + "E58001191": "Mansfield East", + "E58001192": "Mansfield North", + "E58001193": "Mansfield South", + "E58001194": "Mansfield West", + "E58001195": "Misterton", + "E58001196": "Muskham & Farnsfield", + "E58001197": "Newark East", + "E58001198": "Newark West", + "E58001199": "Newstead", + "E58001200": "Nuthall & Kimberley", + "E58001201": "Ollerton", + "E58001202": "Radcliffe on Trent", + "E58001203": "Retford East", + "E58001204": "Retford West", + "E58001205": "Selston", + "E58001206": "Sherwood Forest", + "E58001207": "Southwell", + "E58001208": "Stapleford & Broxtowe Central", + "E58001209": "Sutton Central & East", + "E58001210": "Sutton North", + "E58001211": "Sutton West", + "E58001212": "Toton, Chilwell & Attenborough", + "E58001213": "Tuxford", + "E58001214": "Warsop", + "E58001215": "West Bridgford North", + "E58001216": "West Bridgford South", + "E58001217": "West Bridgford West", + "E58001218": "Worksop East", + "E58001219": "Worksop North", + "E58001220": "Worksop South", + "E58001221": "Worksop West", + "E58001222": "Abingdon East", + "E58001223": "Abingdon North", + "E58001224": "Abingdon South", + "E58001225": "Banbury Calthorpe", + "E58001226": "Banbury Grimsbury and Castle", + "E58001227": "Banbury Hardwick", + "E58001228": "Banbury Ruscote", + "E58001229": "Barton, Sandhills and Risinghurst", + "E58001230": "Benson and Cholsey", + "E58001231": "Berinsfield and Garsington", + "E58001232": "Bicester North", + "E58001233": "Bicester Town", + "E58001234": "Bicester West", + "E58001235": "Bloxham and Easington", + "E58001236": "Burford and Carterton North", + "E58001237": "Carterton South and West", + "E58001238": "Chalgrove and Watlington", + "E58001239": "Charlbury and Wychwood", + "E58001240": "Chipping Norton", + "E58001241": "Churchill and Lye Valley", + "E58001242": "Cowley", + "E58001243": "Deddington", + "E58001244": "Didcot East and Hagbourne", + "E58001245": "Didcot Ladygrove", + "E58001246": "Didcot West", + "E58001247": "Eynsham", + "E58001248": "Faringdon", + "E58001249": "Goring", + "E58001250": "Grove and Wantage", + "E58001251": "Hanborough and Minster Lovell", + "E58001252": "Headington and Quarry", + "E58001253": "Hendreds and Harwell", + "E58001254": "Henley-on-Thames", + "E58001255": "Iffley Fields and St. Mary's", + "E58001256": "Isis", + "E58001257": "Jericho and Osney", + "E58001258": "Kennington and Radley", + "E58001259": "Kidlington South", + "E58001260": "Kingston and Cumnor", + "E58001261": "Kirtlington and Kidlington North", + "E58001262": "Leys", + "E58001263": "Marston and Northway", + "E58001264": "North Hinksey", + "E58001265": "Otmoor", + "E58001266": "Ploughley", + "E58001267": "Rose Hill and Littlemore", + "E58001268": "Shrivenham", + "E58001269": "Sonning Common", + "E58001270": "St. Clement's and Cowley Marsh", + "E58001271": "St. Margaret's", + "E58001272": "Sutton Courtenay and Marcham", + "E58001273": "Thame and Chinnor", + "E58001274": "University Parks", + "E58001275": "Wallingford", + "E58001276": "Wheatley", + "E58001277": "Witney North and East", + "E58001278": "Witney South and Central", + "E58001279": "Witney West and Bampton", + "E58001280": "Wolvercote and Summertown", + "E58001281": "Woodstock", + "E58001282": "Wroxton and Hook Norton", + "E58001283": "Bishop's Hull and Taunton West", + "E58001284": "Blackdown and Neroche", + "E58001285": "Blackmoor Vale", + "E58001286": "Brent", + "E58001287": "Bridgwater East and Bawdrip", + "E58001288": "Bridgwater North and Central", + "E58001289": "Bridgwater South", + "E58001290": "Bridgwater West", + "E58001291": "Brympton", + "E58001292": "Burnham North", + "E58001293": "Cannington", + "E58001294": "Castle Cary", + "E58001295": "Chard North", + "E58001296": "Chard South", + "E58001297": "Cheddar", + "E58001298": "Coker", + "E58001299": "Comeytrowe and Trull", + "E58001300": "Crewkerne", + "E58001301": "Curry Rivel and Langport", + "E58001302": "Dulverton and Exmoor", + "E58001303": "Dunster", + "E58001304": "Frome East", + "E58001305": "Frome North", + "E58001306": "Frome West", + "E58001307": "Glastonbury and Street", + "E58001308": "Highbridge and Burnham South", + "E58001309": "Huntspill", + "E58001310": "Ilminster", + "E58001311": "King Alfred", + "E58001312": "Lydeard", + "E58001313": "Martock", + "E58001314": "Mendip Central and East", + "E58001315": "Mendip Hiils", + "E58001316": "Mendip South", + "E58001317": "Mendip West", + "E58001318": "Minehead", + "E58001319": "Monkton and North Curry", + "E58001320": "North Petherton", + "E58001321": "Rowbarton and Staplegrove", + "E58001322": "Shepton Mallet", + "E58001323": "Somerton", + "E58001324": "South Petherton and Islemoor", + "E58001325": "Taunton East", + "E58001326": "Taunton North", + "E58001327": "Taunton South", + "E58001328": "Upper Tone", + "E58001329": "Watchet and Stogursey", + "E58001330": "Wellington", + "E58001331": "Wells", + "E58001332": "Wincanton and Bruton", + "E58001333": "Yeovil Central", + "E58001334": "Yeovil East", + "E58001335": "Yeovil South", + "E58001336": "Yeovil West", + "E58001337": "Amington", + "E58001338": "Audley and Chesterton", + "E58001339": "Biddulph North", + "E58001340": "Biddulph South and Endon", + "E58001341": "Bolebridge", + "E58001342": "Bradwell, Porthill and Wolstanton", + "E58001343": "Brereton and Ravenhill", + "E58001344": "Brewood", + "E58001345": "Burntwood North", + "E58001346": "Burntwood South", + "E58001347": "Burton Tower", + "E58001348": "Burton Town", + "E58001349": "Burton Trent", + "E58001350": "Cannock Town Centre", + "E58001351": "Cannock Villages", + "E58001352": "Caverswall", + "E58001353": "Chadsmoor", + "E58001354": "Cheadle and Checkley", + "E58001355": "Cheslyn Hay, Essington and Great Wyrley", + "E58001356": "Churnet Valley", + "E58001357": "Codsall", + "E58001358": "Dove", + "E58001359": "Eccleshall", + "E58001360": "Etching Hill and Heath", + "E58001361": "Gnosall and Doxey", + "E58001362": "Hednesford and Rawnsley", + "E58001363": "Horninglow and Stretton", + "E58001364": "Keele, Knutton and Silverdale", + "E58001365": "Kidsgrove", + "E58001366": "Kinver", + "E58001367": "Leek Rural", + "E58001368": "Leek South", + "E58001369": "Lichfield City North", + "E58001370": "Lichfield City South", + "E58001371": "Lichfield Rural East", + "E58001372": "Lichfield Rural North", + "E58001373": "Lichfield Rural South", + "E58001374": "Lichfield Rural West", + "E58001375": "May Bank and Cross Heath", + "E58001376": "Needwood Forest", + "E58001377": "Newcastle Rural", + "E58001378": "Newcastle South", + "E58001379": "Penkridge", + "E58001380": "Perrycrofts", + "E58001381": "Perton", + "E58001382": "Stafford Central", + "E58001383": "Stafford North", + "E58001384": "Stafford South East", + "E58001385": "Stafford Trent Valley", + "E58001386": "Stafford West", + "E58001387": "Stone Rural", + "E58001388": "Stone Urban", + "E58001389": "Stonydelph", + "E58001390": "Talke and Red Street", + "E58001391": "Uttoxeter Rural", + "E58001392": "Uttoxeter Town", + "E58001393": "Watling North", + "E58001394": "Watling South", + "E58001395": "Westlands and Thistleberry", + "E58001396": "Wombourne", + "E58001397": "Aldeburgh and Leiston", + "E58001398": "Beccles", + "E58001399": "Belstead Brook", + "E58001400": "Bixley", + "E58001401": "Blackbourn", + "E58001402": "Blything", + "E58001403": "Bosmere", + "E58001404": "Brandon", + "E58001405": "Bridge", + "E58001406": "Bungay", + "E58001407": "Carlford", + "E58001408": "Chantry", + "E58001409": "Clare", + "E58001410": "Cosford", + "E58001411": "Eastgate and Moreton Hall", + "E58001412": "Exning and Newmarket", + "E58001413": "Felixstowe Coastal", + "E58001414": "Felixstowe North and Trimley", + "E58001415": "Framlingham", + "E58001416": "Gainsborough", + "E58001417": "Gipping Valley", + "E58001418": "Great Cornard", + "E58001419": "Gunton", + "E58001420": "Hadleigh", + "E58001421": "Halesworth", + "E58001422": "Hardwick", + "E58001423": "Hartismere", + "E58001424": "Haverhill Cangle", + "E58001425": "Haverhill East and Kedington", + "E58001426": "Hoxne and Eye", + "E58001427": "Kesgrave and Rushmere St. Andrew", + "E58001428": "Kessingland and Southwold", + "E58001429": "Lowestoft South", + "E58001430": "Martlesham", + "E58001431": "Melford", + "E58001432": "Mildenhall", + "E58001433": "Newmarket and Red Lodge", + "E58001434": "Oulton", + "E58001435": "Pakefield", + "E58001436": "Peninsula", + "E58001437": "Priory Heath", + "E58001438": "Row Heath", + "E58001439": "Rushmere", + "E58001440": "Samford", + "E58001441": "St. Helen's", + "E58001442": "St. John's", + "E58001443": "St. Margaret's and Westgate", + "E58001444": "Stour Valley", + "E58001445": "Stowmarket North and Stowupland", + "E58001446": "Stowmarket South", + "E58001447": "Sudbury East and Waldingfield", + "E58001448": "Sudbury", + "E58001449": "Thedwastre North", + "E58001450": "Thedwastre South", + "E58001451": "Thingoe North", + "E58001452": "Thingoe South", + "E58001453": "Thredling", + "E58001454": "Tower", + "E58001455": "Upper Gipping", + "E58001456": "Whitehouse and Whitton", + "E58001457": "Wickham", + "E58001458": "Wilford", + "E58001459": "Woodbridge", + "E58001460": "Addlestone", + "E58001461": "Ash", + "E58001462": "Ashford", + "E58001463": "Ashtead", + "E58001464": "Bagshot, Windlesham and Chobham", + "E58001465": "Banstead, Woodmansterne and Chipstead", + "E58001466": "Bookham and Fetcham West", + "E58001467": "Camberley East", + "E58001468": "Camberley West", + "E58001469": "Caterham Hill", + "E58001470": "Caterham Valley", + "E58001471": "Chertsey", + "E58001472": "Cobham", + "E58001473": "Cranleigh and Ewhurst", + "E58001474": "Dorking Hills", + "E58001475": "Dorking Rural", + "E58001476": "Dorking South and the Holmwoods", + "E58001477": "Earlswood and Reigate South", + "E58001478": "East Molesey and Esher", + "E58001479": "Egham", + "E58001480": "Englefield Green", + "E58001481": "Epsom Town and Downs", + "E58001482": "Epsom West", + "E58001483": "Ewell Court, Auriol and Cuddington", + "E58001484": "Ewell", + "E58001485": "Farnham Central", + "E58001486": "Farnham North", + "E58001487": "Farnham South", + "E58001488": "Foxhills, Thorpe and Virginia Water", + "E58001489": "Frimley Green and Mytchett", + "E58001490": "Godalming North", + "E58001491": "Godalming South, Milford and Witley", + "E58001492": "Godstone", + "E58001493": "Goldsworth East and Horsell Village", + "E58001494": "Guildford East", + "E58001495": "Guildford North", + "E58001496": "Guildford South East", + "E58001497": "Guildford South West", + "E58001498": "Guildford West", + "E58001499": "Haslemere", + "E58001500": "Heatherside and Parkside", + "E58001501": "Hersham", + "E58001502": "Hinchley Wood, Claygate and Oxshott", + "E58001503": "Horley East", + "E58001504": "Horley West, Salfords and Sidlow", + "E58001505": "Horsleys", + "E58001506": "Knaphill and Goldsworth West", + "E58001507": "Laleham and Shepperton", + "E58001508": "Leatherhead and Fetcham East", + "E58001509": "Lightwater, West End and Bisley", + "E58001510": "Lightwater, West End and Bisley ED (DET)", + "E58001511": "Lingfield", + "E58001512": "Lower Sunbury and Halliford", + "E58001513": "Merstham and Banstead South", + "E58001514": "Nork and Tattenhams", + "E58001515": "Oxted", + "E58001516": "Redhill East", + "E58001517": "Redhill West and Meadvale", + "E58001518": "Reigate", + "E58001519": "Shalford", + "E58001520": "Shere", + "E58001521": "Staines", + "E58001522": "Staines South and Ashford West", + "E58001523": "Stanwell and Stanwell Moor", + "E58001524": "Sunbury Common and Ashford Common", + "E58001525": "Tadworth, Walton and Kingswood", + "E58001526": "The Byfleets", + "E58001527": "The Dittons", + "E58001528": "Walton", + "E58001529": "Walton South and Oatlands", + "E58001530": "Warlingham", + "E58001531": "Waverley Eastern Villages", + "E58001532": "Waverley Western Villages", + "E58001533": "West Ewell", + "E58001534": "West Molesey", + "E58001535": "Weybridge", + "E58001536": "Woking North", + "E58001537": "Woking South East", + "E58001538": "Woking South", + "E58001539": "Woking South West", + "E58001540": "Woodham and New Haw", + "E58001541": "Worplesdon", + "E58001542": "Admirals & Cawston", + "E58001543": "Alcester", + "E58001544": "Arbury", + "E58001545": "Arden", + "E58001546": "Atherstone", + "E58001547": "Attleborough", + "E58001548": "Baddesley & Dordon", + "E58001549": "Bedworth Central", + "E58001550": "Bedworth East", + "E58001551": "Bedworth North", + "E58001552": "Bedworth West", + "E58001553": "Benn", + "E58001554": "Bidford & Welford", + "E58001555": "Bilton & Hillside", + "E58001556": "Brownsover & Coton Park", + "E58001557": "Budbrooke & Bishop's Tachbrook", + "E58001558": "Bulkington & Whitestone", + "E58001559": "Camp Hill", + "E58001560": "Coleshill North & Water Orton", + "E58001561": "Coleshill South & Arley", + "E58001562": "Cubbington & Leek Wootton", + "E58001563": "Dunsmore & Leam Valley", + "E58001564": "Earl Craven", + "E58001565": "Eastlands", + "E58001566": "Feldon", + "E58001567": "Fosse", + "E58001568": "Galley Common", + "E58001569": "Hartshill & Mancetter", + "E58001570": "Hillmorton", + "E58001571": "Kenilworth Park Hill", + "E58001572": "Kenilworth St. John's", + "E58001573": "Kineton & Red Horse", + "E58001574": "Kingsbury", + "E58001575": "Lapworth & West Kenilworth", + "E58001576": "Leamington Brunswick", + "E58001577": "Leamington Clarendon", + "E58001578": "Leamington Milverton", + "E58001579": "Leamington North", + "E58001580": "Leamington Willes", + "E58001581": "New Bilton & Overslade", + "E58001582": "Nuneaton Abbey", + "E58001583": "Nuneaton East", + "E58001584": "Polesworth", + "E58001585": "Shipston", + "E58001586": "Southam, Stockton & Napton", + "E58001587": "Stockingford", + "E58001588": "Stour & the Vale", + "E58001589": "Stratford North", + "E58001590": "Stratford South", + "E58001591": "Stratford West", + "E58001592": "Studley", + "E58001593": "Warwick North", + "E58001594": "Warwick South", + "E58001595": "Warwick West", + "E58001596": "Weddington", + "E58001597": "Wellesbourne", + "E58001598": "Whitnash", + "E58001599": "Angmering & Findon", + "E58001600": "Arundel & Courtwick", + "E58001601": "Bersted", + "E58001602": "Bewbush & Ifield West", + "E58001603": "Billingshurst", + "E58001604": "Bognor Regis East", + "E58001605": "Bognor Regis West & Aldwick", + "E58001606": "Bourne", + "E58001607": "Bramber Castle", + "E58001608": "Broadbridge", + "E58001609": "Broadfield", + "E58001610": "Broadwater", + "E58001611": "Burgess Hill East", + "E58001612": "Burgess Hill North", + "E58001613": "Chichester East", + "E58001614": "Chichester North", + "E58001615": "Chichester South", + "E58001616": "Chichester West", + "E58001617": "Cissbury", + "E58001618": "Cuckfield & Lucastes", + "E58001619": "Durrington & Salvington", + "E58001620": "East Grinstead Meridian", + "E58001621": "East Grinstead South & Ashurst Wood", + "E58001622": "East Preston & Ferring", + "E58001623": "Felpham", + "E58001624": "Fontwell", + "E58001625": "Goring", + "E58001626": "Hassocks & Burgess Hill South", + "E58001627": "Haywards Heath East", + "E58001628": "Haywards Heath Town", + "E58001629": "Henfield", + "E58001630": "Holbrook", + "E58001631": "Horsham East", + "E58001632": "Horsham Hurst", + "E58001633": "Horsham Riverside", + "E58001634": "Hurstpierpoint & Bolney", + "E58001635": "Imberdown", + "E58001636": "Lancing", + "E58001637": "Langley Green & Ifield East", + "E58001638": "Lindfield & High Weald", + "E58001639": "Littlehampton East", + "E58001640": "Littlehampton Town", + "E58001641": "Maidenbower & Worth", + "E58001642": "Middleton", + "E58001643": "Midhurst", + "E58001644": "Northbrook", + "E58001645": "Northgate & West Green", + "E58001646": "Nyetimber", + "E58001647": "Petworth", + "E58001648": "Pound Hill", + "E58001649": "Pulborough", + "E58001650": "Rother Valley", + "E58001651": "Rustington", + "E58001652": "Selsey", + "E58001653": "Shoreham North", + "E58001654": "Shoreham South", + "E58001655": "Sompting & North Lancing", + "E58001656": "Southgate & Gossops Green", + "E58001657": "Southwater & Nuthurst", + "E58001658": "Southwick", + "E58001659": "St. Leonard's Forest", + "E58001660": "Storrington", + "E58001661": "Tarring", + "E58001662": "The Witterings", + "E58001663": "Three Bridges", + "E58001664": "Tilgate & Furnace Green", + "E58001665": "Worth Forest", + "E58001666": "Worthing East", + "E58001667": "Worthing Pier", + "E58001668": "Worthing West", + "E58001669": "Alvechurch", + "E58001670": "Arrow Valley East", + "E58001671": "Arrow Valley West", + "E58001672": "Beacon", + "E58001673": "Bedwardine", + "E58001674": "Bewdley", + "E58001675": "Bowbrook", + "E58001676": "Bredon", + "E58001677": "Broadway", + "E58001678": "Bromsgrove Central", + "E58001679": "Bromsgrove East", + "E58001680": "Bromsgrove South", + "E58001681": "Bromsgrove West", + "E58001682": "Chaddesley", + "E58001683": "Claines", + "E58001684": "Clent Hills", + "E58001685": "Cookley, Wolverley and Wribbenhall", + "E58001686": "Croome", + "E58001687": "Droitwich East", + "E58001688": "Droitwich West", + "E58001689": "Evesham North West", + "E58001690": "Evesham South", + "E58001691": "Gorse Hill and Warndon", + "E58001692": "Hallow", + "E58001693": "Harvington", + "E58001694": "Littletons", + "E58001695": "Malvern Chase", + "E58001696": "Malvern Langland", + "E58001697": "Malvern Link", + "E58001698": "Malvern Trinity", + "E58001699": "Nunnery", + "E58001700": "Ombersley", + "E58001701": "Pershore", + "E58001702": "Powick", + "E58001703": "Rainbow Hill", + "E58001704": "Redditch North", + "E58001705": "Redditch South", + "E58001706": "Riverside", + "E58001707": "St. Barnabas", + "E58001708": "St. Chads", + "E58001709": "St. Georges and St. Oswald", + "E58001710": "St. John", + "E58001711": "St. Johns", + "E58001712": "St. Marys", + "E58001713": "St. Peter", + "E58001714": "St. Stephen", + "E58001715": "Stourport-on-Severn", + "E58001716": "Tenbury", + "E58001717": "Upton Snodsbury", + "E58001718": "Warndon Parish", + "E58001719": "Woodvale", + "E58001720": "Wythall" +} From 2112dfc74da8f8e25353f632e17f9c3e956401a9 Mon Sep 17 00:00:00 2001 From: Chris Blanchard Date: Thu, 29 Nov 2018 14:46:01 +0100 Subject: [PATCH 05/36] Implement CED base model --- app/models/ced.js | 15 ++++ tests/attribute_base.models.unit.js | 1 + tests/helper/index.js | 126 ++++++++++++++-------------- 3 files changed, 80 insertions(+), 62 deletions(-) create mode 100644 app/models/ced.js diff --git a/app/models/ced.js b/app/models/ced.js new file mode 100644 index 00000000..9033c8d6 --- /dev/null +++ b/app/models/ced.js @@ -0,0 +1,15 @@ +"use strict"; + +const TABLE_NAME = "ceds"; + +const { inherits } = require("util"); +const AttributeBase = require("./attribute_base.js"); + +function Model() { + AttributeBase.call(this, TABLE_NAME); +} + +inherits(Model, AttributeBase); + +module.exports = new Model(); + diff --git a/tests/attribute_base.models.unit.js b/tests/attribute_base.models.unit.js index 55163502..54e5cb21 100644 --- a/tests/attribute_base.models.unit.js +++ b/tests/attribute_base.models.unit.js @@ -2,6 +2,7 @@ const helper = require("./helper/index.js"); [ "Ccg", + "Ced", "County", "Constituency", "District", diff --git a/tests/helper/index.js b/tests/helper/index.js index f3c55e7d..f1fc768c 100644 --- a/tests/helper/index.js +++ b/tests/helper/index.js @@ -8,25 +8,26 @@ const randomString = require("random-string"); const rootPath = path.join(__dirname, "../../"); const env = process.env.NODE_ENV || "development"; const NO_RELOAD_DB = !!process.env.NO_RELOAD_DB; -const Base = require(path.join(rootPath, "app/models")); -const config = require(path.join(rootPath + "/config/config"))(env); +const Base = require("../../app/models"); +const config = require("../..//config/config")(env); const seedPostcodePath = path.join(rootPath, "tests/seed/postcode.csv"); const seedPlacesPath = path.join(rootPath, "tests/seed/places/") const AttributeBaseSuite = require("./attribute_base.suite.js"); // Load models -const AttributeBase = require(path.join(rootPath, "app/models/attribute_base")); -const Postcode = require(path.join(rootPath, "app/models/postcode")); -const District = require(path.join(rootPath, "app/models/district")); -const Parish = require(path.join(rootPath, "app/models/parish")); -const County = require(path.join(rootPath, "app/models/county")); -const Ccg = require(path.join(rootPath, "app/models/ccg")); -const Constituency = require(path.join(rootPath, "app/models/constituency")); -const Nuts = require(path.join(rootPath, "app/models/nuts")); -const Ward = require(path.join(rootPath, "app/models/ward")); -const Outcode = require(path.join(rootPath, "app/models/outcode")); -const Place = require(path.join(rootPath, "app/models/place")); -const TerminatedPostcode = require(path.join(rootPath, "app/models/terminated_postcode")); +const AttributeBase = require("../../app/models/attribute_base"); +const Postcode = require("../../app/models/postcode"); +const District = require("../../app/models/district"); +const Parish = require("../../app/models/parish"); +const County = require("../../app/models/county"); +const Ccg = require("../../app/models/ccg"); +const Constituency = require("../../app/models/constituency"); +const Nuts = require("../../app/models/nuts"); +const Ward = require("../../app/models/ward"); +const Outcode = require("../../app/models/outcode"); +const Place = require("../../app/models/place"); +const TerminatedPostcode = require("../../app/models/terminated_postcode"); +const Ced = require("../../app/models/ced"); const CSV_INDEX = { postcode: 2, @@ -525,44 +526,44 @@ function listDatabaseIndexes(cb) { module.exports = { // Data - config: config, - rootPath: rootPath, - seedPostcodePath: seedPostcodePath, + config, + rootPath, + seedPostcodePath, // Methods - allowsCORS: allowsCORS, - clearTestDb: clearTestDb, + allowsCORS, + clearTestDb, removeDiacritics: require("./remove_diacritics"), - inferIndexInfo: inferIndexInfo, - inferSchemaData: inferSchemaData, - sortByIndexColumns: sortByIndexColumns, - testOutcode: testOutcode, - randomOutcode: randomOutcode, - isPlaceObject: isPlaceObject, - randomPostcode: randomPostcode, - randomTerminatedPostcode: randomTerminatedPostcode, - randomLocation: randomLocation, - seedPostcodeDb: seedPostcodeDb, - seedTerminatedPostcodeDb: seedTerminatedPostcodeDb, - clearPostcodeDb: clearPostcodeDb, - clearTerminatedPostcodesDb: clearTerminatedPostcodesDb, - isOutcodeObject: isOutcodeObject, - validCorsOptions: validCorsOptions, - isPostcodeObject: isPostcodeObject, - isTerminatedPostcodeObject: isTerminatedPostcodeObject, - isRawTerminatedPostcodeObject: isRawTerminatedPostcodeObject, - isRawPlaceObject: isRawPlaceObject, - isPostcodeWithDistanceObject: isPostcodeWithDistanceObject, - jsonpResponseBody: jsonpResponseBody, - getCustomRelation: getCustomRelation, - isRawOutcodeObject: isRawOutcodeObject, - isRawPostcodeObject: isRawPostcodeObject, - isRawPostcodeObjectWithFC: isRawPostcodeObjectWithFC, - isRawPostcodeObjectWithFCandDistance: isRawPostcodeObjectWithFCandDistance, - lookupRandomPostcode: lookupRandomPostcode, - listDatabaseRelations: listDatabaseRelations, - listDatabaseIndexes: listDatabaseIndexes, - locationWithNearbyPostcodes: locationWithNearbyPostcodes, + inferIndexInfo, + inferSchemaData, + sortByIndexColumns, + testOutcode, + randomOutcode, + isPlaceObject, + randomPostcode, + randomTerminatedPostcode, + randomLocation, + seedPostcodeDb, + seedTerminatedPostcodeDb, + clearPostcodeDb, + clearTerminatedPostcodesDb, + isOutcodeObject, + validCorsOptions, + isPostcodeObject, + isTerminatedPostcodeObject, + isRawTerminatedPostcodeObject, + isRawPlaceObject, + isPostcodeWithDistanceObject, + jsonpResponseBody, + getCustomRelation, + isRawOutcodeObject, + isRawPostcodeObject, + isRawPostcodeObjectWithFC, + isRawPostcodeObjectWithFCandDistance, + lookupRandomPostcode, + listDatabaseRelations, + listDatabaseIndexes, + locationWithNearbyPostcodes, // Test suites AttributeBaseSuite, @@ -574,19 +575,20 @@ module.exports = { timeout: require("../../app/lib/timeout.js"), // Models - Base: Base, - AttributeBase: AttributeBase, - Postcode: Postcode, - District: District, - Parish: Parish, - County: County, - Ccg: Ccg, - Constituency: Constituency, - Nuts: Nuts, - Ward: Ward, - Outcode: Outcode, - Place: Place, - TerminatedPostcode: TerminatedPostcode, + Base, + AttributeBase, + Postcode, + District, + Parish, + County, + Ccg, + Ced, + Constituency, + Nuts, + Ward, + Outcode, + Place, + TerminatedPostcode, seedPaths: { postcodes: path.join(rootPath, "/tests/seed/postcodes.csv"), customRelation: path.join(rootPath, "/tests/seed/customRelation.csv") From 3fcb42b1b7eb9d30f8af738eb215dd361c1276f4 Mon Sep 17 00:00:00 2001 From: Chris Blanchard Date: Thu, 29 Nov 2018 15:31:57 +0100 Subject: [PATCH 06/36] Make 10.0.0 version in changelog, ratchet minimum node version --- .circleci/config.yml | 139 ++++++++++++++++--------------------------- CHANGELOG.md | 4 ++ README.md | 2 +- package.json | 3 + 4 files changed, 59 insertions(+), 89 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index bd2bd710..b195df83 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,91 +1,54 @@ +--- +default_steps: &default_steps + steps: + - run: + command: "dockerize -wait tcp://localhost:5432 -timeout 2m" + name: "Wait for db" + - checkout + - run: "sudo apt-get update" + - run: "sudo apt install postgresql-client" + - run: "POSTGRES_USER=postgres npm run setup_test_db" + - restore_cache: + keys: + - "nodemodules-deps-{{ checksum \"package-lock.json\" }}" + - run: "npm install" + - save_cache: + key: "nodemodules-deps-{{ checksum \"package-lock.json\" }}" + paths: + - node_modules/ + - run: "npm run test" + - run: "npm run coverage" +jobs: + node10: + <<: *default_steps + docker: + - environment: + - PGHOST=localhost + - PGUSER=postgres + image: "circleci/node:10" + - image: "circleci/postgres:9.6-alpine-postgis" + node11: + <<: *default_steps + docker: + - environment: + - PGHOST=localhost + - PGUSER=postgres + image: "circleci/node:11" + - image: "circleci/postgres:9.6-alpine-postgis" + node8: + <<: *default_steps + docker: + - environment: + - PGHOST=localhost + - PGUSER=postgres + image: "circleci/node:8" + - image: "circleci/postgres:9.6-alpine-postgis" version: 2 -jobs: - node6: - docker: - - image: circleci/node:6.10 - environment: - - PGHOST=localhost - - PGUSER=postgres - - image: circleci/postgres:9.6-alpine-postgis - steps: - - run: - name: Wait for db - command: dockerize -wait tcp://localhost:5432 -timeout 2m - - checkout - - run: sudo apt-get update - - run: sudo apt install postgresql-client - - run: POSTGRES_USER=postgres npm run setup_test_db - - restore_cache: - keys: - - nodemodules-deps-{{ checksum "package-lock.json" }} - - nodemodules-deps- - - run: npm install - - save_cache: - key: nodemodules-deps-{{ checksum "package-lock.json" }} - paths: - - node_modules/ - - run: npm run test - - run: npm run coverage - - - node8: - docker: - - image: circleci/node:8.8 - environment: - - PGHOST=localhost - - PGUSER=postgres - - image: circleci/postgres:9.6-alpine-postgis - steps: - - run: - name: Wait for db - command: dockerize -wait tcp://localhost:5432 -timeout 2m - - checkout - - run: sudo apt-get update - - run: sudo apt install postgresql-client - - run: POSTGRES_USER=postgres npm run setup_test_db - - restore_cache: - keys: - - nodemodules-deps-{{ checksum "package-lock.json" }} - - nodemodules-deps- - - run: npm install - - save_cache: - key: nodemodules-deps-{{ checksum "package-lock.json" }} - paths: - - node_modules/ - - run: npm run test - - run: npm run coverage - node10: - docker: - - image: circleci/node:10 - environment: - - PGHOST=localhost - - PGUSER=postgres - - image: circleci/postgres:9.6-alpine-postgis - steps: - - run: - name: Wait for db - command: dockerize -wait tcp://localhost:5432 -timeout 2m - - checkout - - run: sudo apt-get update - - run: sudo apt install postgresql-client - - run: POSTGRES_USER=postgres npm run setup_test_db - - restore_cache: - keys: - - nodemodules-deps-{{ checksum "package-lock.json" }} - - nodemodules-deps- - - run: npm install - - save_cache: - key: nodemodules-deps-{{ checksum "package-lock.json" }} - paths: - - node_modules/ - - run: npm run test - - run: npm run coverage - -workflows: - version: 2 - node6_node8_node10: - jobs: - - node6 +workflows: + supported_nodes: + jobs: - node8 - node10 - \ No newline at end of file + - node11 + version: 2 + diff --git a/CHANGELOG.md b/CHANGELOG.md index d1fe8c11..c25b08f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ Any changes, including backwards incompatible changes will be listed here +## 10.0.0 (29/11/2018) + +- *Breaking Changes* Only node.js 8 LTS and above supported + ## 9.0.3 (26/10/2018) Minor updates and fixes. Many related to docker improvements diff --git a/README.md b/README.md index 9322e601..2e90456b 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Documentation can be found at [postcodes.io](http://postcodes.io) Please make sure you have the following available: -- Node.js v6.x [(Instructions)](http://nodejs.org/) +- Node.js v8.9 [(Instructions)](http://nodejs.org/) - Postgres (9.5 or greater) - PostGIS extension [(Instructions)](http://postgis.net/install) diff --git a/package.json b/package.json index 5668e157..e8805b79 100644 --- a/package.json +++ b/package.json @@ -85,5 +85,8 @@ "ignore": [ "csv" ] + }, + "engines": { + "node": ">=8.9.0" } } From 3ae972855dbd0a4d265a7bc434b31cd54a47f9b0 Mon Sep 17 00:00:00 2001 From: Chris Blanchard Date: Thu, 29 Nov 2018 16:34:22 +0100 Subject: [PATCH 07/36] Clean up --- tests/helper/index.js | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/tests/helper/index.js b/tests/helper/index.js index f1fc768c..cb95237c 100644 --- a/tests/helper/index.js +++ b/tests/helper/index.js @@ -1,17 +1,16 @@ "use strict"; -const util = require("util"); -const path = require("path"); +const { inherits } = require("util"); +const { resolve, join } = require("path"); const async = require("async"); -const assert = require("chai").assert; +const { assert } = require("chai"); const randomString = require("random-string"); -const rootPath = path.join(__dirname, "../../"); const env = process.env.NODE_ENV || "development"; const NO_RELOAD_DB = !!process.env.NO_RELOAD_DB; const Base = require("../../app/models"); const config = require("../..//config/config")(env); -const seedPostcodePath = path.join(rootPath, "tests/seed/postcode.csv"); -const seedPlacesPath = path.join(rootPath, "tests/seed/places/") +const seedPostcodePath = resolve(__dirname, "../seed/postcode.csv"); +const seedPlacesPath = join(__dirname, "../seed/places/") const AttributeBaseSuite = require("./attribute_base.suite.js"); // Load models @@ -29,25 +28,20 @@ const Place = require("../../app/models/place"); const TerminatedPostcode = require("../../app/models/terminated_postcode"); const Ced = require("../../app/models/ced"); -const CSV_INDEX = { +const CSV_INDEX = Object.freeze({ postcode: 2, northings: 10, eastings: 9 -}; +}); /** * Clears the test database - * - Skips if NUKE_AFTER + * - Skips if PRESERVE_DB * @param {function} callback * @return {undefined} */ const clearTestDb = (callback) => { - if (process.env.PRESERVE_DB !== undefined) { - console.log("Tests concluded. Opted for preserving testing database...") - return callback(null) - }; - - console.log("Tests concluded, wiping DB..."); + if (process.env.PRESERVE_DB !== undefined) return callback(null) const instructions = []; instructions.push(Postcode._destroyRelation.bind(Postcode)); instructions.push(TerminatedPostcode._destroyRelation.bind(TerminatedPostcode)); @@ -157,7 +151,7 @@ function getCustomRelation () { Base.Base.call(this, relationName, schema); } - util.inherits(CustomRelation, Base.Base); + inherits(CustomRelation, Base.Base); return new CustomRelation(); } @@ -527,7 +521,6 @@ function listDatabaseIndexes(cb) { module.exports = { // Data config, - rootPath, seedPostcodePath, // Methods @@ -590,7 +583,7 @@ module.exports = { Place, TerminatedPostcode, seedPaths: { - postcodes: path.join(rootPath, "/tests/seed/postcodes.csv"), - customRelation: path.join(rootPath, "/tests/seed/customRelation.csv") + postcodes: seedPostcodePath, + customRelation: resolve(__dirname, "../seed/customRelation.csv") } }; From fc9b18f17f3d6e9085f9733ce976f891bf94eca2 Mon Sep 17 00:00:00 2001 From: Chris Blanchard Date: Fri, 30 Nov 2018 12:15:28 +0100 Subject: [PATCH 08/36] Move db base model into base.js --- app/models/attribute_base.js | 2 +- app/models/base.js | 261 ++++++++++++++++++++++++++++ app/models/index.js | 274 ++---------------------------- app/models/outcode.js | 2 +- app/models/place.js | 2 +- app/models/postcode.js | 2 +- app/models/terminated_postcode.js | 2 +- bin/import.js | 2 +- bin/import_oson.js | 2 +- bin/rebuild_support_tables.js | 4 +- config/db.js | 2 +- tests/base.unit.js | 10 +- tests/helper/index.js | 67 +++----- tests/helper/setup.js | 47 +++++ 14 files changed, 364 insertions(+), 315 deletions(-) create mode 100644 app/models/base.js create mode 100644 tests/helper/setup.js diff --git a/app/models/attribute_base.js b/app/models/attribute_base.js index ab4a36e3..6c225c47 100644 --- a/app/models/attribute_base.js +++ b/app/models/attribute_base.js @@ -11,7 +11,7 @@ const fs = require("fs"); const util = require("util"); const path = require("path"); const async = require("async"); -const Base = require("./index").Base; +const Base = require("./base").Base; const requiredAttributes = { "code": "VARCHAR(32) NOT NULL UNIQUE", diff --git a/app/models/base.js b/app/models/base.js new file mode 100644 index 00000000..e1875e8b --- /dev/null +++ b/app/models/base.js @@ -0,0 +1,261 @@ +"use strict"; + +const fs = require("fs"); +const pg = require("pg"); +const copyFrom = require("pg-copy-streams").from; +const async = require("async"); +const csv = require("csv"); +const env = process.env.NODE_ENV || "development"; +const defaults = require("../../config/config.js")(env); +const config = defaults.postgres; + +// Instantiate postgres client pool +const pool = pg.Pool(config); + +// All models inherit from base +// Requires schema and relation name +function Base (relation, schema, indexes) { + this.relation = relation; + this.schema = schema; + this.indexes = indexes; +} + +Base.prototype._query = function (query, params, callback) { + if (typeof params === "function") { + callback = params; + params = []; + } + + if (!config) { + return callback( + new Error("Database connection not yet configured."), + null + ); + } + + pool.connect((error, client, done) => { + if (error) return callback(error, null); + client.query(query, params, (error, result) => { + callback(error, result); + done(); + }); + }); +}; + +Base.prototype._create = function (newRecord, callback) { + const query = [`INSERT INTO ${this.relation}`]; + const cols = []; + const values = []; + + for (let col in newRecord) { + if (!this.schema[col]) { + return callback( + new Error("Could not create record.", col, "does not exist"), + null + ); + } + cols.push(col); + values.push(newRecord[col]); + } + + query.push(`(${ cols.join(", ") })`); + query.push(`VALUES (${ dollarise(values) })`); + this._query(query.join(" "), values, callback); +}; + +Base.prototype.all = function (callback) { + this._query(`SELECT * FROM ${this.relation}`, callback); +}; + +Base.prototype.clear = function (callback) { + this._query(`DELETE FROM ${this.relation}`, callback); +}; + +Base.prototype._createRelation = function (callback) { + const query = [`CREATE TABLE IF NOT EXISTS ${this.relation}`]; + const columns = []; + const schema = this.schema; + + for (let column in schema) { + columns.push(column + " " + schema[column]); + } + + query.push(`(${ columns.join(", ") })`); + + this._query(query.join(" "), callback); +}; + +Base.prototype._destroyRelation = function (callback) { + this._query(`DROP TABLE IF EXISTS ${this.relation} CASCADE`, callback); +}; + +Base.prototype.createIndexes = function (callback) { + const indexExecution = []; + const returnInstruction = index => { + const instruction = ["CREATE"]; + if (index.unique) { + instruction.push("UNIQUE INDEX"); + } else { + instruction.push("INDEX"); + } + instruction.push(`ON ${this.relation}`); + instruction.push(`USING ${(index.type || "BTREE")}`); + if (index.opClass) { + instruction.push(`(${index.column} ${index.opClass})`); + } else { + instruction.push(`(${index.column})`); + } + return instruction.join(" "); + }; + + for (let i = 0; i < this.indexes.length; i += 1) { + indexExecution.push(returnInstruction(this.indexes[i])); + } + + async.series(indexExecution.map(instruction => { + return callback => this._query(instruction, callback); + }), callback); +}; + +Base.prototype._csvSeed = function (options, callback) { + let filepath = options.filepath; + if (Array.isArray(options.filepath)) { + filepath = options.filepath; + } else { + filepath = [options.filepath]; + } + const columns = options.columns; + const transform = options.transform || (row => row); + const query = `COPY ${this.relation} (${columns}) FROM STDIN DELIMITER ',' CSV`; + + async.eachLimit(filepath, 5, (filepath, cb) => { + pool.connect((error, client, done) => { + const pgStream = client.query(copyFrom(query)) + .on("end", () => { + done(); + return cb(); + }) + .on("error", error => { + done(); + return cb(error); + }); + fs.createReadStream(filepath, {encoding: "utf8"}) + .pipe(csv.parse()) + .pipe(csv.transform(transform)) + .pipe(csv.stringify()) + .pipe(pgStream); + }); + }, callback); +}; + +Base.prototype._destroyAll = function (callback) { + if (process.env.NODE_ENV !== "test") { + return callback( + `Aborting. Tried to wipe database outside of testing environment` + ); + } + + this._query("drop schema public cascade", (error) => { + if (error) return callback(error, null); + this._query("create public schema", callback); + }); +}; + +Base.prototype._getClient = function (callback) { + pool.connect(callback); +}; + +const dollarise = values => values.map((_, i) => `$${i + 1}`).join(", "); + +function populateLocation(callback) { + /* jshint validthis: true */ + const query = ` + UPDATE + ${this.relation} + SET + location=ST_GeogFromText( + 'SRID=4326;POINT(' || longitude || ' ' || latitude || ')' + ) + WHERE + northings!=0 + AND EASTINGS!=0 + `; + this._query(query, callback); +} + +/** + * Wraps a model and returns a function that when invoked: + * - Creates a temporary name for the Model relation + * - Builds model table with `_setupTable` + * - Drop any existing archived table associated with model + * - Rename existing table (if exists) to archived table + * - Rename new table to active current + * @param {Object} Model + * @return {Function} + */ +const setupWithTableSwap = (Model, sourceFile) => { + return callback => { + const originalName = Model.relation; + const tempName = toTempName(originalName); + const archivedName = toArchiveName(originalName); + + // Create and populate new relation under temporary name + Model.relation = tempName; + + const args = [(error) => { + if (error) return callback(error); + // Restore model name + Model.relation = originalName; + async.forEachSeries([ + // Drop existing archived table + `DROP TABLE IF EXISTS ${archivedName}`, + // Designate current table as archived table + `ALTER TABLE IF EXISTS ${originalName} RENAME TO ${archivedName}`, + // Swap in new table + `ALTER TABLE ${tempName} RENAME TO ${originalName}`, + ], Model._query.bind(Model), callback); + }]; + + if (sourceFile) args.unshift(sourceFile); + + Model._setupTable.apply(Model, args); + }; +}; + +const toTempName = name => `${name}_temp`; +const toArchiveName = name => `${name}_archived`; + +const indexCache = {}; +let ONSPD_CSV_SCHEMA; + +/** + * Returns the index location of an ONSPD param, -1 if not found + * @param {string} code ONSPD column code e.g. `pcd` + * @return {number} + */ +const indexFor = code => { + if (indexCache[code] !== undefined) return indexCache[code]; + if (ONSPD_CSV_SCHEMA === undefined) ONSPD_CSV_SCHEMA = require("../../data/onspd_schema.json"); + indexCache[code] = ONSPD_CSV_SCHEMA.reduce((result, elem, i) => { + if (elem.code === code) return i; + return result; + }, -1); + return indexCache[code]; +}; + +/** + * Extracts the value for `code` from an ONSPD CSV record + * @param {string[]} row + * @param {string} code + * @return {string} + */ +const extractOnspdVal = exports.extractOnspdVal = (row, code) => row[indexFor(code)]; + +module.exports = { + Base, + populateLocation, + setupWithTableSwap, + toTempName, + toArchiveName, + extractOnspdVal, +}; diff --git a/app/models/index.js b/app/models/index.js index e1875e8b..b35f6e2d 100644 --- a/app/models/index.js +++ b/app/models/index.js @@ -1,261 +1,19 @@ -"use strict"; - -const fs = require("fs"); -const pg = require("pg"); -const copyFrom = require("pg-copy-streams").from; -const async = require("async"); -const csv = require("csv"); -const env = process.env.NODE_ENV || "development"; -const defaults = require("../../config/config.js")(env); -const config = defaults.postgres; - -// Instantiate postgres client pool -const pool = pg.Pool(config); - -// All models inherit from base -// Requires schema and relation name -function Base (relation, schema, indexes) { - this.relation = relation; - this.schema = schema; - this.indexes = indexes; -} - -Base.prototype._query = function (query, params, callback) { - if (typeof params === "function") { - callback = params; - params = []; - } - - if (!config) { - return callback( - new Error("Database connection not yet configured."), - null - ); - } - - pool.connect((error, client, done) => { - if (error) return callback(error, null); - client.query(query, params, (error, result) => { - callback(error, result); - done(); - }); - }); -}; - -Base.prototype._create = function (newRecord, callback) { - const query = [`INSERT INTO ${this.relation}`]; - const cols = []; - const values = []; - - for (let col in newRecord) { - if (!this.schema[col]) { - return callback( - new Error("Could not create record.", col, "does not exist"), - null - ); - } - cols.push(col); - values.push(newRecord[col]); - } - - query.push(`(${ cols.join(", ") })`); - query.push(`VALUES (${ dollarise(values) })`); - this._query(query.join(" "), values, callback); -}; - -Base.prototype.all = function (callback) { - this._query(`SELECT * FROM ${this.relation}`, callback); -}; - -Base.prototype.clear = function (callback) { - this._query(`DELETE FROM ${this.relation}`, callback); -}; - -Base.prototype._createRelation = function (callback) { - const query = [`CREATE TABLE IF NOT EXISTS ${this.relation}`]; - const columns = []; - const schema = this.schema; - - for (let column in schema) { - columns.push(column + " " + schema[column]); - } - - query.push(`(${ columns.join(", ") })`); - - this._query(query.join(" "), callback); -}; - -Base.prototype._destroyRelation = function (callback) { - this._query(`DROP TABLE IF EXISTS ${this.relation} CASCADE`, callback); -}; - -Base.prototype.createIndexes = function (callback) { - const indexExecution = []; - const returnInstruction = index => { - const instruction = ["CREATE"]; - if (index.unique) { - instruction.push("UNIQUE INDEX"); - } else { - instruction.push("INDEX"); - } - instruction.push(`ON ${this.relation}`); - instruction.push(`USING ${(index.type || "BTREE")}`); - if (index.opClass) { - instruction.push(`(${index.column} ${index.opClass})`); - } else { - instruction.push(`(${index.column})`); - } - return instruction.join(" "); - }; - - for (let i = 0; i < this.indexes.length; i += 1) { - indexExecution.push(returnInstruction(this.indexes[i])); - } - - async.series(indexExecution.map(instruction => { - return callback => this._query(instruction, callback); - }), callback); -}; - -Base.prototype._csvSeed = function (options, callback) { - let filepath = options.filepath; - if (Array.isArray(options.filepath)) { - filepath = options.filepath; - } else { - filepath = [options.filepath]; - } - const columns = options.columns; - const transform = options.transform || (row => row); - const query = `COPY ${this.relation} (${columns}) FROM STDIN DELIMITER ',' CSV`; - - async.eachLimit(filepath, 5, (filepath, cb) => { - pool.connect((error, client, done) => { - const pgStream = client.query(copyFrom(query)) - .on("end", () => { - done(); - return cb(); - }) - .on("error", error => { - done(); - return cb(error); - }); - fs.createReadStream(filepath, {encoding: "utf8"}) - .pipe(csv.parse()) - .pipe(csv.transform(transform)) - .pipe(csv.stringify()) - .pipe(pgStream); - }); - }, callback); -}; - -Base.prototype._destroyAll = function (callback) { - if (process.env.NODE_ENV !== "test") { - return callback( - `Aborting. Tried to wipe database outside of testing environment` - ); - } - - this._query("drop schema public cascade", (error) => { - if (error) return callback(error, null); - this._query("create public schema", callback); - }); -}; - -Base.prototype._getClient = function (callback) { - pool.connect(callback); -}; - -const dollarise = values => values.map((_, i) => `$${i + 1}`).join(", "); - -function populateLocation(callback) { - /* jshint validthis: true */ - const query = ` - UPDATE - ${this.relation} - SET - location=ST_GeogFromText( - 'SRID=4326;POINT(' || longitude || ' ' || latitude || ')' - ) - WHERE - northings!=0 - AND EASTINGS!=0 - `; - this._query(query, callback); -} - -/** - * Wraps a model and returns a function that when invoked: - * - Creates a temporary name for the Model relation - * - Builds model table with `_setupTable` - * - Drop any existing archived table associated with model - * - Rename existing table (if exists) to archived table - * - Rename new table to active current - * @param {Object} Model - * @return {Function} - */ -const setupWithTableSwap = (Model, sourceFile) => { - return callback => { - const originalName = Model.relation; - const tempName = toTempName(originalName); - const archivedName = toArchiveName(originalName); - - // Create and populate new relation under temporary name - Model.relation = tempName; - - const args = [(error) => { - if (error) return callback(error); - // Restore model name - Model.relation = originalName; - async.forEachSeries([ - // Drop existing archived table - `DROP TABLE IF EXISTS ${archivedName}`, - // Designate current table as archived table - `ALTER TABLE IF EXISTS ${originalName} RENAME TO ${archivedName}`, - // Swap in new table - `ALTER TABLE ${tempName} RENAME TO ${originalName}`, - ], Model._query.bind(Model), callback); - }]; - - if (sourceFile) args.unshift(sourceFile); - - Model._setupTable.apply(Model, args); - }; -}; - -const toTempName = name => `${name}_temp`; -const toArchiveName = name => `${name}_archived`; - -const indexCache = {}; -let ONSPD_CSV_SCHEMA; - -/** - * Returns the index location of an ONSPD param, -1 if not found - * @param {string} code ONSPD column code e.g. `pcd` - * @return {number} - */ -const indexFor = code => { - if (indexCache[code] !== undefined) return indexCache[code]; - if (ONSPD_CSV_SCHEMA === undefined) ONSPD_CSV_SCHEMA = require("../../data/onspd_schema.json"); - indexCache[code] = ONSPD_CSV_SCHEMA.reduce((result, elem, i) => { - if (elem.code === code) return i; - return result; - }, -1); - return indexCache[code]; -}; - -/** - * Extracts the value for `code` from an ONSPD CSV record - * @param {string[]} row - * @param {string} code - * @return {string} - */ -const extractOnspdVal = exports.extractOnspdVal = (row, code) => row[indexFor(code)]; +"use strict"; module.exports = { - Base, - populateLocation, - setupWithTableSwap, - toTempName, - toArchiveName, - extractOnspdVal, + Base: require("./base.js").Base, + AttributeBase: require("./attribute_base"), + Postcode: require("./postcode"), + District: require("./district"), + Parish: require("./parish"), + County: require("./county"), + Ccg: require("./ccg"), + Constituency: require("./constituency"), + Nuts: require("./nuts"), + Ward: require("./ward"), + Outcode: require("./outcode"), + Place: require("./place"), + TerminatedPostcode: require("./terminated_postcode"), + Ced: require("./ced"), }; + diff --git a/app/models/outcode.js b/app/models/outcode.js index 4c5b5d1d..fc2635bb 100644 --- a/app/models/outcode.js +++ b/app/models/outcode.js @@ -3,7 +3,7 @@ const util = require("util"); const path = require("path"); const async = require("async"); -const Base = require("./index").Base; +const Base = require("./base").Base; const env = process.env.NODE_ENV || "development"; const Postcode = require("./postcode"); const defaults = require(path.join(__dirname, "../../config/config.js"))(env).defaults; diff --git a/app/models/place.js b/app/models/place.js index a70504d2..71c5bf2d 100644 --- a/app/models/place.js +++ b/app/models/place.js @@ -5,7 +5,7 @@ const path = require("path"); const util = require("util"); const async = require("async"); const OSPoint = require("ospoint"); -const Base = require("./index").Base; +const Base = require("./base").Base; const QueryStream = require("pg-query-stream"); const env = process.env.NODE_ENV || "development"; const escapeRegex = require("escape-string-regexp"); diff --git a/app/models/postcode.js b/app/models/postcode.js index cc9595d5..9efeb43b 100644 --- a/app/models/postcode.js +++ b/app/models/postcode.js @@ -3,7 +3,7 @@ const util = require("util"); const Pc = require("postcode"); const async = require("async"); -const { Base, populateLocation, extractOnspdVal } = require("./index"); +const { Base, populateLocation, extractOnspdVal } = require("./base"); const QueryStream = require("pg-query-stream"); const env = process.env.NODE_ENV || "development"; const { defaults } = require("../../config/config.js")(env); diff --git a/app/models/terminated_postcode.js b/app/models/terminated_postcode.js index 11b07d05..be13c136 100644 --- a/app/models/terminated_postcode.js +++ b/app/models/terminated_postcode.js @@ -1,7 +1,7 @@ "use strict"; const util = require("util"); -const { Base, populateLocation, extractOnspdVal } = require("./index"); +const { Base, populateLocation, extractOnspdVal } = require("./base"); const async = require("async"); const Pc = require("postcode"); diff --git a/bin/import.js b/bin/import.js index 3a5c9504..6af273c6 100755 --- a/bin/import.js +++ b/bin/import.js @@ -30,7 +30,7 @@ const Parish = require("../app/models/parish.js"); const Ccg = require("../app/models/ccg.js"); const Outcode = require("../app/models/outcode.js"); const Constituency = require("../app/models/constituency.js"); -const { toTempName, setupWithTableSwap } = require("../app/models/index.js"); +const { toTempName, setupWithTableSwap } = require("../app/models/base.js"); if (!sourceFile) { console.log("Aborting Import. No source file specified"); diff --git a/bin/import_oson.js b/bin/import_oson.js index c6d0b8b1..56788205 100755 --- a/bin/import_oson.js +++ b/bin/import_oson.js @@ -17,7 +17,7 @@ const fs = require("fs"); const async = require("async"); const prompt = require("prompt"); const Place = require("../app/models/place.js"); -const { toTempName, setupWithTableSwap } = require("../app/models/index.js"); +const { toTempName, setupWithTableSwap } = require("../app/models/base.js"); if (!sourceDirectory) { console.log("Aborting Import. No source directory specified"); diff --git a/bin/rebuild_support_tables.js b/bin/rebuild_support_tables.js index 51e988eb..168aaf78 100755 --- a/bin/rebuild_support_tables.js +++ b/bin/rebuild_support_tables.js @@ -4,7 +4,7 @@ var fs = require("fs"); var path = require("path"); var async = require("async"); var env = process.env.NODE_ENV || "development"; -var Base = require(path.join(__dirname, "../app/models")); +var Base = require(path.join(__dirname, "../app/models/base.js")); var config = require(path.join(__dirname, "../config/config"))(env); var District = require(path.join(__dirname, "../app/models/district.js")); var Ward = require(path.join(__dirname, "../app/models/ward.js")); @@ -39,4 +39,4 @@ setupSupportTables(function (error, result) { console.log("Completed update."); process.exit(0); } -}); \ No newline at end of file +}); diff --git a/config/db.js b/config/db.js index b2464926..a6895098 100644 --- a/config/db.js +++ b/config/db.js @@ -1,4 +1,4 @@ "use strict"; // Instantiate pg client pool by loading base model -module.exports = () => require("../app/models/index"); +module.exports = () => require("../app/models/base"); diff --git a/tests/base.unit.js b/tests/base.unit.js index 44a2e8b3..f5d309bf 100644 --- a/tests/base.unit.js +++ b/tests/base.unit.js @@ -1,11 +1,15 @@ "use strict"; const helper = require("./helper"); -const assert = require("chai").assert; +const { assert } = require("chai"); const { - toTempName, toArchiveName, setupWithTableSwap, getLocation, Base, + toTempName, + toArchiveName, + setupWithTableSwap, + getLocation, + Base, extractOnspdVal -} = require("../app/models/index.js"); +} = require("../app/models/base.js"); describe("Base model", function () { describe("Base model instance methods", function () { diff --git a/tests/helper/index.js b/tests/helper/index.js index cb95237c..cb7da877 100644 --- a/tests/helper/index.js +++ b/tests/helper/index.js @@ -7,26 +7,28 @@ const { assert } = require("chai"); const randomString = require("random-string"); const env = process.env.NODE_ENV || "development"; const NO_RELOAD_DB = !!process.env.NO_RELOAD_DB; -const Base = require("../../app/models"); -const config = require("../..//config/config")(env); +const config = require("../../config/config")(env); const seedPostcodePath = resolve(__dirname, "../seed/postcode.csv"); const seedPlacesPath = join(__dirname, "../seed/places/") const AttributeBaseSuite = require("./attribute_base.suite.js"); // Load models -const AttributeBase = require("../../app/models/attribute_base"); -const Postcode = require("../../app/models/postcode"); -const District = require("../../app/models/district"); -const Parish = require("../../app/models/parish"); -const County = require("../../app/models/county"); -const Ccg = require("../../app/models/ccg"); -const Constituency = require("../../app/models/constituency"); -const Nuts = require("../../app/models/nuts"); -const Ward = require("../../app/models/ward"); -const Outcode = require("../../app/models/outcode"); -const Place = require("../../app/models/place"); -const TerminatedPostcode = require("../../app/models/terminated_postcode"); -const Ced = require("../../app/models/ced"); +const { + Base, + AttributeBase, + Postcode, + District, + Parish, + County, + Ccg, + Constituency, + Nuts, + Ward, + Outcode, + Place, + TerminatedPostcode, + Ced, +} = require("../../app/models/index.js"); const CSV_INDEX = Object.freeze({ postcode: 2, @@ -34,29 +36,6 @@ const CSV_INDEX = Object.freeze({ eastings: 9 }); -/** - * Clears the test database - * - Skips if PRESERVE_DB - * @param {function} callback - * @return {undefined} - */ -const clearTestDb = (callback) => { - if (process.env.PRESERVE_DB !== undefined) return callback(null) - const instructions = []; - instructions.push(Postcode._destroyRelation.bind(Postcode)); - instructions.push(TerminatedPostcode._destroyRelation.bind(TerminatedPostcode)); - instructions.push(District._destroyRelation.bind(District)); - instructions.push(Parish._destroyRelation.bind(Parish)); - instructions.push(Nuts._destroyRelation.bind(Nuts)); - instructions.push(County._destroyRelation.bind(County)); - instructions.push(Constituency._destroyRelation.bind(Constituency)); - instructions.push(Ccg._destroyRelation.bind(Ccg)); - instructions.push(Ward._destroyRelation.bind(Ward)); - instructions.push(Outcode._destroyRelation.bind(Outcode)); - instructions.push(Place._destroyRelation.bind(Place)); - async.series(instructions, callback); -}; - // Infers columns schema from columnData const inferSchemaData = columnData => { const columnName = columnData.column_name; @@ -148,10 +127,10 @@ function getCustomRelation () { }; function CustomRelation() { - Base.Base.call(this, relationName, schema); + Base.call(this, relationName, schema); } - inherits(CustomRelation, Base.Base); + inherits(CustomRelation, Base); return new CustomRelation(); } @@ -486,7 +465,7 @@ const databaseRelationsQuery = ` // List relations in database function listDatabaseRelations(cb) { - Base.Base.prototype._query(databaseRelationsQuery, cb); + Base.prototype._query(databaseRelationsQuery, cb); } // Credit: https://stackoverflow.com/questions/6777456/list-all-index-names-column-names-and-its-table-name-of-a-postgresql-database @@ -515,7 +494,7 @@ const databaseIndexesQuery = ` // Lists indexes in database function listDatabaseIndexes(cb) { - Base.Base.prototype._query(databaseIndexesQuery, cb); + Base.prototype._query(databaseIndexesQuery, cb); } module.exports = { @@ -525,7 +504,7 @@ module.exports = { // Methods allowsCORS, - clearTestDb, + ...require("./setup.js"), removeDiacritics: require("./remove_diacritics"), inferIndexInfo, inferSchemaData, @@ -584,6 +563,6 @@ module.exports = { TerminatedPostcode, seedPaths: { postcodes: seedPostcodePath, - customRelation: resolve(__dirname, "../seed/customRelation.csv") + customRelation: join(__dirname, "../seed/customRelation.csv") } }; diff --git a/tests/helper/setup.js b/tests/helper/setup.js new file mode 100644 index 00000000..48f3936f --- /dev/null +++ b/tests/helper/setup.js @@ -0,0 +1,47 @@ +"use strict"; + +const { + Postcode, + District, + Parish, + County, + Ccg, + Constituency, + Nuts, + Ward, + Outcode, + Place, + TerminatedPostcode, + Ced, +} = require("../../app/models/index.js"); + +const async = require("async"); + +/** + * Clears the test database + * - Skips if PRESERVE_DB + * @param {function} callback + * @return {undefined} + */ +const clearTestDb = callback => { + if (process.env.PRESERVE_DB !== undefined) return callback(null); + const instructions = []; + instructions.push(Postcode._destroyRelation.bind(Postcode)); + instructions.push(TerminatedPostcode._destroyRelation.bind(TerminatedPostcode)); + instructions.push(District._destroyRelation.bind(District)); + instructions.push(Parish._destroyRelation.bind(Parish)); + instructions.push(Nuts._destroyRelation.bind(Nuts)); + instructions.push(County._destroyRelation.bind(County)); + instructions.push(Constituency._destroyRelation.bind(Constituency)); + instructions.push(Ccg._destroyRelation.bind(Ccg)); + instructions.push(Ward._destroyRelation.bind(Ward)); + instructions.push(Outcode._destroyRelation.bind(Outcode)); + instructions.push(Place._destroyRelation.bind(Place)); + instructions.push(Ced._destroyRelation.bind(Ced)); + async.series(instructions, callback); +}; + +module.exports = { + clearTestDb, +}; + From 03e3ef8f10da4833cab5ae6eba5c27dbfbf2c16b Mon Sep 17 00:00:00 2001 From: Chris Blanchard Date: Fri, 30 Nov 2018 17:08:35 +0100 Subject: [PATCH 09/36] Ignore sessions --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 6af3c404..0fb706fd 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ process.json log/ npm-debug.log *.swp +*.vim From 8a5f9fe9c735acf166d483d9c7cdbaca65b54e8b Mon Sep 17 00:00:00 2001 From: Chris Blanchard Date: Fri, 30 Nov 2018 23:04:29 +0100 Subject: [PATCH 10/36] Move out test scaffolding methods, add Ced to test setup --- bin/clearTestDb.sh | 1 + tests/helper/index.js | 64 +---------------------------------- tests/helper/setup.js | 77 ++++++++++++++++++++++++++++++++++--------- 3 files changed, 63 insertions(+), 79 deletions(-) diff --git a/bin/clearTestDb.sh b/bin/clearTestDb.sh index f287deaa..d2224cdb 100755 --- a/bin/clearTestDb.sh +++ b/bin/clearTestDb.sh @@ -3,6 +3,7 @@ const path = require("path"); const helper = require(path.join(__dirname, "../tests/helper")); +console.log("Cleaaring test DB"); helper.clearTestDb(error => { if (error) throw error; console.log("Done."); diff --git a/tests/helper/index.js b/tests/helper/index.js index cb7da877..21c57122 100644 --- a/tests/helper/index.js +++ b/tests/helper/index.js @@ -1,15 +1,12 @@ "use strict"; const { inherits } = require("util"); -const { resolve, join } = require("path"); +const { join } = require("path"); const async = require("async"); const { assert } = require("chai"); const randomString = require("random-string"); const env = process.env.NODE_ENV || "development"; -const NO_RELOAD_DB = !!process.env.NO_RELOAD_DB; const config = require("../../config/config")(env); -const seedPostcodePath = resolve(__dirname, "../seed/postcode.csv"); -const seedPlacesPath = join(__dirname, "../seed/places/") const AttributeBaseSuite = require("./attribute_base.suite.js"); // Load models @@ -135,59 +132,6 @@ function getCustomRelation () { return new CustomRelation(); } -function seedTerminatedPostcodeDb (callback) { - if (NO_RELOAD_DB) { - return callback(null); - } - const instructions = []; - instructions.push(function (callback) { - TerminatedPostcode._setupTable(seedPostcodePath, callback); - }); - async.series(instructions, callback); -} - - -function seedPostcodeDb (callback) { - if (NO_RELOAD_DB) { - return callback(null); - } - - const instructions = []; - instructions.push(function (callback) { - Postcode._setupTable(seedPostcodePath, callback); - }); - instructions.push(function (callback) { - TerminatedPostcode._setupTable(seedPostcodePath, callback); - }); - instructions.push(District._setupTable.bind(District)); - instructions.push(Parish._setupTable.bind(Parish)); - instructions.push(Nuts._setupTable.bind(Nuts)); - instructions.push(County._setupTable.bind(County)); - instructions.push(Constituency._setupTable.bind(Constituency)); - instructions.push(Ccg._setupTable.bind(Ccg)); - instructions.push(Ward._setupTable.bind(Ward)); - instructions.push(Outcode._setupTable.bind(Outcode)); - instructions.push(function (callback) { - Place._setupTable(seedPlacesPath, callback); - }); - - async.series(instructions, callback); -} - -function clearTerminatedPostcodesDb(callback, force) { - if (NO_RELOAD_DB) { - return callback(null); - } - TerminatedPostcode._destroyRelation(callback); -} - -// Runs before each test to clear test database -function clearPostcodeDb(callback, force) { - if (NO_RELOAD_DB) { - return callback(null); - } - Postcode._destroyRelation(callback); -} //Generates a random integer from 1 to max inclusive const getRandom = function (max) { @@ -500,7 +444,6 @@ function listDatabaseIndexes(cb) { module.exports = { // Data config, - seedPostcodePath, // Methods allowsCORS, @@ -515,10 +458,6 @@ module.exports = { randomPostcode, randomTerminatedPostcode, randomLocation, - seedPostcodeDb, - seedTerminatedPostcodeDb, - clearPostcodeDb, - clearTerminatedPostcodesDb, isOutcodeObject, validCorsOptions, isPostcodeObject, @@ -562,7 +501,6 @@ module.exports = { Place, TerminatedPostcode, seedPaths: { - postcodes: seedPostcodePath, customRelation: join(__dirname, "../seed/customRelation.csv") } }; diff --git a/tests/helper/setup.js b/tests/helper/setup.js index 48f3936f..fe93f430 100644 --- a/tests/helper/setup.js +++ b/tests/helper/setup.js @@ -1,5 +1,11 @@ "use strict"; +const async = require("async"); +const { join, resolve } = require("path"); +const NO_RELOAD_DB = !!process.env.NO_RELOAD_DB; +const seedPostcodePath = resolve(__dirname, "../seed/postcode.csv"); +const seedPlacesPath = join(__dirname, "../seed/places/") + const { Postcode, District, @@ -15,8 +21,6 @@ const { Ced, } = require("../../app/models/index.js"); -const async = require("async"); - /** * Clears the test database * - Skips if PRESERVE_DB @@ -25,23 +29,64 @@ const async = require("async"); */ const clearTestDb = callback => { if (process.env.PRESERVE_DB !== undefined) return callback(null); - const instructions = []; - instructions.push(Postcode._destroyRelation.bind(Postcode)); - instructions.push(TerminatedPostcode._destroyRelation.bind(TerminatedPostcode)); - instructions.push(District._destroyRelation.bind(District)); - instructions.push(Parish._destroyRelation.bind(Parish)); - instructions.push(Nuts._destroyRelation.bind(Nuts)); - instructions.push(County._destroyRelation.bind(County)); - instructions.push(Constituency._destroyRelation.bind(Constituency)); - instructions.push(Ccg._destroyRelation.bind(Ccg)); - instructions.push(Ward._destroyRelation.bind(Ward)); - instructions.push(Outcode._destroyRelation.bind(Outcode)); - instructions.push(Place._destroyRelation.bind(Place)); - instructions.push(Ced._destroyRelation.bind(Ced)); - async.series(instructions, callback); + async.parallel([ + Postcode, + TerminatedPostcode, + District, + Parish, + Nuts, + County, + Constituency, + Ccg, + Ward, + Outcode, + Place, + Ced, + ].map(m => m._destroyRelation.bind(m)), callback); +}; + +const seedTerminatedPostcodeDb = callback => { + if (NO_RELOAD_DB) return callback(null); + TerminatedPostcode._setupTable(seedPostcodePath, callback); +} + + +const seedPostcodeDb = callback => { + if (NO_RELOAD_DB) return callback(null); + async.series([ + cb => Postcode._setupTable(seedPostcodePath, cb), + cb => TerminatedPostcode._setupTable(seedPostcodePath, cb), + cb => Place._setupTable(seedPlacesPath, cb), + ].concat([ + District, + Parish, + Nuts, + County, + Constituency, + Ccg, + Ward, + Outcode, + Ced, + ].map(m => m._setupTable.bind(m))), callback); +}; + +const clearTerminatedPostcodesDb = callback => { + if (NO_RELOAD_DB) return callback(null); + TerminatedPostcode._destroyRelation(callback); +}; + +// Runs before each test to clear test database +const clearPostcodeDb = callback => { + if (NO_RELOAD_DB) return callback(null); + Postcode._destroyRelation(callback); }; module.exports = { clearTestDb, + seedPostcodePath, + seedTerminatedPostcodeDb, + seedPostcodeDb, + clearTerminatedPostcodesDb, + clearPostcodeDb, }; From bf22af8a8e276894c9c7c45cd590bb6e36474737 Mon Sep 17 00:00:00 2001 From: Chris Blanchard Date: Sun, 2 Dec 2018 11:04:42 +0100 Subject: [PATCH 11/36] Remove unused models, re-export with spread syntax instead --- tests/helper/index.js | 28 +++------------------------- 1 file changed, 3 insertions(+), 25 deletions(-) diff --git a/tests/helper/index.js b/tests/helper/index.js index 21c57122..dcb76954 100644 --- a/tests/helper/index.js +++ b/tests/helper/index.js @@ -14,17 +14,7 @@ const { Base, AttributeBase, Postcode, - District, - Parish, - County, - Ccg, - Constituency, - Nuts, - Ward, - Outcode, - Place, TerminatedPostcode, - Ced, } = require("../../app/models/index.js"); const CSV_INDEX = Object.freeze({ @@ -485,21 +475,9 @@ module.exports = { string: require("../../app/lib/string.js"), timeout: require("../../app/lib/timeout.js"), - // Models - Base, - AttributeBase, - Postcode, - District, - Parish, - County, - Ccg, - Ced, - Constituency, - Nuts, - Ward, - Outcode, - Place, - TerminatedPostcode, + // Load in models + ...require("../../app/models/index.js"), + seedPaths: { customRelation: join(__dirname, "../seed/customRelation.csv") } From c7dc70816aba40bd5ea2c78e28d4ca42cd75a97d Mon Sep 17 00:00:00 2001 From: Chris Blanchard Date: Mon, 3 Dec 2018 12:02:23 +0100 Subject: [PATCH 12/36] Move out pg helper methods --- tests/helper/index.js | 59 ++------------------------------- tests/helper/pg.js | 76 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 56 deletions(-) create mode 100644 tests/helper/pg.js diff --git a/tests/helper/index.js b/tests/helper/index.js index dcb76954..443405fe 100644 --- a/tests/helper/index.js +++ b/tests/helper/index.js @@ -377,60 +377,6 @@ function testOutcode(o) { ].forEach(prop => assert.property(o, prop)); } -// Credit: https://www.peterbe.com/plog/select-all-relations-in-postgresql -const databaseRelationsQuery = ` - SELECT - c.relname as "Name", - CASE c.relkind WHEN 'r' THEN 'table' - WHEN 'v' THEN 'view' - WHEN 'm' THEN 'materialized view' - WHEN 'i' THEN 'index' - WHEN 'S' THEN 'sequence' - WHEN 's' THEN 'special' - WHEN 'f' THEN 'foreign table' END as "Type" - FROM pg_catalog.pg_class c - LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace - WHERE c.relkind IN ('r','v','m','S','f','') - AND n.nspname <> 'pg_catalog' - AND n.nspname <> 'information_schema' - AND n.nspname !~ '^pg_toast' - AND pg_catalog.pg_table_is_visible(c.oid); -`; - -// List relations in database -function listDatabaseRelations(cb) { - Base.prototype._query(databaseRelationsQuery, cb); -} - -// Credit: https://stackoverflow.com/questions/6777456/list-all-index-names-column-names-and-its-table-name-of-a-postgresql-database -const databaseIndexesQuery = ` - SELECT i.relname as indname, - i.relowner as indowner, - idx.indrelid::regclass, - am.amname as indam, - idx.indkey, - ARRAY( - SELECT pg_get_indexdef(idx.indexrelid, k + 1, true) - FROM generate_subscripts(idx.indkey, 1) as k - ORDER BY k - ) as indkey_names, - idx.indexprs IS NOT NULL as indexprs, - idx.indpred IS NOT NULL as indpred - FROM pg_index as idx - JOIN pg_class as i - ON i.oid = idx.indexrelid - JOIN pg_am as am - ON i.relam = am.oid - JOIN pg_namespace as ns - ON ns.oid = i.relnamespace - AND ns.nspname = ANY(current_schemas(false)); -`; - -// Lists indexes in database -function listDatabaseIndexes(cb) { - Base.prototype._query(databaseIndexesQuery, cb); -} - module.exports = { // Data config, @@ -462,10 +408,11 @@ module.exports = { isRawPostcodeObjectWithFC, isRawPostcodeObjectWithFCandDistance, lookupRandomPostcode, - listDatabaseRelations, - listDatabaseIndexes, locationWithNearbyPostcodes, + // PG helper methods + ...require("./pg.js"), + // Test suites AttributeBaseSuite, diff --git a/tests/helper/pg.js b/tests/helper/pg.js new file mode 100644 index 00000000..bcb94251 --- /dev/null +++ b/tests/helper/pg.js @@ -0,0 +1,76 @@ +/** + * PG helper methods + */ + +const { Base } = require("../../app/models/index.js"); + +// Credit: https://www.peterbe.com/plog/select-all-relations-in-postgresql +const databaseRelationsQuery = ` + SELECT + c.relname as "Name", + CASE c.relkind WHEN 'r' THEN 'table' + WHEN 'v' THEN 'view' + WHEN 'm' THEN 'materialized view' + WHEN 'i' THEN 'index' + WHEN 'S' THEN 'sequence' + WHEN 's' THEN 'special' + WHEN 'f' THEN 'foreign table' END as "Type" + FROM pg_catalog.pg_class c + LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace + WHERE c.relkind IN ('r','v','m','S','f','') + AND n.nspname <> 'pg_catalog' + AND n.nspname <> 'information_schema' + AND n.nspname !~ '^pg_toast' + AND pg_catalog.pg_table_is_visible(c.oid); +`; + +/** + * Lists all relations + * + * @param cb + * @returns {undefined} + */ +const listDatabaseRelations = cb => { + if (cb) return Base.prototype._query(databaseRelationsQuery, cb); + return new Promise((resolve, reject) => { + Base.prototype._query(databaseRelationsQuery, (error, result) => { + if (error) return reject(error); + return resolve(result); + }); + }); +}; + +// Credit: https://stackoverflow.com/questions/6777456/list-all-index-names-column-names-and-its-table-name-of-a-postgresql-database +const databaseIndexesQuery = ` + SELECT i.relname as indname, + i.relowner as indowner, + idx.indrelid::regclass, + am.amname as indam, + idx.indkey, + ARRAY( + SELECT pg_get_indexdef(idx.indexrelid, k + 1, true) + FROM generate_subscripts(idx.indkey, 1) as k + ORDER BY k + ) as indkey_names, + idx.indexprs IS NOT NULL as indexprs, + idx.indpred IS NOT NULL as indpred + FROM pg_index as idx + JOIN pg_class as i + ON i.oid = idx.indexrelid + JOIN pg_am as am + ON i.relam = am.oid + JOIN pg_namespace as ns + ON ns.oid = i.relnamespace + AND ns.nspname = ANY(current_schemas(false)); +`; + +// Lists indexes in database +const listDatabaseIndexes = cb => { + Base.prototype._query(databaseIndexesQuery, cb); +}; + +module.exports = { + listDatabaseRelations, + listDatabaseIndexes, +}; + From cc69f30fac947603be629dd86e16a888e9ddbf25 Mon Sep 17 00:00:00 2001 From: Chris Blanchard Date: Mon, 3 Dec 2018 13:56:15 +0100 Subject: [PATCH 13/36] Move data table and outcode creation to own modules --- app/lib/setup.js | 52 +++++++++++++++++++++++++++++++ tests/setup.unit.js | 74 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+) create mode 100644 app/lib/setup.js create mode 100644 tests/setup.unit.js diff --git a/app/lib/setup.js b/app/lib/setup.js new file mode 100644 index 00000000..8dd585cc --- /dev/null +++ b/app/lib/setup.js @@ -0,0 +1,52 @@ +"use strict"; + +const { series } = require("async"); +const models = require("../models/index.js"); +const { Outcode } = models; + +// List of support tables generated from data/ directory +const SUPPORT_TABLES = [ + "Ccg", + "Ced", + "Constituency", + "County", + "District", + "Nuts", + "Parish", + "Ward", +].map(name => models[name]); + +/** + * Generates support tables in DB + * + * @returns {Promise} + */ +const setupSupportTables = () => { + return new Promise((resolve, reject) => { + series(SUPPORT_TABLES.map(m => m._setupTable.bind(m)), (error, result) => { + if (error) return reject(error); + return resolve(result); + }); + }); +}; + +/** + * Generates outcodes from existing postcodes relation + * + * @returns {Promise} + */ +const setupOutcodeTable = () => { + return new Promise((resolve, reject) => { + Outcode._setupTable((error, result) => { + if (error) return reject(error); + return resolve(result); + }); + }); +}; + +module.exports = { + setupSupportTables, + setupOutcodeTable, + SUPPORT_TABLES, +}; + diff --git a/tests/setup.unit.js b/tests/setup.unit.js new file mode 100644 index 00000000..3f31c49a --- /dev/null +++ b/tests/setup.unit.js @@ -0,0 +1,74 @@ +"use strict"; + +const { assert } = require("chai"); +const { + setupSupportTables, + setupOutcodeTable, + SUPPORT_TABLES, +} = require("../app/lib/setup.js"); +const { series, parallel } = require("async"); +const { + listDatabaseRelations, + clearPostcodeDb, + seedPostcodeDb, + Outcode, +} = require("./helper/index.js"); + +const clearSupportTables = () => { + return new Promise((resolve, reject) => { + const instructions = SUPPORT_TABLES.map(m => m._destroyRelation.bind(m)); + parallel(instructions, (error, result) => { + if (error) return reject(error); + return resolve(result); + }); + }); +}; + +describe("Setup methods", () => { + before(function (done) { + this.timeout(0); + series([ + clearPostcodeDb, + seedPostcodeDb + ], done); + }); + + after(clearPostcodeDb); + + describe("setupSupportTables", () => { + before(async () => { + await clearSupportTables() + }); + + after(async function () { + this.timeout(0); + await setupSupportTables(); + }); + + it ("creates support tables", async function () { + this.timeout(0); + const existing = await listDatabaseRelations(); + SUPPORT_TABLES.forEach(( { relation } ) => { + assert.isFalse(existing.rows.some(({ Name }) => Name === relation)); + }); + await setupSupportTables(); + const created = await listDatabaseRelations(); + SUPPORT_TABLES.forEach(( { relation } ) => { + assert.isTrue(created.rows.some(({ Name }) => Name === relation)); + }); + }); + }); + + describe("setupOutcodeTable", () => { + before(done => Outcode._destroyRelation(done)); + + it ("creates outcode table", async () => { + const existing = await listDatabaseRelations(); + assert.isFalse(existing.rows.some(({ Name }) => Name === "outcodes")); + await setupOutcodeTable(); + const created = await listDatabaseRelations(); + assert.isTrue(created.rows.some(({ Name }) => Name === "outcodes")); + }); + }); +}); + From f0f4028cc71c09652dc4036df6d84ad03cc754ca Mon Sep 17 00:00:00 2001 From: Chris Blanchard Date: Mon, 3 Dec 2018 14:14:17 +0100 Subject: [PATCH 14/36] Rewrite support table rebuild script --- bin/rebuild_support_tables.js | 57 ++++++++++++----------------------- 1 file changed, 20 insertions(+), 37 deletions(-) diff --git a/bin/rebuild_support_tables.js b/bin/rebuild_support_tables.js index 168aaf78..3d375350 100755 --- a/bin/rebuild_support_tables.js +++ b/bin/rebuild_support_tables.js @@ -1,42 +1,25 @@ #!/usr/bin/env node -var fs = require("fs"); -var path = require("path"); -var async = require("async"); -var env = process.env.NODE_ENV || "development"; -var Base = require(path.join(__dirname, "../app/models/base.js")); -var config = require(path.join(__dirname, "../config/config"))(env); -var District = require(path.join(__dirname, "../app/models/district.js")); -var Ward = require(path.join(__dirname, "../app/models/ward.js")); -var Nuts = require(path.join(__dirname, "../app/models/nuts.js")); -var County = require(path.join(__dirname, "../app/models/county.js")); -var Parish = require(path.join(__dirname, "../app/models/parish.js")); -var Ccg = require(path.join(__dirname, "../app/models/ccg.js")); -var Outcode = require(path.join(__dirname, "../app/models/outcode.js")); -var Constituency = require(path.join(__dirname, "../app/models/constituency.js")); +const { + setupSupportTables, + setupOutcodeTable, +} = require("../app/lib/setup.js"); -// Performing checks - -function setupSupportTables (callback) { - console.log("Setting up support tables..."); - var instructions = [ - District._setupTable.bind(District), - Constituency._setupTable.bind(Constituency), - County._setupTable.bind(County), - Ccg._setupTable.bind(Ccg), - Ward._setupTable.bind(Ward), - Parish._setupTable.bind(Parish), - Outcode._setupTable.bind(Outcode) - ]; - async.series(instructions, callback); -} - -setupSupportTables(function (error, result) { - if (error) { +const main = async () => { + try { + console.log("Rebuilding data tables"); + await setupSupportTables(); + console.log("Done"); + console.log("Rebuilding outcodes table"); + await setupOutcodeTable(); + console.log("Done"); + console.log("Completed rebuild"); + process.exit(0); + } catch (e) { console.log("An error occurred:", error); process.exit(1); - } else { - console.log("Completed update."); - process.exit(0); - } -}); + } +}; + +main(); + From 8c1dae53d9645b7176422967aa66e53ac6ef6db3 Mon Sep 17 00:00:00 2001 From: Chris Blanchard Date: Mon, 3 Dec 2018 15:41:10 +0100 Subject: [PATCH 15/36] Add CEDs to import --- bin/import.js | 64 ++++++++++++++++++--------------------------------- 1 file changed, 23 insertions(+), 41 deletions(-) diff --git a/bin/import.js b/bin/import.js index 6af273c6..537cfb3d 100755 --- a/bin/import.js +++ b/bin/import.js @@ -18,48 +18,28 @@ your previous dataset Type 'YES' to continue `; -const async = require("async"); const prompt = require("prompt"); -const Postcode = require("../app/models/postcode.js"); -const TerminatedPostcode = require("../app/models/terminated_postcode.js") -const District = require("../app/models/district.js"); -const Ward = require("../app/models/ward.js"); -const Nuts = require("../app/models/nuts.js"); -const County = require("../app/models/county.js"); -const Parish = require("../app/models/parish.js"); -const Ccg = require("../app/models/ccg.js"); -const Outcode = require("../app/models/outcode.js"); -const Constituency = require("../app/models/constituency.js"); +const { forEach, series } = require("async"); +const { + Postcode, + TerminatedPostcode, + Outcode, + Base, +} = require("../app/models/index.js"); const { toTempName, setupWithTableSwap } = require("../app/models/base.js"); +const { SUPPORT_TABLES, setupSupportTables } = require("../app/lib/setup.js"); if (!sourceFile) { console.log("Aborting Import. No source file specified"); return process.exit(1); } -const supportTables = [ - District, - Constituency, - County, - Ccg, - Ward, - Nuts, - Parish, -]; - -const streamedTables = [ - Postcode, - TerminatedPostcode, - Outcode, -]; - const dropTempTables = callback => { - console.log("Dropping any temporary tables"); - const queries = [] - .concat(streamedTables) - .concat(supportTables) - .map(Model => `DROP TABLE IF EXISTS ${toTempName(Model.relation)}`); - async.forEach(queries, Postcode._query.bind(Postcode), callback); + console.log("Dropping any temporary tables"); + const deletes = [Postcode, TerminatedPostcode, Outcode] + .concat(SUPPORT_TABLES) + .map(Model => `DROP TABLE IF EXISTS ${toTempName(Model.relation)}`); + forEach(deletes, Postcode._query.bind(Postcode), callback); }; const createPostgisExtension = callback => { @@ -82,12 +62,14 @@ const setupPostcodesTable = callback => { setupWithTableSwap(Postcode, sourceFile)(callback); }; -const setupSupportTables = callback => { +const setupDataTables = callback => { console.log("Setting up support tables..."); - async.series(supportTables.map(Model => { - console.log(`Building support table: ${Model.relation}...`); - return cb => Model._setupTable.call(Model, cb); - }), callback); + try { + const result = setupSupportTables(); + callback(null, result); + } catch (e) { + callback(e); + } }; prompt.start(); @@ -107,13 +89,13 @@ prompt.get([{ } const start = process.hrtime(); - async.series([ + series([ createPostgisExtension, dropTempTables, setupPostcodesTable, setupTerminatedPostcodesTable, - setupSupportTables, - setupOutcodeTable, + setupOutcodeTable, + setupDataTables, ], error => { if (error) { console.log("Unable to complete import process due to error:", JSON.stringify(error, 2, 2)); From 04a011b16d0317b0d4646b65bc524ad977b27c8e Mon Sep 17 00:00:00 2001 From: Chris Blanchard Date: Mon, 3 Dec 2018 15:52:50 +0100 Subject: [PATCH 16/36] Update dependencies --- CHANGELOG.md | 1 + package-lock.json | 840 ++++++++++++++++++++++++++++++++-------------- package.json | 16 +- 3 files changed, 591 insertions(+), 266 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c25b08f1..b705e918 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ Any changes, including backwards incompatible changes will be listed here ## 10.0.0 (29/11/2018) - *Breaking Changes* Only node.js 8 LTS and above supported +- Updated dependencies ## 9.0.3 (26/10/2018) diff --git a/package-lock.json b/package-lock.json index 33ae0def..81ef38c2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -164,36 +164,36 @@ }, "dependencies": { "mime-db": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" + "version": "1.37.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", + "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==" }, "mime-types": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", - "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "version": "2.1.21", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", + "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", "requires": { - "mime-db": "~1.33.0" + "mime-db": "~1.37.0" } } } }, "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.6.1.tgz", + "integrity": "sha512-ZoJjft5B+EJBjUyu9C9Hc0OZyPZSSlOF+plzouTrg6UlA8f+e/n8NIgBFG/9tppJtpPWfthHakK7juJdNDODww==", "dev": true, "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", + "fast-deep-equal": "^2.0.1", "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" } }, "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", "dev": true, "requires": { "color-convert": "^1.9.0" @@ -202,7 +202,7 @@ "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "integrity": "sha1-vNZ5HqWuCXJeF+WtmIE0zUCz2RE=", "dev": true, "requires": { "sprintf-js": "~1.0.2" @@ -210,14 +210,17 @@ }, "array-flatten": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "resolved": "http://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, "asn1": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", - "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", - "dev": true + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, + "requires": { + "safer-buffer": "~2.1.0" + } }, "assert-plus": { "version": "1.0.0", @@ -234,7 +237,7 @@ "async": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", - "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", + "integrity": "sha1-skWiPKcZMAROxT+kaqAKPofGphA=", "requires": { "lodash": "^4.17.10" }, @@ -249,7 +252,7 @@ "async-listener": { "version": "0.6.9", "resolved": "https://registry.npmjs.org/async-listener/-/async-listener-0.6.9.tgz", - "integrity": "sha512-E7Z2/QMs0EPt/o9wpYO/J3hmMCDdr1aVDS3ttlur5D5JlZtxhfuOwi4e7S8zbYIxA5qOOYdxfqGj97XAfdNvkQ==", + "integrity": "sha1-UbyV5BCVQX8zki+03uTyMrMiZIg=", "requires": { "semver": "^5.3.0", "shimmer": "^1.1.0" @@ -275,9 +278,9 @@ "dev": true }, "aws4": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz", - "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", "dev": true }, "balanced-match": { @@ -286,19 +289,25 @@ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "basic-auth": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.0.tgz", - "integrity": "sha1-AV2z81PgLlY3d1X5YnQuiYHnu7o=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha1-uZgnm/R844NEtPPPkW1Gebv1Hjo=", "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "5.1.2" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } } }, "bcrypt-pbkdf": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", - "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", "dev": true, - "optional": true, "requires": { "tweetnacl": "^0.14.3" } @@ -344,9 +353,16 @@ "browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "integrity": "sha1-uqVZ7hTO1zRSIputcyZGfGH6vWA=", "dev": true }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true, + "optional": true + }, "buffer-writer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-1.0.1.tgz", @@ -391,7 +407,7 @@ "chalk": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "integrity": "sha1-GMSasWoDe26wFSzIPjRxM4IVtm4=", "dev": true, "requires": { "ansi-styles": "^3.2.1", @@ -416,9 +432,9 @@ }, "dependencies": { "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -431,12 +447,6 @@ } } }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true - }, "color-convert": { "version": "1.9.2", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.2.tgz", @@ -470,7 +480,7 @@ "commander": { "version": "2.15.1", "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "integrity": "sha1-30boZ9D8Kuxmo0ZitAapzK//Ww8=", "dev": true }, "commonlog-bunyan": { @@ -492,6 +502,54 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "optional": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true, + "optional": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, "console-browserify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", @@ -509,12 +567,12 @@ "content-type": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + "integrity": "sha1-4TjMdeBAxyexlm/l5fjJruJW/js=" }, "continuation-local-storage": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/continuation-local-storage/-/continuation-local-storage-3.2.1.tgz", - "integrity": "sha512-jx44cconVqkCEEyLSKWwkvUXwO561jXMa3LPjTPsm5QR22PA0/mhe33FT4Xb5y74JDvt/Cq+5lm8S8rskLv9ZA==", + "integrity": "sha1-EfYT906RT+mzTJKtLSj+auHbf/s=", "requires": { "async-listener": "^0.6.0", "emitter-listener": "^1.1.1" @@ -533,7 +591,7 @@ "cookiejar": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", - "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==", + "integrity": "sha1-3YojVTB1L5iPmghE8/xYnjERElw=", "dev": true }, "core-util-is": { @@ -543,25 +601,26 @@ "dev": true }, "cors": { - "version": "2.8.4", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.4.tgz", - "integrity": "sha1-K9OB8usgECAQXNUOpZ2mMJBpRoY=", + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", "requires": { "object-assign": "^4", "vary": "^1" } }, "coveralls": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.0.1.tgz", - "integrity": "sha512-FAzXwiDOYLGDWH+zgoIA+8GbWv50hlx+kpEJyvzLKOdnIBv9uWoVl4DhqGgyUHpiRjAlF8KYZSipWXYtllWH6Q==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.0.2.tgz", + "integrity": "sha512-Tv0LKe/MkBOilH2v7WBiTBdudg2ChfGbdXafc/s330djpF3zKOmuehTeRwjXWc7pzfj9FrDUTA7tEx6Div8NFw==", "dev": true, "requires": { - "js-yaml": "^3.6.1", + "growl": "~> 1.10.0", + "js-yaml": "^3.11.0", "lcov-parse": "^0.0.10", - "log-driver": "^1.2.5", + "log-driver": "^1.2.7", "minimist": "^1.2.0", - "request": "^2.79.0" + "request": "^2.85.0" } }, "csv": { @@ -623,7 +682,7 @@ "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", "requires": { "ms": "2.0.0" } @@ -631,7 +690,7 @@ "deep-eql": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "integrity": "sha1-38lARACtHI/gI+faHfHBR8S0RN8=", "dev": true, "requires": { "type-detect": "^4.0.0" @@ -645,7 +704,7 @@ "deep-metrics": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/deep-metrics/-/deep-metrics-0.0.1.tgz", - "integrity": "sha512-732WmZgCWxOkf4QBvrCjPPuT6wTEzaGye/4JqYsU/sO0J53UNX4PBwK0JV262BZ5cxgLmKhU+NlrtKdPDgybkg==", + "integrity": "sha1-isMzMZXMXsoFmyJOscph/EzaUP0=", "requires": { "semver": "^5.3.0" }, @@ -664,9 +723,9 @@ "dev": true }, "depd": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", - "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" }, "destroy": { "version": "1.0.4", @@ -676,7 +735,7 @@ "diff": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "integrity": "sha1-gAwN0eCov7yVg1wgKtIg/jF+WhI=", "dev": true }, "dom-serializer": { @@ -691,22 +750,22 @@ "dependencies": { "domelementtype": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", + "resolved": "http://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=", "dev": true }, "entities": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", - "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", "dev": true } } }, "domelementtype": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz", - "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.2.1.tgz", + "integrity": "sha512-SQVCLFS2E7G5CRCMdn6K9bIhRj1bS6QBWZfF0TUPh4V/BbqrQ619IdSS3/izn0FZ+9l+uODzaZjb08fjOfablA==", "dev": true }, "domhandler": { @@ -738,13 +797,13 @@ } }, "ecc-jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", - "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", "dev": true, - "optional": true, "requires": { - "jsbn": "~0.1.0" + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" } }, "ee-first": { @@ -753,14 +812,13 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "ejs": { - "version": "2.5.7", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.5.7.tgz", - "integrity": "sha512-ukpoEmMkRXdzig9UGWFSY+GqurZ+PROb/Q/6NUf+FYlIAI4fO79XFcFzob3TikfbaZZ3OGQAmryINW6/ELWPAg==" + "version": "https://registry.npmjs.org/ejs/-/ejs-2.5.7.tgz", + "integrity": "sha1-zIcsFoiArjxxiXYv1f/ACJbJUYo=" }, "ejs-cli": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/ejs-cli/-/ejs-cli-2.0.1.tgz", - "integrity": "sha512-snHiB9CdKB/ndZw94u9Z7qn9e6ctkURO/31cEGkgHS2dDkDxjqwJJYRg/XdGpItBCMiiAk/NUN9h0GomNitbpQ==", + "integrity": "sha1-XRwGVkcFry6zuAS1q7ik0OHyJyw=", "dev": true, "requires": { "async": "~0.2.10", @@ -814,10 +872,17 @@ }, "entities": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz", + "resolved": "http://registry.npmjs.org/entities/-/entities-1.0.0.tgz", "integrity": "sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY=", "dev": true }, + "es6-promise": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.5.tgz", + "integrity": "sha512-n6wvpdE43VFtJq+lUDYDBFUwV8TZbuGXLV4D6wKafg13ldznKsyEvatubnmUe31zcvelSzOHF+XbaT+Bl9ObDg==", + "dev": true, + "optional": true + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -829,9 +894,9 @@ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "esprima": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", - "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, "esutils": { @@ -852,13 +917,13 @@ "dev": true }, "express": { - "version": "4.16.3", - "resolved": "https://registry.npmjs.org/express/-/express-4.16.3.tgz", - "integrity": "sha1-avilAjUNsyRuzEvs9rWjTSL37VM=", + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz", + "integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==", "requires": { "accepts": "~1.3.5", "array-flatten": "1.1.1", - "body-parser": "1.18.2", + "body-parser": "1.18.3", "content-disposition": "0.5.2", "content-type": "~1.0.4", "cookie": "0.3.1", @@ -875,10 +940,10 @@ "on-finished": "~2.3.0", "parseurl": "~1.3.2", "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.3", - "qs": "6.5.1", + "proxy-addr": "~2.0.4", + "qs": "6.5.2", "range-parser": "~1.2.0", - "safe-buffer": "5.1.1", + "safe-buffer": "5.1.2", "send": "0.16.2", "serve-static": "1.13.2", "setprototypeof": "1.1.0", @@ -888,71 +953,20 @@ "vary": "~1.1.2" }, "dependencies": { - "body-parser": { - "version": "1.18.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", - "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", - "requires": { - "bytes": "3.0.0", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.1", - "http-errors": "~1.6.2", - "iconv-lite": "0.4.19", - "on-finished": "~2.3.0", - "qs": "6.5.1", - "raw-body": "2.3.2", - "type-is": "~1.6.15" - } - }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" - }, - "iconv-lite": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", - "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, - "raw-body": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", - "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", - "requires": { - "bytes": "3.0.0", - "http-errors": "1.6.2", - "iconv-lite": "0.4.19", - "unpipe": "1.0.0" - }, - "dependencies": { - "depd": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", - "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" - }, - "http-errors": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", - "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", - "requires": { - "depd": "1.1.1", - "inherits": "2.0.3", - "setprototypeof": "1.0.3", - "statuses": ">= 1.3.1 < 2" - } - }, - "setprototypeof": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", - "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" - } - } + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "statuses": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" + "integrity": "sha1-u3PURtonlhBu/MG2AaJT1sRr0Ic=" } } }, @@ -961,6 +975,19 @@ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" }, + "extract-zip": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.7.tgz", + "integrity": "sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k=", + "dev": true, + "optional": true, + "requires": { + "concat-stream": "1.6.2", + "debug": "2.6.9", + "mkdirp": "0.5.1", + "yauzl": "2.4.1" + } + }, "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", @@ -973,9 +1000,9 @@ "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=" }, "fast-deep-equal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", "dev": true }, "fast-json-stable-stringify": { @@ -984,9 +1011,19 @@ "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", "dev": true }, + "fd-slicer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", + "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", + "dev": true, + "optional": true, + "requires": { + "pend": "~1.2.0" + } + }, "finalhandler": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + "resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", "requires": { "debug": "2.6.9", @@ -1025,7 +1062,7 @@ "formidable": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz", - "integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg==", + "integrity": "sha1-cPt8oCkO5v+WEJBBX0s989IIJlk=", "dev": true }, "forwarded": { @@ -1038,12 +1075,29 @@ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" }, + "fs-extra": { + "version": "1.0.0", + "resolved": "http://registry.npmjs.org/fs-extra/-/fs-extra-1.0.0.tgz", + "integrity": "sha1-zTzl9+fLYUWIP8rjGR6Yd/hYeVA=", + "dev": true, + "optional": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^2.1.0", + "klaw": "^1.0.0" + } + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, "get-func-name": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", @@ -1077,10 +1131,17 @@ "integrity": "sha512-hYyf+kI8dm3nORsiiXUQigOU62hDLfJ9G01uyGMxhc6BKsircrUhC4uJPQPUSuq2GrTmiiEt7ewxlMdBewfmKQ==", "dev": true }, + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", + "dev": true, + "optional": true + }, "growl": { "version": "1.10.5", "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "integrity": "sha1-8nNdwig2dPpnR4sQGBBZNVw2nl4=", "dev": true }, "har-schema": { @@ -1090,21 +1151,40 @@ "dev": true }, "har-validator": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", - "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", "dev": true, "requires": { - "ajv": "^5.1.0", + "ajv": "^6.5.5", "har-schema": "^2.0.0" } }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, + "hasha": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-2.2.0.tgz", + "integrity": "sha1-eNfL/B5tZjA/55g3NlmEUXsvbuE=", + "dev": true, + "optional": true, + "requires": { + "is-stream": "^1.0.1", + "pinkie-promise": "^2.0.0" + } + }, "he": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", @@ -1113,7 +1193,7 @@ }, "htmlparser2": { "version": "3.8.3", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz", + "resolved": "http://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz", "integrity": "sha1-mWwosZFRaovoZQGn15dX5ccMEGg=", "dev": true, "requires": { @@ -1161,7 +1241,7 @@ "iconv-lite": { "version": "0.4.23", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", - "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "integrity": "sha1-KXhx9jvlB63Pv8pxXQzQ7thOmmM=", "requires": { "safer-buffer": ">= 2.1.2 < 3" } @@ -1183,22 +1263,29 @@ "invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "integrity": "sha1-YQ88ksk1nOHbYW5TgAjSP/NRWOY=", "dev": true, "requires": { "loose-envify": "^1.0.0" } }, "ipaddr.js": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.6.0.tgz", - "integrity": "sha1-4/o1e3c9phnybpXwSdBVxyeW+Gs=" + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz", + "integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4=" }, "is": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/is/-/is-3.2.1.tgz", "integrity": "sha1-0Kwq1V63sL7JJqUmb2xmKqqD3KU=" }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true, + "optional": true + }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", @@ -1211,6 +1298,13 @@ "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", "dev": true }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true, + "optional": true + }, "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", @@ -1254,7 +1348,7 @@ "js-yaml": { "version": "3.12.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz", - "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", + "integrity": "sha1-6u1lbsg0TxD1J8a/obbiJE3hZ9E=", "dev": true, "requires": { "argparse": "^1.0.7", @@ -1265,8 +1359,7 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true, - "optional": true + "dev": true }, "jsesc": { "version": "2.5.1", @@ -1275,19 +1368,22 @@ "dev": true }, "jshint": { - "version": "2.9.5", - "resolved": "https://registry.npmjs.org/jshint/-/jshint-2.9.5.tgz", - "integrity": "sha1-HnJSkVzmgbQIJ+4UJIxG006apiw=", + "version": "2.9.6", + "resolved": "https://registry.npmjs.org/jshint/-/jshint-2.9.6.tgz", + "integrity": "sha512-KO9SIAKTlJQOM4lE64GQUtGBRpTOuvbrRrSZw3AhUxMNG266nX9hK2cKA4SBhXOj0irJGyNyGSLT62HGOVDEOA==", "dev": true, "requires": { "cli": "~1.0.0", "console-browserify": "1.1.x", "exit": "0.1.x", "htmlparser2": "3.8.x", - "lodash": "3.7.x", + "lodash": "~4.17.10", "minimatch": "~3.0.2", + "phantom": "~4.0.1", + "phantomjs-prebuilt": "~2.1.7", "shelljs": "0.3.x", - "strip-json-comments": "1.0.x" + "strip-json-comments": "1.0.x", + "unicode-5.2.0": "^0.7.5" } }, "json-schema": { @@ -1297,9 +1393,9 @@ "dev": true }, "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, "json-stringify-safe": { @@ -1307,6 +1403,16 @@ "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, + "jsonfile": { + "version": "2.4.0", + "resolved": "http://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", + "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", + "dev": true, + "optional": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -1319,6 +1425,23 @@ "verror": "1.10.0" } }, + "kew": { + "version": "0.7.0", + "resolved": "http://registry.npmjs.org/kew/-/kew-0.7.0.tgz", + "integrity": "sha1-edk9LTM2PW/dKXCzNdkUGtWR15s=", + "dev": true, + "optional": true + }, + "klaw": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", + "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=", + "dev": true, + "optional": true, + "requires": { + "graceful-fs": "^4.1.9" + } + }, "lcov-parse": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-0.0.10.tgz", @@ -1326,9 +1449,9 @@ "dev": true }, "lodash": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.7.0.tgz", - "integrity": "sha1-Nni9irmVBXwHreg27S7wh9qBHUU=", + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", "dev": true }, "lodash.findindex": { @@ -1349,12 +1472,12 @@ "lodash.merge": { "version": "4.6.1", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.1.tgz", - "integrity": "sha512-AOYza4+Hf5z1/0Hztxpm2/xiPZgi/cjMqdnKTUWTBSKchJlxXXuUSxCCl8rJlf4g6yww/j6mA8nC8Hw/EZWxKQ==" + "integrity": "sha1-rcJdnLmbk5HFliTzefu6YNcRHVQ=" }, "log-driver": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", - "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==", + "integrity": "sha1-Y7lQIfBwL+36LJuwok53l9cYcdg=", "dev": true }, "loose-envify": { @@ -1390,7 +1513,7 @@ "mime": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", - "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" + "integrity": "sha1-Eh+evEnjdm8xGnbh+hyAA8SwOqY=" }, "mime-db": { "version": "1.30.0", @@ -1410,7 +1533,7 @@ "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", "requires": { "brace-expansion": "^1.1.7" } @@ -1438,7 +1561,7 @@ "mocha": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", - "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", + "integrity": "sha1-bYrlCPWRZ/lA8rWzxKYSrlDJCuY=", "dev": true, "requires": { "browser-stdout": "1.3.1", @@ -1457,7 +1580,7 @@ "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", "dev": true, "requires": { "ms": "2.0.0" @@ -1466,7 +1589,7 @@ "glob": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -1492,13 +1615,13 @@ "optional": true }, "morgan": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.0.tgz", - "integrity": "sha1-0B+mxlhZt2/PMbPLU6OCGjEdgFE=", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.1.tgz", + "integrity": "sha1-Co0Wc0odmvvIJLmd+H5zjlji2lk=", "requires": { "basic-auth": "~2.0.0", "debug": "2.6.9", - "depd": "~1.1.1", + "depd": "~1.1.2", "on-finished": "~2.3.0", "on-headers": "~1.0.1" } @@ -1542,10 +1665,11 @@ "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" }, "node.extend": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/node.extend/-/node.extend-2.0.0.tgz", - "integrity": "sha1-dSWih1Z36lNHhKXhCseJVhOWFN8=", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/node.extend/-/node.extend-2.0.2.tgz", + "integrity": "sha512-pDT4Dchl94/+kkgdwyS2PauDFjZG0Hk0IcHIB+LkW27HLDtdoeMxHTxZh39DYbPP8UflWXWj9JcdDozF+YDOpQ==", "requires": { + "has": "^1.0.3", "is": "^3.2.1" } }, @@ -1588,7 +1712,6 @@ "version": "0.1.4", "bundled": true, "dev": true, - "optional": true, "requires": { "kind-of": "^3.0.2", "longest": "^1.0.1", @@ -2531,8 +2654,7 @@ "longest": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "lru-cache": { "version": "4.1.3", @@ -3605,9 +3727,9 @@ } }, "oauth-sign": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", "dev": true }, "object-assign": { @@ -3676,6 +3798,13 @@ "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", "dev": true }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "dev": true, + "optional": true + }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -3717,9 +3846,9 @@ "integrity": "sha1-wCIDLIlJ8xKk+R+2QJzgQHa+Mlc=" }, "pg-query-stream": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pg-query-stream/-/pg-query-stream-1.1.1.tgz", - "integrity": "sha1-Zel0Nu+AnR4WDrqE6/EbnkdC6rQ=", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pg-query-stream/-/pg-query-stream-1.1.2.tgz", + "integrity": "sha512-84hsUVjbrvXYIpVH+6FrKajfOnmTo7Wc/CXPtSyv41JmUnLcQ2lcVrkR7+m4VPtL28FdcZA7Q7ab4X0za4BhrQ==", "requires": { "pg-cursor": "1.3.0" } @@ -3743,6 +3872,84 @@ "split": "^1.0.0" } }, + "phantom": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/phantom/-/phantom-4.0.12.tgz", + "integrity": "sha512-Tz82XhtPmwCk1FFPmecy7yRGZG2btpzY2KI9fcoPT7zT9det0CcMyfBFPp1S8DqzsnQnm8ZYEfdy528mwVtksA==", + "dev": true, + "optional": true, + "requires": { + "phantomjs-prebuilt": "^2.1.16", + "split": "^1.0.1", + "winston": "^2.4.0" + }, + "dependencies": { + "async": { + "version": "1.0.0", + "resolved": "http://registry.npmjs.org/async/-/async-1.0.0.tgz", + "integrity": "sha1-+PwEyjoTeErenhZBr5hXjPvWR6k=", + "dev": true, + "optional": true + }, + "colors": { + "version": "1.0.3", + "resolved": "http://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", + "dev": true, + "optional": true + }, + "winston": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/winston/-/winston-2.4.4.tgz", + "integrity": "sha512-NBo2Pepn4hK4V01UfcWcDlmiVTs7VTB1h7bgnB0rgP146bYhMxX0ypCz3lBOfNxCO4Zuek7yeT+y/zM1OfMw4Q==", + "dev": true, + "optional": true, + "requires": { + "async": "~1.0.0", + "colors": "1.0.x", + "cycle": "1.0.x", + "eyes": "0.1.x", + "isstream": "0.1.x", + "stack-trace": "0.0.x" + } + } + } + }, + "phantomjs-prebuilt": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/phantomjs-prebuilt/-/phantomjs-prebuilt-2.1.16.tgz", + "integrity": "sha1-79ISpKOWbTZHaE6ouniFSb4q7+8=", + "dev": true, + "optional": true, + "requires": { + "es6-promise": "^4.0.3", + "extract-zip": "^1.6.5", + "fs-extra": "^1.0.0", + "hasha": "^2.2.0", + "kew": "^0.7.0", + "progress": "^1.1.8", + "request": "^2.81.0", + "request-progress": "^2.0.1", + "which": "^1.2.10" + } + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true, + "optional": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "optional": true, + "requires": { + "pinkie": "^2.0.0" + } + }, "pkginfo": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.4.1.tgz", @@ -3751,7 +3958,7 @@ "pmx": { "version": "1.6.7", "resolved": "https://registry.npmjs.org/pmx/-/pmx-1.6.7.tgz", - "integrity": "sha512-CoyZD1EWj/fvpuEPnndB11s5onzN5p/0bxGsBuwbyb8uFtg3lMxXys1pXs88gReiRnMSYCSt25J3GCc6AnxoFQ==", + "integrity": "sha1-sPyAYbyDQ6QGnRjk7k8DHeCviQo=", "requires": { "debug": "^3", "deep-metrics": "^0.0.1", @@ -3806,9 +4013,16 @@ "process-nextick-args": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "integrity": "sha1-o31zL0JxtKsa0HDTVQjoKQeI/6o=", "dev": true }, + "progress": { + "version": "1.1.8", + "resolved": "http://registry.npmjs.org/progress/-/progress-1.1.8.tgz", + "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", + "dev": true, + "optional": true + }, "prompt": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/prompt/-/prompt-1.0.0.tgz", @@ -3830,24 +4044,31 @@ } }, "proxy-addr": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.3.tgz", - "integrity": "sha512-jQTChiCJteusULxjBp8+jftSQE5Obdl3k4cnmLA6WXtK6XFuWRnvVL7aCiBqaLPM8c4ph0S4tKna8XvmIwEnXQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz", + "integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==", "requires": { "forwarded": "~0.1.2", - "ipaddr.js": "1.6.0" + "ipaddr.js": "1.8.0" } }, + "psl": { + "version": "1.1.29", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz", + "integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==", + "dev": true + }, "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true }, "qs": { "version": "6.5.1", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" + "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==", + "dev": true }, "random-string": { "version": "0.2.0", @@ -3863,7 +4084,7 @@ "raw-body": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", - "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", + "integrity": "sha1-GzJOzmtXBuFThVvBFIxlu39uoMM=", "requires": { "bytes": "3.0.0", "http-errors": "1.6.3", @@ -3881,7 +4102,7 @@ }, "readable-stream": { "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", "dev": true, "requires": { @@ -3892,31 +4113,82 @@ } }, "request": { - "version": "2.87.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz", - "integrity": "sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw==", + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", "dev": true, "requires": { "aws-sign2": "~0.7.0", - "aws4": "^1.6.0", + "aws4": "^1.8.0", "caseless": "~0.12.0", - "combined-stream": "~1.0.5", - "extend": "~3.0.1", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", "forever-agent": "~0.6.1", - "form-data": "~2.3.1", - "har-validator": "~5.0.3", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", "http-signature": "~1.2.0", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.17", - "oauth-sign": "~0.8.2", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", "performance-now": "^2.1.0", - "qs": "~6.5.1", - "safe-buffer": "^5.1.1", - "tough-cookie": "~2.3.3", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", "tunnel-agent": "^0.6.0", - "uuid": "^3.1.0" + "uuid": "^3.3.2" + }, + "dependencies": { + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "mime-db": { + "version": "1.37.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", + "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==", + "dev": true + }, + "mime-types": { + "version": "2.1.21", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", + "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", + "dev": true, + "requires": { + "mime-db": "~1.37.0" + } + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "dev": true + } + } + }, + "request-progress": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-2.0.1.tgz", + "integrity": "sha1-XTa7V5YcZzqlt4jbyBQf3yO0Tgg=", + "dev": true, + "optional": true, + "requires": { + "throttleit": "^1.0.0" } }, "revalidator": { @@ -3946,7 +4218,7 @@ "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "integrity": "sha1-RPoWGwGHuVSd2Eu5GAL5vYOFzWo=" }, "semver": { "version": "4.3.2", @@ -3956,7 +4228,7 @@ "send": { "version": "0.16.2", "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", - "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", + "integrity": "sha1-bsyh4PjBVtFBWXVZhI32RzCmu8E=", "requires": { "debug": "2.6.9", "depd": "~1.1.2", @@ -3973,15 +4245,10 @@ "statuses": "~1.4.0" }, "dependencies": { - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" - }, "statuses": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" + "integrity": "sha1-u3PURtonlhBu/MG2AaJT1sRr0Ic=" } } }, @@ -4000,14 +4267,14 @@ "ms": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + "integrity": "sha1-MKWGTrPrsKZvLr5tcnrwagnYbgo=" } } }, "serve-static": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", - "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", + "integrity": "sha1-CV6Ecv1bRiN9tQzkhqQ/S4bGzsE=", "requires": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", @@ -4018,18 +4285,18 @@ "setprototypeof": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + "integrity": "sha1-0L2FU2iHtv58DYGMuWLZ2RxU5lY=" }, "shelljs": { "version": "0.3.0", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz", + "resolved": "http://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz", "integrity": "sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E=", "dev": true }, "shimmer": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.0.tgz", - "integrity": "sha512-xTCx2vohXC2EWWDqY/zb4+5Mu28D+HYNSOuFzsyRDRvI/e1ICb69afwaUwfjr+25ZXldbOLyp+iDUZHq8UnTag==" + "integrity": "sha1-+Wb3VVeJdj502IQRk2haXnhzZmU=" }, "sigmund": { "version": "1.0.1", @@ -4046,7 +4313,7 @@ "split": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", - "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "integrity": "sha1-YFvZvjA6pZ+zX5Ip++oN3snqB9k=", "requires": { "through": "2" } @@ -4058,9 +4325,9 @@ "dev": true }, "sshpk": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz", - "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=", + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.15.2.tgz", + "integrity": "sha512-Ra/OXQtuh0/enyl4ETZAfTaeksa6BXks5ZcjpSUNrjBr0DvrJKX+1fsKDPpT9TBXgHAFsa4510aNVgI8g/+SzA==", "dev": true, "requires": { "asn1": "~0.2.3", @@ -4091,7 +4358,7 @@ }, "string_decoder": { "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", "dev": true }, @@ -4137,13 +4404,13 @@ "ms": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "integrity": "sha1-MKWGTrPrsKZvLr5tcnrwagnYbgo=", "dev": true }, "readable-stream": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -4158,7 +4425,7 @@ "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -4179,12 +4446,19 @@ "supports-color": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "integrity": "sha1-HGszdALCE3YF7+GfEP7DkPb6q1Q=", "dev": true, "requires": { "has-flag": "^3.0.0" } }, + "throttleit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz", + "integrity": "sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw=", + "dev": true, + "optional": true + }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -4197,12 +4471,21 @@ "dev": true }, "tough-cookie": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", - "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", "dev": true, "requires": { + "psl": "^1.1.24", "punycode": "^1.4.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + } } }, "trim-right": { @@ -4224,8 +4507,7 @@ "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true, - "optional": true + "dev": true }, "type-detect": { "version": "4.0.5", @@ -4236,7 +4518,7 @@ "type-is": { "version": "1.6.16", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", - "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", + "integrity": "sha1-+JzjQVQcZysl7nrjxz3uOyvlAZQ=", "requires": { "media-typer": "0.3.0", "mime-types": "~2.1.18" @@ -4257,11 +4539,33 @@ } } }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true, + "optional": true + }, + "unicode-5.2.0": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/unicode-5.2.0/-/unicode-5.2.0-0.7.5.tgz", + "integrity": "sha512-KVGLW1Bri30x00yv4HNM8kBxoqFXr0Sbo55735nvrlsx4PYBZol3UtoWgO492fSwmsetzPEZzy73rbU8OGXJcA==", + "dev": true + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -4344,6 +4648,16 @@ } } }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "optional": true, + "requires": { + "isexe": "^2.0.0" + } + }, "winston": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/winston/-/winston-2.1.1.tgz", @@ -4390,6 +4704,16 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" + }, + "yauzl": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz", + "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=", + "dev": true, + "optional": true, + "requires": { + "fd-slicer": "~1.0.1" + } } } } diff --git a/package.json b/package.json index e8805b79..bf77494e 100644 --- a/package.json +++ b/package.json @@ -52,19 +52,19 @@ "body-parser": "~1.18.3", "bunyan": "~1.8.12", "commonlog-bunyan": "~0.4.3", - "cors": "~2.8.4", + "cors": "~2.8.5", "csv": "~1.1.0", "csv-parse": "~3.2.0", "ejs": "https://registry.npmjs.org/ejs/-/ejs-2.5.7.tgz", "escape-string-regexp": "~1.0.5", - "express": "~4.16.3", + "express": "~4.16.4", "minimist": "~1.2.0", "morgan": "~1.9.1", - "node.extend": "~2.0.0", + "node.extend": "~2.0.2", "ospoint": "~0.2.0", "pg": "~7.4.3", "pg-copy-streams": "~1.2.0", - "pg-query-stream": "~1.1.1", + "pg-query-stream": "~1.1.2", "pmx": "~1.6.7", "postcode": "~0.2.5", "prompt": "~1.0.0", @@ -72,14 +72,14 @@ }, "devDependencies": { "chai": "~4.1.2", - "coveralls": "~3.0.1", + "coveralls": "~3.0.2", "ejs-cli": "~2.0.1", - "jshint": "~2.9.5", + "jshint": "~2.9.6", "mocha": "~5.2.0", "mocha-lcov-reporter": "~1.3.0", "nyc": "~12.0.2", - "supertest": "~3.3.0", - "random-string": "~0.2.0" + "random-string": "~0.2.0", + "supertest": "~3.3.0" }, "greenkeeper": { "ignore": [ From 4de4e815c528642533b5b9c4b705360c2db89310 Mon Sep 17 00:00:00 2001 From: Chris Blanchard Date: Mon, 3 Dec 2018 15:53:42 +0100 Subject: [PATCH 17/36] Add CED to filterable attributes --- config/defaults.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/defaults.js b/config/defaults.js index ec5c40d7..475bcfb1 100644 --- a/config/defaults.js +++ b/config/defaults.js @@ -84,6 +84,7 @@ module.exports = { "admin_county", "admin_ward", "ccg", - "nuts" + "nuts", + "ced" ] }; From ba343822e73026855d7c499530844fdb6809244e Mon Sep 17 00:00:00 2001 From: Chris Blanchard Date: Wed, 5 Dec 2018 15:46:47 +0100 Subject: [PATCH 18/36] Remove redundant test helper method --- tests/outcodes.integration.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/outcodes.integration.js b/tests/outcodes.integration.js index 4d4874de..01752f28 100644 --- a/tests/outcodes.integration.js +++ b/tests/outcodes.integration.js @@ -36,7 +36,7 @@ describe("Outcodes routes", () => { assert.equal(response.body.result.outcode, testOutcode); assert.isUndefined(response.body.result.id); assert.isUndefined(response.body.result.location); - helper.testOutcode(response.body.result); + helper.isOutcodeObject(response.body.result); done(); }); }); @@ -51,7 +51,7 @@ describe("Outcodes routes", () => { if (error) return done(error); assert.equal(response.body.status, 200); assert.equal(response.body.result.outcode, testOutcode); - helper.testOutcode(response.body.result); + helper.isOutcodeObject(response.body.result); done(); }); }); @@ -66,7 +66,7 @@ describe("Outcodes routes", () => { if (error) return done(error); assert.equal(response.body.status, 200); assert.equal(response.body.result.outcode, testOutcode); - helper.testOutcode(response.body.result); + helper.isOutcodeObject(response.body.result); done(); }); }); From e8e8a7090d158f5e1326d51166bb56fd2641f1aa Mon Sep 17 00:00:00 2001 From: Chris Blanchard Date: Wed, 5 Dec 2018 15:47:20 +0100 Subject: [PATCH 19/36] Group object checking helper methods --- tests/helper/index.js | 198 +--------------------------------- tests/helper/type_checking.js | 166 ++++++++++++++++++++++++++++ 2 files changed, 170 insertions(+), 194 deletions(-) create mode 100644 tests/helper/type_checking.js diff --git a/tests/helper/index.js b/tests/helper/index.js index 443405fe..fcbb8788 100644 --- a/tests/helper/index.js +++ b/tests/helper/index.js @@ -169,7 +169,7 @@ function randomLocation(callback) { } function lookupRandomPostcode(callback) { - Postcode.random(function (error, result) { + Postcode.random((error, result) => { if (error) { throw error; } @@ -196,187 +196,6 @@ function validCorsOptions(response) { "X-Requested-With, Content-Type, Accept, Origin"); } -function isRawPlaceObject(o) { - [ - "id", - "code", - "longitude", - "latitude", - "location", - "eastings", - "northings", - "min_eastings", - "min_northings", - "max_eastings", - "max_northings", - "bounding_polygon", - "local_type", - "outcode", - "name_1", - "name_1_lang", - "name_1_search", - "name_1_search_ts", - "name_2", - "name_2_lang", - "name_2_search", - "name_2_search_ts", - "county_unitary", - "county_unitary_type", - "district_borough", - "district_borough_type", - "region", - "country", - "polygon" - ].forEach(prop => assert.property(o, prop)); -} - -function isPlaceObject(o) { - [ - "code", - "longitude", - "latitude", - "eastings", - "northings", - "min_eastings", - "min_northings", - "max_eastings", - "max_northings", - "local_type", - "outcode", - "name_1", - "name_1_lang", - "name_2", - "name_2_lang", - "county_unitary", - "county_unitary_type", - "district_borough", - "district_borough_type", - "region", - "country" - ].forEach(prop => assert.property(o, prop)); - - [ - "id", - "location", - "name_1_search", - "name_1_search_ts", - "name_2_search", - "name_2_search_ts", - "bounding_polygon", - "polygon" - ].forEach(prop => assert.notProperty(o, prop)); -} - -const rawPostcodeAttributes = Object.keys(Postcode.schema); -const postcodeAttributes = Postcode.whitelistedAttributes; - - -//baseObject is the main template of an object -//additionalArr is an array of extra attributes on the postcode object -//blackListedAttr is an array of attributes that Postcode object not supposed to have -function isSomeObject(o, baseObjectAttr, additionalAttr, blackListedAttr) { - if (!additionalAttr) additionalAttr = []; - if (!blackListedAttr) blackListedAttr = []; - - const whiteBaseObjAttr = baseObjectAttr.reduce((acc,curr) => { - if (!blackListedAttr.includes(curr)) {acc.push(curr)} - return acc; - }, []); - whiteBaseObjAttr.forEach(attr => assert.property(o, attr)); - if (additionalAttr) { - additionalAttr.forEach(attr => assert.property(o, attr)); - } - const expectedObjLen = whiteBaseObjAttr.length + additionalAttr.length; - assert.equal(Object.keys(o).length, expectedObjLen); -} - -function isPostcodeObject(o, additionalAttr, blackListedAttr) { - if (!additionalAttr) additionalAttr = []; - if (!blackListedAttr) blackListedAttr = []; - isSomeObject(o, postcodeAttributes, additionalAttr, blackListedAttr); -} - -function isPostcodeWithDistanceObject(o) { - isPostcodeObject(o, ["distance"]); -} -//raw Object is the one that only has properties specified in the schema -function isRawPostcodeObject(o, additionalAttr, blackListedAttr) { - if (!additionalAttr) additionalAttr = []; - if (!blackListedAttr) blackListedAttr = []; - isSomeObject(o, rawPostcodeAttributes, additionalAttr, blackListedAttr); -} - -function isRawPostcodeObjectWithFC(o, additionalAttr, blackListedAttr) { - if (!additionalAttr) additionalAttr = []; - if (!blackListedAttr) blackListedAttr = []; - isRawPostcodeObject(o, Postcode.getForeignColNames().concat(additionalAttr), blackListedAttr); -} - -function isRawPostcodeObjectWithFCandDistance(o) { - isRawPostcodeObjectWithFC(o, ["distance"]) -} - -const terminatedPostcodeAttributes = TerminatedPostcode.whitelistedAttributes; -const rawTerminatedPostcodeAttributes = Object.keys(TerminatedPostcode.schema); - -function isTerminatedPostcodeObject(o) { - terminatedPostcodeAttributes.forEach(attr => assert.property(o, attr)); - assert.equal(Object.keys(o).length, terminatedPostcodeAttributes.length); -} - -function isRawTerminatedPostcodeObject(o) { - rawTerminatedPostcodeAttributes.forEach(attr => assert.property(o, attr)); - assert.equal(Object.keys(o).length, rawTerminatedPostcodeAttributes.length); -} - -function isOutcodeObject(o) { - ["id", "location"].forEach(prop => assert.notProperty(o, prop)); - - [ - "eastings", - "latitude", - "northings", - "longitude", - "admin_ward", - "admin_county", - "admin_district", - "parish", - "outcode", - "country" - ].forEach(prop => assert.property(o, prop)); -} - -function isRawOutcodeObject(o) { - [ - "id", - "eastings", - "latitude", - "location", - "northings", - "longitude", - "admin_ward", - "admin_county", - "admin_district", - "parish", - "outcode", - "country" - ].forEach(prop => assert.property(o, prop)) -} - -function testOutcode(o) { - [ - "longitude", - "latitude", - "northings", - "eastings", - "admin_ward", - "admin_district", - "admin_county", - "parish", - "country" - ].forEach(prop => assert.property(o, prop)); -} - module.exports = { // Data config, @@ -388,28 +207,19 @@ module.exports = { inferIndexInfo, inferSchemaData, sortByIndexColumns, - testOutcode, randomOutcode, - isPlaceObject, randomPostcode, randomTerminatedPostcode, randomLocation, - isOutcodeObject, validCorsOptions, - isPostcodeObject, - isTerminatedPostcodeObject, - isRawTerminatedPostcodeObject, - isRawPlaceObject, - isPostcodeWithDistanceObject, jsonpResponseBody, getCustomRelation, - isRawOutcodeObject, - isRawPostcodeObject, - isRawPostcodeObjectWithFC, - isRawPostcodeObjectWithFCandDistance, lookupRandomPostcode, locationWithNearbyPostcodes, + // Type checking methods + ...require("./type_checking.js"), + // PG helper methods ...require("./pg.js"), diff --git a/tests/helper/type_checking.js b/tests/helper/type_checking.js new file mode 100644 index 00000000..f8712dda --- /dev/null +++ b/tests/helper/type_checking.js @@ -0,0 +1,166 @@ +"use strict"; + +const { + Postcode, + TerminatedPostcode, +} = require("../../app/models/index.js"); +const { assert } = require("chai"); + +// Retrieve object size +const len = o => Object.keys(o).length; + +// Dedupes contents of array +const dedupe = a => [...new Set(a)]; + +/** + * Returns a function to be passed to an iterator. Checks props exist + * in object o + * + * @param {Object} o + * @returns {Function} + */ +const checkProps = o => (prop) => assert.property(o, prop); + +/** + * Returns a function to be passed to an iterator. Checks props + * do no exist in object o + * + * @param {Object} o + * @returns {Function} + */ +const checkNotProps = o => (prop) => assert.notProperty(o, prop); + + +/** + * Definition of place type + * public methods should be publicly available + */ +const placeType = { + public: [ + "code", + "longitude", + "latitude", + "eastings", + "northings", + "min_eastings", + "min_northings", + "max_eastings", + "max_northings", + "local_type", + "outcode", + "name_1", + "name_1_lang", + "name_2", + "name_2_lang", + "county_unitary", + "county_unitary_type", + "district_borough", + "district_borough_type", + "region", + "country" + ], + private: [ + "id", + "location", + "name_1_search", + "name_1_search_ts", + "name_2_search", + "name_2_search_ts", + "bounding_polygon", + "polygon" + ], +}; + +const isRawPlaceObject = o => { + placeType.public.forEach(checkProps(o)); + placeType.private.forEach(checkProps(o)); +}; + +const isPlaceObject = o => { + placeType.public.forEach(checkProps(o)); + placeType.private.forEach(checkNotProps(o)); +}; + +const outcodeType = { + private: ["id", "location"], + public: [ + "eastings", + "latitude", + "northings", + "longitude", + "admin_ward", + "admin_county", + "admin_district", + "parish", + "outcode", + "country" + ], +}; +const isOutcodeObject = o => { + outcodeType.public.forEach(checkProps(o)); + outcodeType.private.forEach(checkNotProps(o)); +}; + +const isRawOutcodeObject = o => { + outcodeType.public.forEach(checkProps(o)) + outcodeType.private.forEach(checkProps(o)) +}; + +const rawPostcodeAttributes = Object.keys(Postcode.schema); +const postcodeAttributes = Postcode.whitelistedAttributes; + +//baseObject is the main template of an object +//additionalArr is an array of extra attributes on the postcode object +//blackListedAttr is an array of attributes that Postcode object not supposed to have +const isObject = (o, requiredAttr, additionalAttr = [], blacklist = []) => { + const whitelist = dedupe(requiredAttr.concat(additionalAttr)); + whitelist.forEach(checkProps(o)); + blacklist.forEach(checkNotProps(o)); + assert.equal(len(o), whitelist.length); +}; + +const isPostcodeObject = (o, additionalAttr = [], blacklist = []) => { + isObject(o, postcodeAttributes, additionalAttr, blacklist); +}; + +const isPostcodeWithDistanceObject = o => isPostcodeObject(o, ["distance"]); + +//raw Object is the one that only has properties specified in the schema +const isRawPostcodeObject = (o, additionalAttr, blacklist) => { + isObject(o, rawPostcodeAttributes, additionalAttr, blacklist); +}; + +const isRawPostcodeObjectWithFC = (o, additionalAttr, blacklist) => { + isRawPostcodeObject(o, Postcode.getForeignColNames().concat(additionalAttr || []), blacklist); +}; + +const isRawPostcodeObjectWithFCandDistance = o => isRawPostcodeObjectWithFC(o, ["distance"]); + +const terminatedPostcodeAttributes = TerminatedPostcode.whitelistedAttributes; +const rawTerminatedPostcodeAttributes = Object.keys(TerminatedPostcode.schema); + +const isTerminatedPostcodeObject = o => { + terminatedPostcodeAttributes.forEach(attr => assert.property(o, attr)); + assert.equal(Object.keys(o).length, terminatedPostcodeAttributes.length); +}; + +const isRawTerminatedPostcodeObject = o => { + rawTerminatedPostcodeAttributes.forEach(attr => assert.property(o, attr)); + assert.equal(Object.keys(o).length, rawTerminatedPostcodeAttributes.length); +}; + +module.exports = { + isRawOutcodeObject, + isRawPostcodeObject, + isRawPostcodeObjectWithFC, + isRawPostcodeObjectWithFCandDistance, + isPlaceObject, + isPostcodeObject, + isTerminatedPostcodeObject, + isRawTerminatedPostcodeObject, + isRawPlaceObject, + isPostcodeWithDistanceObject, + isOutcodeObject, + isRawOutcodeObject, +}; + From d62149a8f86bd560d55210a017a71663620099f5 Mon Sep 17 00:00:00 2001 From: Chris Blanchard Date: Wed, 5 Dec 2018 16:15:35 +0100 Subject: [PATCH 20/36] Isolate http helper methods --- tests/helper/http.js | 28 ++++++++++++++++++++ tests/helper/index.js | 60 +++++++++++++------------------------------ 2 files changed, 46 insertions(+), 42 deletions(-) create mode 100644 tests/helper/http.js diff --git a/tests/helper/http.js b/tests/helper/http.js new file mode 100644 index 00000000..b5c1ca9f --- /dev/null +++ b/tests/helper/http.js @@ -0,0 +1,28 @@ +"use strict"; + +const { assert } = require("chai"); + +const allowsCORS = response => { + assert.equal(response.headers["access-control-allow-origin"], "*"); +}; + +const validCorsOptions = response => { + allowsCORS(response); + assert.equal(response.headers["access-control-allow-methods"], + "GET,POST,OPTIONS"); + assert.equal(response.headers["access-control-allow-headers"], + "X-Requested-With, Content-Type, Accept, Origin"); +}; + +// Rough regex to extract json object +const jsonpResponseBody = response => { + const result = response.text.match(/\(.*\)/); + return JSON.parse(result[0].slice(1, result[0].length - 1)); +}; + +module.exports = { + allowsCORS, + validCorsOptions, + jsonpResponseBody, +}; + diff --git a/tests/helper/index.js b/tests/helper/index.js index fcbb8788..4bae16e9 100644 --- a/tests/helper/index.js +++ b/tests/helper/index.js @@ -124,9 +124,7 @@ function getCustomRelation () { //Generates a random integer from 1 to max inclusive -const getRandom = function (max) { - return Math.ceil(Math.random() * max); -} +const getRandom = max => Math.ceil(Math.random() * max); const QueryTerminatedPostcode = ` SELECT @@ -136,7 +134,6 @@ const QueryTerminatedPostcode = ` OFFSET $1 `; - function randomTerminatedPostcode (callback) { const randomId = getRandom(8); // 9 terminated postcodes in the // testing database @@ -147,28 +144,25 @@ function randomTerminatedPostcode (callback) { }); } -function randomPostcode(callback) { - Postcode.random(function (error, result) { - callback(error, result.postcode); +const randomPostcode = callback => { + Postcode.random((error, { postcode }) => { + callback(error, postcode); }); -} +}; -function randomOutcode(callback) { - return Postcode.random(function (error, result) { - callback(error, result.outcode) +const randomOutcode = callback => { + return Postcode.random((error, { outcode }) => { + callback(error, outcode) }); -} +}; -function randomLocation(callback) { - return Postcode.random(function (error, result) { - callback(error, { - longitude: result.longitude, - latitude: result.latitude - }) +const randomLocation = callback => { + return Postcode.random((error, { longitude, latitude }) => { + callback(error, { longitude, latitude }); }); -} +}; -function lookupRandomPostcode(callback) { +const lookupRandomPostcode = callback => { Postcode.random((error, result) => { if (error) { throw error; @@ -177,32 +171,16 @@ function lookupRandomPostcode(callback) { }); } -function jsonpResponseBody (response) { - // Rough regex to extract json object - const result = response.text.match(/\(.*\)/); - return JSON.parse(result[0].slice(1, result[0].length - 1)); -} - -function allowsCORS (response) { - assert.equal(response.headers["access-control-allow-origin"], "*"); -} - -function validCorsOptions(response) { - assert.equal(response.headers["access-control-allow-origin"], - "*"); - assert.equal(response.headers["access-control-allow-methods"], - "GET,POST,OPTIONS"); - assert.equal(response.headers["access-control-allow-headers"], - "X-Requested-With, Content-Type, Accept, Origin"); -} - module.exports = { // Data config, // Methods - allowsCORS, ...require("./setup.js"), + + // HTTP Helpers + ...require("./http.js"), + removeDiacritics: require("./remove_diacritics"), inferIndexInfo, inferSchemaData, @@ -211,8 +189,6 @@ module.exports = { randomPostcode, randomTerminatedPostcode, randomLocation, - validCorsOptions, - jsonpResponseBody, getCustomRelation, lookupRandomPostcode, locationWithNearbyPostcodes, From b40ca541a035acbffa861d3e9126d4b127ef57a2 Mon Sep 17 00:00:00 2001 From: Chris Blanchard Date: Wed, 5 Dec 2018 18:00:36 +0100 Subject: [PATCH 21/36] Add county electoral districts. Fixes #216 --- CHANGELOG.md | 1 + app/models/postcode.js | 32 +- .../contents/documentation_content.ejs | 450 +++++++++--------- tests/postcode.regression.unit.js | 6 + 4 files changed, 256 insertions(+), 233 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b705e918..9520729c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ Any changes, including backwards incompatible changes will be listed here ## 10.0.0 (29/11/2018) - *Breaking Changes* Only node.js 8 LTS and above supported +- Postcode responses now include County Electoral Districts (ceds) - Updated dependencies ## 9.0.3 (26/10/2018) diff --git a/app/models/postcode.js b/app/models/postcode.js index 9efeb43b..f531ff5c 100644 --- a/app/models/postcode.js +++ b/app/models/postcode.js @@ -1,8 +1,8 @@ "use strict"; -const util = require("util"); +const { inherits } = require("util"); const Pc = require("postcode"); -const async = require("async"); +const { series } = require("async"); const { Base, populateLocation, extractOnspdVal } = require("./base"); const QueryStream = require("pg-query-stream"); const env = process.env.NODE_ENV || "development"; @@ -33,6 +33,7 @@ const postcodeSchema = { "incode" : "VARCHAR(5)", "outcode" : "VARCHAR(5)", "ccg_id" : "VARCHAR(32)", + "ced_id": "VARCHAR(32)", "constituency_id" : "VARCHAR(32)" }; @@ -57,18 +58,22 @@ const relationships = [{ table: "districts", key: "admin_district_id", foreignKey: "code" -}, { +},{ table: "parishes", key: "parish_id", foreignKey: "code" -}, { +},{ table: "counties", key: "admin_county_id", foreignKey: "code" -}, { +},{ table: "wards", key: "admin_ward_id", foreignKey: "code" +},{ + table: "ceds", + key: "ced_id", + foreignKey: "code" },{ table: "ccgs", key: "ccg_id", @@ -98,15 +103,18 @@ const foreignColumns = [{ },{ field: "districts.name", as: "admin_district" -}, { +},{ field: "parishes.name", as: "parish" -}, { +},{ field: "counties.name", as: "admin_county" }, { field: "wards.name", as: "admin_ward" +},{ + field: "ceds.name", + as: "ced" },{ field: "ccgs.name", as: "ccg" @@ -129,7 +137,7 @@ function Postcode () { Base.call(this, "postcodes", postcodeSchema, indexes); } -util.inherits(Postcode, Base); +inherits(Postcode, Base); const findQuery = ` SELECT @@ -181,7 +189,8 @@ Postcode.prototype.whitelistedAttributes = [ "primary_care_trust", "incode", "outcode", - "codes" + "codes", + "ced", ]; Postcode.prototype.loadPostcodeIds = function (type, callback) { @@ -602,6 +611,7 @@ Postcode.prototype.toJson = function (address) { parish: address.parish_id, parliamentary_constituency: address.constituency_id, ccg: address.ccg_id, + ced: address.ced_id, nuts: address.nuts_code }; delete address.id; @@ -612,6 +622,7 @@ Postcode.prototype.toJson = function (address) { delete address.admin_ward_id; delete address.parish_id; delete address.ccg_id; + delete address.ced_id; delete address.nuts_id; delete address.nuts_code; delete address.constituency_id; @@ -619,7 +630,7 @@ Postcode.prototype.toJson = function (address) { }; Postcode.prototype._setupTable = function (filepath, callback) { - async.series([ + series([ this._createRelation.bind(this), this.clear.bind(this), cb => this.seedPostcodes.call(this, filepath, cb), @@ -672,6 +683,7 @@ Postcode.prototype.seedPostcodes = function (filepath, callback) { { column: "nuts_id", method: row => row.extract("nuts") }, { column: "incode", method: row => row.extract("pcds").split(" ")[1] }, { column: "outcode", method: row => row.extract("pcds").split(" ")[0] }, + { column: "ced_id", method: row => row.extract("ced") }, { column: "ccg_id", method: row => row.extract("ccg") }, ]); diff --git a/app/views/includes/contents/documentation_content.ejs b/app/views/includes/contents/documentation_content.ejs index 48bf640b..1ab3977c 100644 --- a/app/views/includes/contents/documentation_content.ejs +++ b/app/views/includes/contents/documentation_content.ejs @@ -121,301 +121,305 @@ msoa
string|null 2011 Census middle layer super output area (MSOA). The 2011 Census middle layer SOA (MSOA) code for England and Wales and intermediate zone for Scotland. - + + ced
string|null + County Electoral District. The county electoral division code for each English postcode. Pseudo codes are included for the remainder of the UK. The field will be blank for English postcodes with no grid reference. English county councils use county electoral divisions (CED) to elect councillors. These CEDs must be confined within district boundaries, but need not be based on whole electoral wards. The only exceptions are the Isles of Scilly and the Greater London Authority (GLA). CEDs do not exist within UAs. + + ccg
string|null Clinical Commissioning Group. Clinical commissioning groups (CCGs) are NHS organisations set up by the Health and Social Care Act 2012 to organise the delivery of NHS services in England. - - + + nuts
string|null Nomenclature of Units for Territorial Statistics (NUTS) / Local Administrative Units (LAU) areas. The LAU2 code for each postcode. NUTS is a hierarchical classification of spatial units that provides a breakdown of the - European Union’s territory for producing regional statistics which are comparable across the Union. The NUTS area classification in the United Kingdom comprises current national administrative and electoral areas, except in Scotland + European Union’s territory for producing regional statistics which are comparable across the Union. The NUTS area classification in the United Kingdom comprises current national administrative and electoral areas, except in Scotland where some NUTS areas comprise whole and/or part Local Enterprise Regions. NUTS levels 1-3 are frozen for a minimum of three years and NUTS levels 4 and 5 are now local Administrative Units (LAU) levels 1 and 2 respectively. - - + + codes
Object Returns an ID or Code associated with the postcode. Typically, these are a 9 character code known as an ONS Code or GSS Code. This is currently only available for districts, parishes, counties, CCGs, NUTS and wards. - - + + codes.admin_district
string|null See description of admin_district field. - - + + codes.admin_county
string|null See description of admin_county field. - - + + codes.admin_ward
string|null See description of admin_ward field. - - + + codes.parish
string|null See description of parish field. - - + + codes.ccg
string|null See description of ccg field. - - + + codes.nuts
string|null See description of nuts field. - - - -

Terminated Postcode Data (Ordnance Survey Postcode Directory Dataset)

-

Data returned by the /terminated_postcodes API

- - - + + +
+

Terminated Postcode Data (Ordnance Survey Postcode Directory Dataset)

+

Data returned by the /terminated_postcodes API

+ + + - - - - + + + + - - + + - - + + - - + + - - + + - - -
Field Description
postcode
string
Postcode. All currently terminated postcodes within the United Kingdom, the Channel Islands and the Isle of Man, received every 3 months from Royal Mail. 2, 3 or 4-character outward code, single space and 3-character inward code.
year_terminated
integer
Termination year.Year of termination of a postcode
month_terminated
integer
Termination month.Month of termination of a postcode. 1-January, 2-February, ..., 12-December
longitude
double float
Longitude.The WGS84 longitude given the Postcode's national grid reference
latitude
double float
Latitude.The WGS84 latitude given the Postcode's national grid reference
-

Places Data (Ordnance Survey Open Names Dataset)

-

Data returned by the /places API

-

Note: Some fields may be null if not applicable or populated. These fields return type is specified as type|null

- - - + + +
+

Places Data (Ordnance Survey Open Names Dataset)

+

Data returned by the /places API

+

Note: Some fields may be null if not applicable or populated. These fields return type is specified as type|null

+ + + - - - - + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + Suburban Area - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - -
Field Description
code
string
code. A unique identifier that enables records to be identified easily. The identifier will be persistent for all LocalTypes except Section of Named Road and Section of Numbered Road.
eastings
integer
Eastings. The Ordnance Survey postcode grid reference Easting to 1 metre resolution; blank for postcodes in the Channel Islands and the Isle of Man.
northings
integer
Northings. The Ordnance Survey postcode grid reference Northing to 1 metre resolution; blank for postcodes in the Channel Islands and the Isle of Man.
[max|min]_northings, [max|min]_eastings
integer
Maximum & Minimum, Northings & Eastings. Bounding box or Minimum Bounding Rectangle (MBR) for roads and settlements. For Settlements and Sections of Named and Numbered Roads, the MBR gives a representation of the extent of these features and is not snapped to the real world extent.
country
string
Country. The country (i.e. one of the four constituent countries of the United Kingdom or the Channel Islands or the Isle of Man) to which each place is assigned.
longitude
double float
Longitude. The WGS84 longitude given the Place's national grid reference
latitude
double float
Latitude. The WGS84 latitude given the Place's national grid reference
local_type
string
Local Type. The Ordnance Survey classification for the named place being represented by the specific feature, e.g. City, Town, Village, Hamlet, Other Settlement, - Suburban Area
outcode
string
Outcode. The postcode district, for example, SO15outcode
string
Outcode. The postcode district, for example, SO15
name_1
string
Name. The proper noun that applies to the real world entity. Names that are prefixed by the definite article are not formatted for alphabetical sorting, that is, ‘The Pennines’ not ‘Pennines, The’.name_1
string
Name. The proper noun that applies to the real world entity. Names that are prefixed by the definite article are not formatted for alphabetical sorting, that is, ‘The Pennines’ not ‘Pennines, The’.
name_1_lang
string|null
Language of Name. The language type is only set where more than one name exists, e.g. cym (Welsh), eng (English), gla (Scottish Gaelic)name_1_lang
string|null
Language of Name. The language type is only set where more than one name exists, e.g. cym (Welsh), eng (English), gla (Scottish Gaelic)
name_2
string|null
Name (in case of multiple languages). The proper noun that applies to the real world entity. Names that are prefixed by the definite article are not formatted for alphabetical sorting, that is, ‘The Pennines’ not ‘Pennines, - The’.name_2
string|null
Name (in case of multiple languages). The proper noun that applies to the real world entity. Names that are prefixed by the definite article are not formatted for alphabetical sorting, that is, ‘The Pennines’ not ‘Pennines, + The’.
name_2_lang
string|null
Language of Name. The language type is only set where more than one name exists, e.g. cym (Welsh), eng (English), gla (Scottish Gaelic)name_2_lang
string|null
Language of Name. The language type is only set where more than one name exists, e.g. cym (Welsh), eng (English), gla (Scottish Gaelic)
county_unitary
string|null
Administrative Area. The name of the County (non-metropolitan or Metropolitan), Unitary Authority or Greater London Authority administrative area that the point geometry for feature is within or nearest to.county_unitary
string|null
Administrative Area. The name of the County (non-metropolitan or Metropolitan), Unitary Authority or Greater London Authority administrative area that the point geometry for feature is within or nearest to.
county_unitary_type
string|null
Administrative Area Type. Classifies the type of administrative unit.county_unitary_type
string|null
Administrative Area Type. Classifies the type of administrative unit.
district_borough
string|null
District or Borough. The name of the District, Metropolitan District or London Borough administrative unit that the point geometry for the feature is within.district_borough
string|null
District or Borough. The name of the District, Metropolitan District or London Borough administrative unit that the point geometry for the feature is within.
district_borough_type
string|null
Borough Type. Classifies the type of administrative unit.district_borough_type
string|null
Borough Type. Classifies the type of administrative unit.
region
string
Region. The name of the European Region (was Government O ice Region) that the point geometry for the feature is within or nearest to.
-

Methods

-

The following is a list of available API methods

-

Postcode Lookup

-

This uniquely identifies a postcode.

-

Returns a single postcode entity for a given postcode (case, space insensitive).

-

If no postcode is found it returns "404" response code.

GET
https://api.postcodes.io/postcodes/:postcode

-

Bulk Postcode Lookup

-

Accepts a JSON object containing an array of postcodes. Returns a list of matching postcodes and respective available data.

-

Be sure to submit JSON requests setting Content-Type to application/json

-

Accepts up to 100 postcodes.

POST
https://api.postcodes.io/postcodes

-

Post Data

-

This method requires a JSON object containing an array of postcodes to be posted, e.g.

{  
-  "postcodes" : ["PR3 0SG", "M45 6GN", "EX165BL"]
-}
-

Optional Query Parameters

-

filter= (not required) A comma separated whitelist of attributes to be returned in the result object(s), e.g. filter=postcode,longitude,latitude. null responses will continue to return null. - If no attributes match the result object, an empty object is returned

-

Reverse Geocoding

-

Returns nearest postcodes for a given longitude and latitude.

GET
https://api.postcodes.io/postcodes?lon=:longitude&lat=:latitude

-

Optional Query Parameters

-

limit= (not required) Limits number of postcodes matches to return. Defaults to 10. Needs to be less than 100.

-

radius= (not required) Limits number of postcodes matches to return. Defaults to 100m. Needs to be less than 2,000m.

-

wideSearch= (not required) Search up to 20km radius, but subject to a maximum of 10 results. Since lookups over a wide area can be very expensive, we've created this method to allow you choose to make the trade off between search - radius and number of results. Defaults to false. When enabled, radius and limits over 10 are ignored.

-

Legacy

-

Note that the old style of reverse geocoding (including optional headers above) will still be supported. Namely,

GET
https://api.postcodes.io/postcodes/lon/:longitude/lat/:latitude

-

Bulk Reverse Geocoding

-

Bulk translates geolocations into Postcodes. Accepts up to 100 geolocations.

POST
https://api.postcodes.io/postcodes

-

Post Data

-

This method requires a JSON object containing an array of geolocation objects to be POSTed. Each geolocation object accepts an optional radius (meters) and limit argument. Both default to 100m and 10 respectively. It also accepts a wideSearch - argument, e.g.

{
+            region 
string + Region. The name of the European Region (was Government O ice Region) that the point geometry for the feature is within or nearest to. + + + +

Methods

+

The following is a list of available API methods

+

Postcode Lookup

+

This uniquely identifies a postcode.

+

Returns a single postcode entity for a given postcode (case, space insensitive).

+

If no postcode is found it returns "404" response code.

GET
https://api.postcodes.io/postcodes/:postcode

+

Bulk Postcode Lookup

+

Accepts a JSON object containing an array of postcodes. Returns a list of matching postcodes and respective available data.

+

Be sure to submit JSON requests setting Content-Type to application/json

+

Accepts up to 100 postcodes.

POST
https://api.postcodes.io/postcodes

+

Post Data

+

This method requires a JSON object containing an array of postcodes to be posted, e.g.

{  
+              "postcodes" : ["PR3 0SG", "M45 6GN", "EX165BL"]
+            }
+

Optional Query Parameters

+

filter= (not required) A comma separated whitelist of attributes to be returned in the result object(s), e.g. filter=postcode,longitude,latitude. null responses will continue to return null. +If no attributes match the result object, an empty object is returned

+

Reverse Geocoding

+

Returns nearest postcodes for a given longitude and latitude.

GET
https://api.postcodes.io/postcodes?lon=:longitude&lat=:latitude

+

Optional Query Parameters

+

limit= (not required) Limits number of postcodes matches to return. Defaults to 10. Needs to be less than 100.

+

radius= (not required) Limits number of postcodes matches to return. Defaults to 100m. Needs to be less than 2,000m.

+

wideSearch= (not required) Search up to 20km radius, but subject to a maximum of 10 results. Since lookups over a wide area can be very expensive, we've created this method to allow you choose to make the trade off between search +radius and number of results. Defaults to false. When enabled, radius and limits over 10 are ignored.

+

Legacy

+

Note that the old style of reverse geocoding (including optional headers above) will still be supported. Namely,

GET
https://api.postcodes.io/postcodes/lon/:longitude/lat/:latitude

+

Bulk Reverse Geocoding

+

Bulk translates geolocations into Postcodes. Accepts up to 100 geolocations.

POST
https://api.postcodes.io/postcodes

+

Post Data

+

This method requires a JSON object containing an array of geolocation objects to be POSTed. Each geolocation object accepts an optional radius (meters) and limit argument. Both default to 100m and 10 respectively. It also accepts a wideSearch +argument, e.g.

{
   "geolocations" : [{
-  "longitude" : -3.15807731271522,
-  "latitude" : 51.4799900627036
+    "longitude" : -3.15807731271522,
+      "latitude" : 51.4799900627036
   },{
-  "longitude" : -1.12935802905177,
-  "latitude" : 50.7186356978817,
-  "limit" : 100,
-  "radius" : 500
+    "longitude" : -1.12935802905177,
+      "latitude" : 50.7186356978817,
+      "limit" : 100,
+      "radius" : 500
   }]
 }
-

Optional Query Parameters

-

filter= (not required) A comma separated whitelist of attributes to be returned in the result object(s). e.g. filter=postcode,longitude,latitude. null responses will continue to return null. - If no attributes match the result object, an empty object is returned

-

wideSearch= (not required) Search up to 20km radius, but subject to a maximum of 10 results

-

Postcode Query

-

Submit a postcode query and receive a complete list of postcode matches and all associated postcode data.

-

This is essentially a postcode search which prefix matches and returns postcodes in sorted order (case insensitive)

-

This method space sensitive, i.e. it detects for spaces between outward and inward parts of the postcode (some examples detailed in this issue)

-

The result set can either be empty or populated with up to 100 postcode entities. Either way it will always return a 200 response code

-

GET
https://api.postcodes.io/postcodes?q=[query]

-

Optional Query Parameters

-

query= (not required) aliases toq=

-

limit= (not required) Limits number of postcodes matches to return. Defaults to 10. Needs to be less than 100.

-

Postcode Validation

-

Convenience method to validate a postcode. Returns true or false (meaning valid or invalid respectively)

GET
https://api.postcodes.io/postcodes/:postcode/validate

-

Nearest Postcode

-

Returns nearest postcodes for a given postcode.

GET
https://api.postcodes.io/postcodes/:postcode/nearest

-

Optional Query Parameters

-

limit= (not required) Limits number of postcodes matches to return. Defaults to 10. Needs to be less than 100.

-

radius= (not required) Limits number of postcodes matches to return. Defaults to 100m. Needs to be less than 2,000m.

-

Postcode Autocomplete

-

Convenience method to return an list of matching postcodes.

GET
https://api.postcodes.io/postcodes/:postcode/autocomplete

-

Optional Query Parameters

-

limit= (not required) Limits number of postcodes matches to return. Defaults to 10. Needs to be less than 100.

-

Random Postcode

-

Returns a random postcode and all available data for that postcode.

GET
https://api.postcodes.io/random/postcodes

-

Optional Query Parameters

-

outcode= (not required) Filters random postcodes by outcode. Returns null if invalid outcode.

-

Outward Code Lookup

-

Geolocation data for the centroid of the outward code specified. The outward code represents the first half of any postcode (separated by a space).

GET
https://api.postcodes.io/outcodes/:outcode

-

Outcode Reverse Geocoding

-

Returns nearest outcodes for a given longitude and latitude.

GET
https://api.postcodes.io/outcodes?lon=:longitude&lat=:latitude

-

Optional Query Parameters

-

limit= (not required) Limits number of postcodes matches to return. Defaults to 10. Needs to be less than 100.

-

radius= (not required) Limits number of postcodes matches to return. Defaults to 5,000m. Needs to be less than 25,000m.

-

Nearest Outcode

-

Returns nearest outcodes for a given outcode.

GET
https://api.postcodes.io/outcodes/:outcode/nearest

-

Optional Query Parameters

-

limit= (not required) Limits number of postcodes matches to return. Defaults to 10. Needs to be less than 100.

-

radius= (not required) Limits number of postcodes matches to return. Defaults to 5,000m. Needs to be less than 25,000m.

-

Terminated Postcode Lookup

-

Lookup a terminated postcode. Returns the postcode, year and month of termination. Returns 404 if postcode does not exist in our database of terminated postcodes or not valid.

GET
https://api.postcodes.io/terminated_postcodes/:postcode

-

Place Lookup

-

Find a place by OSGB code (e.g. "osgb4000000074564391"). Returns all available data if found. Returns 404 if place does not exist.

GET
https://api.postcodes.io/places/:code

-

Place Query

-

Submit a place query and receive a complete list of places matches and associated data.

GET
https://api.postcodes.io/places?q=[query]

-

Optional Query Parameters

-

query= (not required) aliases toq=

-

limit= (not required) Limits number of places matches to return. Defaults to 10. Needs to be less than 100.

-

Random Place

-

Returns a random place and all associated data

GET
https://api.postcodes.io/random/places

-

Install Notes

-

There may be occasions where you want to host postcodes.io yourself. We've laid out instructions below on how to do this.

-

Installing Postcodes.io is matter of cloning the source. Simply run

$ git clone https://github.com/ideal-postcodes/postcodes.io.git

-

We currently provide 3 methods to import the necessary datasets

-
    -
  1. The easiest is to use our install script. This will require postgres superuser privileges to create and provision your database. It will then download the database and import the data from a pg_dump (hosted and created by us).
  2. -
  3. You can download the pg_dump and attempt to import the database yourself with psql or pg_restore.
  4. -
  5. Lastly, you can retrieve the raw CSV data from the ONS and import it with our import script.
  6. -
-

Requirements

- -

Data Download

-

Data is sourced and can be downloaded from the Office for National Statistics Data Portal.The dataset is often not easy to find. The simplest way is to search - for "Postcode Directory".

-

Alternatively you can download a pg_dumpof the postcode database which is hosted by us. This is the same dump which is used by the install script. The link to the latest - dump can be found in our repository.

-

Install Script

-

Currently the install process targets *nix systems. We've provisioned and tested on OS X Mavericks & Ubuntu 14.04. Please let us know if you have issues on other operating systems.

-

Postcodes.io is packaged with a script to provision postgres and download a dump (pg_dump) of the ONS Postcode Directory (hosted by us). The script will - do the following,

-
    -
  • Prompt you for your postgres superuser credentials (most of the setup steps require this level of privilege)
  • -
  • Create a new user "postcodesio"
  • -
  • Create a new database "postcodesiodb" owned by "postcodesio"
  • -
  • Create the necessary extensions on "postcodesiodb"
  • -
  • Download, unzip and stream the Postcode directory into Postgres
  • -
-

To run,

$ npm run setup

-

Manual Installation from our pg_dump

-

To setup the database from our pg_dump simply download, gunzip and use either psql or pg_restore to import the data into Postgresql.

-

For example,

$ cat postcodeiodb.sql | psql postcodesiodb

-

Manual Installation

-

The install scripts can be linked to your shell (or can be found in the bin/ directory). To link, navigate to postcodes.io/ and run $ npm link

-

ONS Postcode Directory

-

Download the latest Office for National Statistic's "Postcode Lookup Dataset". Unzip the data locally. Run the import script $ postcodesio:import_onspd passing the path to the CSV data.

$ postcodesio:import_onspd /path/to/data/ONSPD/Data/data.csv

-

OS Open Names

-

Download the latest Ordnance Survey Open Names dataset. Unzip the data locally. Run the import script $ postcodesio:import_oson passing the path that contains the CSV data files.

$ postcodesio:import_oson /path/to/data/opname_csv_gb/Data/

- - - \ No newline at end of file +

Optional Query Parameters

+

filter= (not required) A comma separated whitelist of attributes to be returned in the result object(s). e.g. filter=postcode,longitude,latitude. null responses will continue to return null. +If no attributes match the result object, an empty object is returned

+

wideSearch= (not required) Search up to 20km radius, but subject to a maximum of 10 results

+

Postcode Query

+

Submit a postcode query and receive a complete list of postcode matches and all associated postcode data.

+

This is essentially a postcode search which prefix matches and returns postcodes in sorted order (case insensitive)

+

This method space sensitive, i.e. it detects for spaces between outward and inward parts of the postcode (some examples detailed in this issue)

+

The result set can either be empty or populated with up to 100 postcode entities. Either way it will always return a 200 response code

+

GET
https://api.postcodes.io/postcodes?q=[query]

+

Optional Query Parameters

+

query= (not required) aliases toq=

+

limit= (not required) Limits number of postcodes matches to return. Defaults to 10. Needs to be less than 100.

+

Postcode Validation

+

Convenience method to validate a postcode. Returns true or false (meaning valid or invalid respectively)

GET
https://api.postcodes.io/postcodes/:postcode/validate

+

Nearest Postcode

+

Returns nearest postcodes for a given postcode.

GET
https://api.postcodes.io/postcodes/:postcode/nearest

+

Optional Query Parameters

+

limit= (not required) Limits number of postcodes matches to return. Defaults to 10. Needs to be less than 100.

+

radius= (not required) Limits number of postcodes matches to return. Defaults to 100m. Needs to be less than 2,000m.

+

Postcode Autocomplete

+

Convenience method to return an list of matching postcodes.

GET
https://api.postcodes.io/postcodes/:postcode/autocomplete

+

Optional Query Parameters

+

limit= (not required) Limits number of postcodes matches to return. Defaults to 10. Needs to be less than 100.

+

Random Postcode

+

Returns a random postcode and all available data for that postcode.

GET
https://api.postcodes.io/random/postcodes

+

Optional Query Parameters

+

outcode= (not required) Filters random postcodes by outcode. Returns null if invalid outcode.

+

Outward Code Lookup

+

Geolocation data for the centroid of the outward code specified. The outward code represents the first half of any postcode (separated by a space).

GET
https://api.postcodes.io/outcodes/:outcode

+

Outcode Reverse Geocoding

+

Returns nearest outcodes for a given longitude and latitude.

GET
https://api.postcodes.io/outcodes?lon=:longitude&lat=:latitude

+

Optional Query Parameters

+

limit= (not required) Limits number of postcodes matches to return. Defaults to 10. Needs to be less than 100.

+

radius= (not required) Limits number of postcodes matches to return. Defaults to 5,000m. Needs to be less than 25,000m.

+

Nearest Outcode

+

Returns nearest outcodes for a given outcode.

GET
https://api.postcodes.io/outcodes/:outcode/nearest

+

Optional Query Parameters

+

limit= (not required) Limits number of postcodes matches to return. Defaults to 10. Needs to be less than 100.

+

radius= (not required) Limits number of postcodes matches to return. Defaults to 5,000m. Needs to be less than 25,000m.

+

Terminated Postcode Lookup

+

Lookup a terminated postcode. Returns the postcode, year and month of termination. Returns 404 if postcode does not exist in our database of terminated postcodes or not valid.

GET
https://api.postcodes.io/terminated_postcodes/:postcode

+

Place Lookup

+

Find a place by OSGB code (e.g. "osgb4000000074564391"). Returns all available data if found. Returns 404 if place does not exist.

GET
https://api.postcodes.io/places/:code

+

Place Query

+

Submit a place query and receive a complete list of places matches and associated data.

GET
https://api.postcodes.io/places?q=[query]

+

Optional Query Parameters

+

query= (not required) aliases toq=

+

limit= (not required) Limits number of places matches to return. Defaults to 10. Needs to be less than 100.

+

Random Place

+

Returns a random place and all associated data

GET
https://api.postcodes.io/random/places

+

Install Notes

+

There may be occasions where you want to host postcodes.io yourself. We've laid out instructions below on how to do this.

+

Installing Postcodes.io is matter of cloning the source. Simply run

$ git clone https://github.com/ideal-postcodes/postcodes.io.git

+

We currently provide 3 methods to import the necessary datasets

+
    +
  1. The easiest is to use our install script. This will require postgres superuser privileges to create and provision your database. It will then download the database and import the data from a pg_dump (hosted and created by us).
  2. +
  3. You can download the pg_dump and attempt to import the database yourself with psql or pg_restore.
  4. +
  5. Lastly, you can retrieve the raw CSV data from the ONS and import it with our import script.
  6. +
+

Requirements

+ +

Data Download

+

Data is sourced and can be downloaded from the Office for National Statistics Data Portal.The dataset is often not easy to find. The simplest way is to search +for "Postcode Directory".

+

Alternatively you can download a pg_dumpof the postcode database which is hosted by us. This is the same dump which is used by the install script. The link to the latest +dump can be found in our repository.

+

Install Script

+

Currently the install process targets *nix systems. We've provisioned and tested on OS X Mavericks & Ubuntu 14.04. Please let us know if you have issues on other operating systems.

+

Postcodes.io is packaged with a script to provision postgres and download a dump (pg_dump) of the ONS Postcode Directory (hosted by us). The script will +do the following,

+
    +
  • Prompt you for your postgres superuser credentials (most of the setup steps require this level of privilege)
  • +
  • Create a new user "postcodesio"
  • +
  • Create a new database "postcodesiodb" owned by "postcodesio"
  • +
  • Create the necessary extensions on "postcodesiodb"
  • +
  • Download, unzip and stream the Postcode directory into Postgres
  • +
+

To run,

$ npm run setup

+

Manual Installation from our pg_dump

+

To setup the database from our pg_dump simply download, gunzip and use either psql or pg_restore to import the data into Postgresql.

+

For example,

$ cat postcodeiodb.sql | psql postcodesiodb

+

Manual Installation

+

The install scripts can be linked to your shell (or can be found in the bin/ directory). To link, navigate to postcodes.io/ and run $ npm link

+

ONS Postcode Directory

+

Download the latest Office for National Statistic's "Postcode Lookup Dataset". Unzip the data locally. Run the import script $ postcodesio:import_onspd passing the path to the CSV data.

$ postcodesio:import_onspd /path/to/data/ONSPD/Data/data.csv

+

OS Open Names

+

Download the latest Ordnance Survey Open Names dataset. Unzip the data locally. Run the import script $ postcodesio:import_oson passing the path that contains the CSV data files.

$ postcodesio:import_oson /path/to/data/opname_csv_gb/Data/

+ + + diff --git a/tests/postcode.regression.unit.js b/tests/postcode.regression.unit.js index 3f2e0db1..5ab2c77d 100644 --- a/tests/postcode.regression.unit.js +++ b/tests/postcode.regression.unit.js @@ -45,6 +45,8 @@ describe("Postcode data regression testing", function () { incode: '3BS', outcode: 'AB12', ccg_id: 'S03000012', + ced: null, + ced_id: 'S99999999', constituency_id: 'S14000002', parliamentary_constituency: 'Aberdeen South', admin_district: 'Aberdeen City', @@ -87,6 +89,8 @@ describe("Postcode data regression testing", function () { incode: '5ZZ', outcode: 'SE1P', ccg_id: 'E38000171', + ced: null, + ced_id: 'E99999999', constituency_id: 'E14000553', parliamentary_constituency: 'Bermondsey and Old Southwark', admin_district: 'Southwark', @@ -131,6 +135,8 @@ describe("Postcode data regression testing", function () { incode: '4WD', outcode: 'JE2', ccg_id: 'L99999999', + ced: null, + ced_id: 'L99999999', constituency_id: 'L99999999', parliamentary_constituency: null, admin_district: null, From 0f37358e1ce15787c7a909844b9e29e9a1fdd31a Mon Sep 17 00:00:00 2001 From: Chris Blanchard Date: Wed, 5 Dec 2018 18:34:12 +0100 Subject: [PATCH 22/36] Add outcodes documentation. Fixes #124 --- CHANGELOG.md | 1 + .../contents/documentation_content.ejs | 502 ++++++++++-------- 2 files changed, 279 insertions(+), 224 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9520729c..6fa123a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ Any changes, including backwards incompatible changes will be listed here - *Breaking Changes* Only node.js 8 LTS and above supported - Postcode responses now include County Electoral Districts (ceds) +- Updated documentation (outcodes, installation) - Updated dependencies ## 9.0.3 (26/10/2018) diff --git a/app/views/includes/contents/documentation_content.ejs b/app/views/includes/contents/documentation_content.ejs index 1ab3977c..852aa71e 100644 --- a/app/views/includes/contents/documentation_content.ejs +++ b/app/views/includes/contents/documentation_content.ejs @@ -24,7 +24,7 @@

If we make backwards-incompatible changes in the future, this will be released under a versioned endpoint.

Available Data Fields

Postcode Data (Ordnance Survey Postcode Directory Dataset)

-

Data returned by the /postcodes and /outcodes API

+

Data points returned by the /postcodes and /outcodes API

Note: Some fields may be null if not applicable or populated. These fields return type is specified as type|null

@@ -124,302 +124,356 @@ - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - -
ced
string|null
County Electoral District. The county electoral division code for each English postcode. Pseudo codes are included for the remainder of the UK. The field will be blank for English postcodes with no grid reference. English county councils use county electoral divisions (CED) to elect councillors. These CEDs must be confined within district boundaries, but need not be based on whole electoral wards. The only exceptions are the Isles of Scilly and the Greater London Authority (GLA). CEDs do not exist within UAs.
ccg
string|null
Clinical Commissioning Group. Clinical commissioning groups (CCGs) are NHS organisations set up by the Health and Social Care Act 2012 to organise the delivery of NHS services in England.
nuts
string|null
Nomenclature of Units for Territorial Statistics (NUTS) / Local Administrative Units (LAU) areas. The LAU2 code for each postcode. NUTS is a hierarchical classification of spatial units that provides a breakdown of the - European Union’s territory for producing regional statistics which are comparable across the Union. The NUTS area classification in the United Kingdom comprises current national administrative and electoral areas, except in Scotland + European Union’s territory for producing regional statistics which are comparable across the Union. The NUTS area classification in the United Kingdom comprises current national administrative and electoral areas, except in Scotland where some NUTS areas comprise whole and/or part Local Enterprise Regions. NUTS levels 1-3 are frozen for a minimum of three years and NUTS levels 4 and 5 are now local Administrative Units (LAU) levels 1 and 2 respectively.
codes
Object
Returns an ID or Code associated with the postcode. Typically, these are a 9 character code known as an ONS Code or GSS Code. This is currently only available for districts, parishes, counties, CCGs, NUTS and wards.
codes.admin_district
string|null
See description of admin_district field.
codes.admin_county
string|null
See description of admin_county field.
codes.admin_ward
string|null
See description of admin_ward field.
codes.parish
string|null
See description of parish field.
codes.ccg
string|null
See description of ccg field.
codes.nuts
string|null
See description of nuts field.
-

Terminated Postcode Data (Ordnance Survey Postcode Directory Dataset)

-

Data returned by the /terminated_postcodes API

- - - + + +
+ +

Outcode Data (Ordnance Survey Postcode Directory Dataset)

+

Data returned by the /outcodes API

+ + + + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Field Description
outcode
string
Outcode. The outcode or outward code is the part of the postcode before the single space in the middle. It is between two and four characters long. Examples of outward codes include "L1", "W1A", "RH1", "RH10" or "SE1P". A few outward codes are non-geographic, not divulging where mail is to be sent.
eastings
integer|null
Eastings. The Ordnance Survey postcode grid reference Easting to 1 metre resolution; blank for postcodes in the Channel Islands and the Isle of Man. Grid references for postcodes in Northern Ireland relate to the Irish Grid system. Computed as the arithmetic mean of the input coordinates.
northings
integer|null
Northings. The Ordnance Survey postcode grid reference Northing to 1 metre resolution; blank for postcodes in the Channel Islands and the Isle of Man. Grid references for postcodes in Northern Ireland relate to the Irish Grid system. Computed as the arithmetic mean of the input coordinates.
admin_county
string[]
County. The current counties under the outcode.
admin_district
string[]
District. The current district/unitary authorites under the outcode.
admin_ward
string[]
Ward. The current administrative/electoral areas under the outcode.
longitude
double float
Longitude. The WGS84 longitude given the Postcode's national grid reference. Computed as the arithmetic mean of input coordinates.
latitude
double float
Latitude. The WGS84 latitude given the Postcode's national grid reference. Computed as the arithmetic mean of input coordinates.
country
string[]
Country. The countries included in the outcode.
parish
string[]
Parish (England)/ community (Wales). The parishes included in the outcode.
+

Terminated Postcode Data (Ordnance Survey Postcode Directory Dataset)

+

Data returned by the /terminated_postcodes API

+ + + + + + + + + - - + + - - + + - - + + - - + + - - -
FieldDescription
postcode
string
Postcode. All currently terminated postcodes within the United Kingdom, the Channel Islands and the Isle of Man, received every 3 months from Royal Mail. 2, 3 or 4-character outward code, single space and 3-character inward code.
year_terminated
integer
Termination year.Year of termination of a postcode
month_terminated
integer
Termination month.Month of termination of a postcode. 1-January, 2-February, ..., 12-December
longitude
double float
Longitude.The WGS84 longitude given the Postcode's national grid reference
latitude
double float
Latitude.The WGS84 latitude given the Postcode's national grid reference
-

Places Data (Ordnance Survey Open Names Dataset)

-

Data returned by the /places API

-

Note: Some fields may be null if not applicable or populated. These fields return type is specified as type|null

- - - + + +
+

Places Data (Ordnance Survey Open Names Dataset)

+

Data returned by the /places API

+

Note: Some fields may be null if not applicable or populated. These fields return type is specified as type|null

+ + + - - - - + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + Suburban Area - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - -
Field Description
code
string
code. A unique identifier that enables records to be identified easily. The identifier will be persistent for all LocalTypes except Section of Named Road and Section of Numbered Road.
eastings
integer
Eastings. The Ordnance Survey postcode grid reference Easting to 1 metre resolution; blank for postcodes in the Channel Islands and the Isle of Man.
northings
integer
Northings. The Ordnance Survey postcode grid reference Northing to 1 metre resolution; blank for postcodes in the Channel Islands and the Isle of Man.
[max|min]_northings, [max|min]_eastings
integer
Maximum & Minimum, Northings & Eastings. Bounding box or Minimum Bounding Rectangle (MBR) for roads and settlements. For Settlements and Sections of Named and Numbered Roads, the MBR gives a representation of the extent of these features and is not snapped to the real world extent.
country
string
Country. The country (i.e. one of the four constituent countries of the United Kingdom or the Channel Islands or the Isle of Man) to which each place is assigned.
longitude
double float
Longitude. The WGS84 longitude given the Place's national grid reference
latitude
double float
Latitude. The WGS84 latitude given the Place's national grid reference
local_type
string
Local Type. The Ordnance Survey classification for the named place being represented by the specific feature, e.g. City, Town, Village, Hamlet, Other Settlement, - Suburban Area
outcode
string
Outcode. The postcode district, for example, SO15outcode
string
Outcode. The postcode district, for example, SO15
name_1
string
Name. The proper noun that applies to the real world entity. Names that are prefixed by the definite article are not formatted for alphabetical sorting, that is, ‘The Pennines’ not ‘Pennines, The’.name_1
string
Name. The proper noun that applies to the real world entity. Names that are prefixed by the definite article are not formatted for alphabetical sorting, that is, ‘The Pennines’ not ‘Pennines, The’.
name_1_lang
string|null
Language of Name. The language type is only set where more than one name exists, e.g. cym (Welsh), eng (English), gla (Scottish Gaelic)name_1_lang
string|null
Language of Name. The language type is only set where more than one name exists, e.g. cym (Welsh), eng (English), gla (Scottish Gaelic)
name_2
string|null
Name (in case of multiple languages). The proper noun that applies to the real world entity. Names that are prefixed by the definite article are not formatted for alphabetical sorting, that is, ‘The Pennines’ not ‘Pennines, - The’.name_2
string|null
Name (in case of multiple languages). The proper noun that applies to the real world entity. Names that are prefixed by the definite article are not formatted for alphabetical sorting, that is, ‘The Pennines’ not ‘Pennines, + The’.
name_2_lang
string|null
Language of Name. The language type is only set where more than one name exists, e.g. cym (Welsh), eng (English), gla (Scottish Gaelic)name_2_lang
string|null
Language of Name. The language type is only set where more than one name exists, e.g. cym (Welsh), eng (English), gla (Scottish Gaelic)
county_unitary
string|null
Administrative Area. The name of the County (non-metropolitan or Metropolitan), Unitary Authority or Greater London Authority administrative area that the point geometry for feature is within or nearest to.county_unitary
string|null
Administrative Area. The name of the County (non-metropolitan or Metropolitan), Unitary Authority or Greater London Authority administrative area that the point geometry for feature is within or nearest to.
county_unitary_type
string|null
Administrative Area Type. Classifies the type of administrative unit.county_unitary_type
string|null
Administrative Area Type. Classifies the type of administrative unit.
district_borough
string|null
District or Borough. The name of the District, Metropolitan District or London Borough administrative unit that the point geometry for the feature is within.district_borough
string|null
District or Borough. The name of the District, Metropolitan District or London Borough administrative unit that the point geometry for the feature is within.
district_borough_type
string|null
Borough Type. Classifies the type of administrative unit.district_borough_type
string|null
Borough Type. Classifies the type of administrative unit.
region
string
Region. The name of the European Region (was Government O ice Region) that the point geometry for the feature is within or nearest to.
-

Methods

-

The following is a list of available API methods

-

Postcode Lookup

-

This uniquely identifies a postcode.

-

Returns a single postcode entity for a given postcode (case, space insensitive).

-

If no postcode is found it returns "404" response code.

GET
https://api.postcodes.io/postcodes/:postcode

-

Bulk Postcode Lookup

-

Accepts a JSON object containing an array of postcodes. Returns a list of matching postcodes and respective available data.

-

Be sure to submit JSON requests setting Content-Type to application/json

-

Accepts up to 100 postcodes.

POST
https://api.postcodes.io/postcodes

-

Post Data

-

This method requires a JSON object containing an array of postcodes to be posted, e.g.

{  
-              "postcodes" : ["PR3 0SG", "M45 6GN", "EX165BL"]
-            }
-

Optional Query Parameters

-

filter= (not required) A comma separated whitelist of attributes to be returned in the result object(s), e.g. filter=postcode,longitude,latitude. null responses will continue to return null. -If no attributes match the result object, an empty object is returned

-

Reverse Geocoding

-

Returns nearest postcodes for a given longitude and latitude.

GET
https://api.postcodes.io/postcodes?lon=:longitude&lat=:latitude

-

Optional Query Parameters

-

limit= (not required) Limits number of postcodes matches to return. Defaults to 10. Needs to be less than 100.

-

radius= (not required) Limits number of postcodes matches to return. Defaults to 100m. Needs to be less than 2,000m.

-

wideSearch= (not required) Search up to 20km radius, but subject to a maximum of 10 results. Since lookups over a wide area can be very expensive, we've created this method to allow you choose to make the trade off between search -radius and number of results. Defaults to false. When enabled, radius and limits over 10 are ignored.

-

Legacy

-

Note that the old style of reverse geocoding (including optional headers above) will still be supported. Namely,

GET
https://api.postcodes.io/postcodes/lon/:longitude/lat/:latitude

-

Bulk Reverse Geocoding

-

Bulk translates geolocations into Postcodes. Accepts up to 100 geolocations.

POST
https://api.postcodes.io/postcodes

-

Post Data

-

This method requires a JSON object containing an array of geolocation objects to be POSTed. Each geolocation object accepts an optional radius (meters) and limit argument. Both default to 100m and 10 respectively. It also accepts a wideSearch -argument, e.g.

{
+              region 
string + Region. The name of the European Region (was Government O ice Region) that the point geometry for the feature is within or nearest to. + + + +

Methods

+

The following is a list of available API methods

+

Postcode Lookup

+

This uniquely identifies a postcode.

+

Returns a single postcode entity for a given postcode (case, space insensitive).

+

If no postcode is found it returns "404" response code.

GET
https://api.postcodes.io/postcodes/:postcode

+

Bulk Postcode Lookup

+

Accepts a JSON object containing an array of postcodes. Returns a list of matching postcodes and respective available data.

+

Be sure to submit JSON requests setting Content-Type to application/json

+

Accepts up to 100 postcodes.

POST
https://api.postcodes.io/postcodes

+

Post Data

+

This method requires a JSON object containing an array of postcodes to be posted, e.g.

{  
+  "postcodes" : ["PR3 0SG", "M45 6GN", "EX165BL"]
+}
+

Optional Query Parameters

+

filter= (not required) A comma separated whitelist of attributes to be returned in the result object(s), e.g. filter=postcode,longitude,latitude. null responses will continue to return null. + If no attributes match the result object, an empty object is returned

+

Reverse Geocoding

+

Returns nearest postcodes for a given longitude and latitude.

GET
https://api.postcodes.io/postcodes?lon=:longitude&lat=:latitude

+

Optional Query Parameters

+

limit= (not required) Limits number of postcodes matches to return. Defaults to 10. Needs to be less than 100.

+

radius= (not required) Limits number of postcodes matches to return. Defaults to 100m. Needs to be less than 2,000m.

+

wideSearch= (not required) Search up to 20km radius, but subject to a maximum of 10 results. Since lookups over a wide area can be very expensive, we've created this method to allow you choose to make the trade off between search + radius and number of results. Defaults to false. When enabled, radius and limits over 10 are ignored.

+

Legacy

+

Note that the old style of reverse geocoding (including optional headers above) will still be supported. Namely,

GET
https://api.postcodes.io/postcodes/lon/:longitude/lat/:latitude

+

Bulk Reverse Geocoding

+

Bulk translates geolocations into Postcodes. Accepts up to 100 geolocations.

POST
https://api.postcodes.io/postcodes

+

Post Data

+

This method requires a JSON object containing an array of geolocation objects to be POSTed. Each geolocation object accepts an optional radius (meters) and limit argument. Both default to 100m and 10 respectively. It also accepts a wideSearch + argument, e.g.

{
   "geolocations" : [{
-    "longitude" : -3.15807731271522,
-      "latitude" : 51.4799900627036
+  "longitude" : -3.15807731271522,
+  "latitude" : 51.4799900627036
   },{
-    "longitude" : -1.12935802905177,
-      "latitude" : 50.7186356978817,
-      "limit" : 100,
-      "radius" : 500
+  "longitude" : -1.12935802905177,
+  "latitude" : 50.7186356978817,
+  "limit" : 100,
+  "radius" : 500
   }]
 }
-

Optional Query Parameters

-

filter= (not required) A comma separated whitelist of attributes to be returned in the result object(s). e.g. filter=postcode,longitude,latitude. null responses will continue to return null. -If no attributes match the result object, an empty object is returned

-

wideSearch= (not required) Search up to 20km radius, but subject to a maximum of 10 results

-

Postcode Query

-

Submit a postcode query and receive a complete list of postcode matches and all associated postcode data.

-

This is essentially a postcode search which prefix matches and returns postcodes in sorted order (case insensitive)

-

This method space sensitive, i.e. it detects for spaces between outward and inward parts of the postcode (some examples detailed in this issue)

-

The result set can either be empty or populated with up to 100 postcode entities. Either way it will always return a 200 response code

-

GET
https://api.postcodes.io/postcodes?q=[query]

-

Optional Query Parameters

-

query= (not required) aliases toq=

-

limit= (not required) Limits number of postcodes matches to return. Defaults to 10. Needs to be less than 100.

-

Postcode Validation

-

Convenience method to validate a postcode. Returns true or false (meaning valid or invalid respectively)

GET
https://api.postcodes.io/postcodes/:postcode/validate

-

Nearest Postcode

-

Returns nearest postcodes for a given postcode.

GET
https://api.postcodes.io/postcodes/:postcode/nearest

-

Optional Query Parameters

-

limit= (not required) Limits number of postcodes matches to return. Defaults to 10. Needs to be less than 100.

-

radius= (not required) Limits number of postcodes matches to return. Defaults to 100m. Needs to be less than 2,000m.

-

Postcode Autocomplete

-

Convenience method to return an list of matching postcodes.

GET
https://api.postcodes.io/postcodes/:postcode/autocomplete

-

Optional Query Parameters

-

limit= (not required) Limits number of postcodes matches to return. Defaults to 10. Needs to be less than 100.

-

Random Postcode

-

Returns a random postcode and all available data for that postcode.

GET
https://api.postcodes.io/random/postcodes

-

Optional Query Parameters

-

outcode= (not required) Filters random postcodes by outcode. Returns null if invalid outcode.

-

Outward Code Lookup

-

Geolocation data for the centroid of the outward code specified. The outward code represents the first half of any postcode (separated by a space).

GET
https://api.postcodes.io/outcodes/:outcode

-

Outcode Reverse Geocoding

-

Returns nearest outcodes for a given longitude and latitude.

GET
https://api.postcodes.io/outcodes?lon=:longitude&lat=:latitude

-

Optional Query Parameters

-

limit= (not required) Limits number of postcodes matches to return. Defaults to 10. Needs to be less than 100.

-

radius= (not required) Limits number of postcodes matches to return. Defaults to 5,000m. Needs to be less than 25,000m.

-

Nearest Outcode

-

Returns nearest outcodes for a given outcode.

GET
https://api.postcodes.io/outcodes/:outcode/nearest

-

Optional Query Parameters

-

limit= (not required) Limits number of postcodes matches to return. Defaults to 10. Needs to be less than 100.

-

radius= (not required) Limits number of postcodes matches to return. Defaults to 5,000m. Needs to be less than 25,000m.

-

Terminated Postcode Lookup

-

Lookup a terminated postcode. Returns the postcode, year and month of termination. Returns 404 if postcode does not exist in our database of terminated postcodes or not valid.

GET
https://api.postcodes.io/terminated_postcodes/:postcode

-

Place Lookup

-

Find a place by OSGB code (e.g. "osgb4000000074564391"). Returns all available data if found. Returns 404 if place does not exist.

GET
https://api.postcodes.io/places/:code

-

Place Query

-

Submit a place query and receive a complete list of places matches and associated data.

GET
https://api.postcodes.io/places?q=[query]

-

Optional Query Parameters

-

query= (not required) aliases toq=

-

limit= (not required) Limits number of places matches to return. Defaults to 10. Needs to be less than 100.

-

Random Place

-

Returns a random place and all associated data

GET
https://api.postcodes.io/random/places

-

Install Notes

-

There may be occasions where you want to host postcodes.io yourself. We've laid out instructions below on how to do this.

-

Installing Postcodes.io is matter of cloning the source. Simply run

$ git clone https://github.com/ideal-postcodes/postcodes.io.git

-

We currently provide 3 methods to import the necessary datasets

-
    -
  1. The easiest is to use our install script. This will require postgres superuser privileges to create and provision your database. It will then download the database and import the data from a pg_dump (hosted and created by us).
  2. -
  3. You can download the pg_dump and attempt to import the database yourself with psql or pg_restore.
  4. -
  5. Lastly, you can retrieve the raw CSV data from the ONS and import it with our import script.
  6. -
-

Requirements

- -

Data Download

-

Data is sourced and can be downloaded from the Office for National Statistics Data Portal.The dataset is often not easy to find. The simplest way is to search -for "Postcode Directory".

-

Alternatively you can download a pg_dumpof the postcode database which is hosted by us. This is the same dump which is used by the install script. The link to the latest -dump can be found in our repository.

-

Install Script

-

Currently the install process targets *nix systems. We've provisioned and tested on OS X Mavericks & Ubuntu 14.04. Please let us know if you have issues on other operating systems.

-

Postcodes.io is packaged with a script to provision postgres and download a dump (pg_dump) of the ONS Postcode Directory (hosted by us). The script will -do the following,

-
    -
  • Prompt you for your postgres superuser credentials (most of the setup steps require this level of privilege)
  • -
  • Create a new user "postcodesio"
  • -
  • Create a new database "postcodesiodb" owned by "postcodesio"
  • -
  • Create the necessary extensions on "postcodesiodb"
  • -
  • Download, unzip and stream the Postcode directory into Postgres
  • -
-

To run,

$ npm run setup

-

Manual Installation from our pg_dump

-

To setup the database from our pg_dump simply download, gunzip and use either psql or pg_restore to import the data into Postgresql.

-

For example,

$ cat postcodeiodb.sql | psql postcodesiodb

-

Manual Installation

-

The install scripts can be linked to your shell (or can be found in the bin/ directory). To link, navigate to postcodes.io/ and run $ npm link

-

ONS Postcode Directory

-

Download the latest Office for National Statistic's "Postcode Lookup Dataset". Unzip the data locally. Run the import script $ postcodesio:import_onspd passing the path to the CSV data.

$ postcodesio:import_onspd /path/to/data/ONSPD/Data/data.csv

-

OS Open Names

-

Download the latest Ordnance Survey Open Names dataset. Unzip the data locally. Run the import script $ postcodesio:import_oson passing the path that contains the CSV data files.

$ postcodesio:import_oson /path/to/data/opname_csv_gb/Data/

- - +

Optional Query Parameters

+

filter= (not required) A comma separated whitelist of attributes to be returned in the result object(s). e.g. filter=postcode,longitude,latitude. null responses will continue to return null. + If no attributes match the result object, an empty object is returned

+

wideSearch= (not required) Search up to 20km radius, but subject to a maximum of 10 results

+

Postcode Query

+

Submit a postcode query and receive a complete list of postcode matches and all associated postcode data.

+

This is essentially a postcode search which prefix matches and returns postcodes in sorted order (case insensitive)

+

This method space sensitive, i.e. it detects for spaces between outward and inward parts of the postcode (some examples detailed in this issue)

+

The result set can either be empty or populated with up to 100 postcode entities. Either way it will always return a 200 response code

+

GET
https://api.postcodes.io/postcodes?q=[query]

+

Optional Query Parameters

+

query= (not required) aliases toq=

+

limit= (not required) Limits number of postcodes matches to return. Defaults to 10. Needs to be less than 100.

+

Postcode Validation

+

Convenience method to validate a postcode. Returns true or false (meaning valid or invalid respectively)

GET
https://api.postcodes.io/postcodes/:postcode/validate

+

Nearest Postcode

+

Returns nearest postcodes for a given postcode.

GET
https://api.postcodes.io/postcodes/:postcode/nearest

+

Optional Query Parameters

+

limit= (not required) Limits number of postcodes matches to return. Defaults to 10. Needs to be less than 100.

+

radius= (not required) Limits number of postcodes matches to return. Defaults to 100m. Needs to be less than 2,000m.

+

Postcode Autocomplete

+

Convenience method to return an list of matching postcodes.

GET
https://api.postcodes.io/postcodes/:postcode/autocomplete

+

Optional Query Parameters

+

limit= (not required) Limits number of postcodes matches to return. Defaults to 10. Needs to be less than 100.

+

Random Postcode

+

Returns a random postcode and all available data for that postcode.

GET
https://api.postcodes.io/random/postcodes

+

Optional Query Parameters

+

outcode= (not required) Filters random postcodes by outcode. Returns null if invalid outcode.

+

Outward Code Lookup

+

Geolocation data for the centroid of the outward code specified. The outward code represents the first half of any postcode (separated by a space).

GET
https://api.postcodes.io/outcodes/:outcode

+

Outcode Reverse Geocoding

+

Returns nearest outcodes for a given longitude and latitude.

GET
https://api.postcodes.io/outcodes?lon=:longitude&lat=:latitude

+

Optional Query Parameters

+

limit= (not required) Limits number of postcodes matches to return. Defaults to 10. Needs to be less than 100.

+

radius= (not required) Limits number of postcodes matches to return. Defaults to 5,000m. Needs to be less than 25,000m.

+

Nearest Outcode

+

Returns nearest outcodes for a given outcode.

GET
https://api.postcodes.io/outcodes/:outcode/nearest

+

Optional Query Parameters

+

limit= (not required) Limits number of postcodes matches to return. Defaults to 10. Needs to be less than 100.

+

radius= (not required) Limits number of postcodes matches to return. Defaults to 5,000m. Needs to be less than 25,000m.

+

Terminated Postcode Lookup

+

Lookup a terminated postcode. Returns the postcode, year and month of termination. Returns 404 if postcode does not exist in our database of terminated postcodes or not valid.

GET
https://api.postcodes.io/terminated_postcodes/:postcode

+

Place Lookup

+

Find a place by OSGB code (e.g. "osgb4000000074564391"). Returns all available data if found. Returns 404 if place does not exist.

GET
https://api.postcodes.io/places/:code

+

Place Query

+

Submit a place query and receive a complete list of places matches and associated data.

GET
https://api.postcodes.io/places?q=[query]

+

Optional Query Parameters

+

query= (not required) aliases toq=

+

limit= (not required) Limits number of places matches to return. Defaults to 10. Needs to be less than 100.

+

Random Place

+

Returns a random place and all associated data

GET
https://api.postcodes.io/random/places

+

Install Notes

+

There may be occasions where you want to host postcodes.io yourself. We've laid out instructions below on how to do this.

+

Installing Postcodes.io is matter of cloning the source. Simply run

$ git clone https://github.com/ideal-postcodes/postcodes.io.git

+

We currently provide 3 methods to import the necessary datasets

+
    +
  1. The easiest is to use our install script. This will require postgres superuser privileges to create and provision your database. It will then download the database and import the data from a pg_dump (hosted and created by us).
  2. +
  3. You can download the pg_dump and attempt to import the database yourself with psql or pg_restore.
  4. +
  5. Lastly, you can retrieve the raw CSV data from the ONS and import it with our import script.
  6. +
+

Requirements

+ +

Data Download

+

Data is sourced and can be downloaded from the Office for National Statistics Data Portal.The dataset is often not easy to find. The simplest way is to search + for "Postcode Directory".

+

Alternatively you can download a pg_dumpof the postcode database which is hosted by us. This is the same dump which is used by the install script. The link to the latest + dump can be found in our repository.

+

Install Script

+

Currently the install process targets *nix systems. We've provisioned and tested on OS X Mavericks & Ubuntu 14.04. Please let us know if you have issues on other operating systems.

+

Postcodes.io is packaged with a script to provision postgres and download a dump (pg_dump) of the ONS Postcode Directory (hosted by us). The script will + do the following,

+
    +
  • Prompt you for your postgres superuser credentials (most of the setup steps require this level of privilege)
  • +
  • Create a new user "postcodesio"
  • +
  • Create a new database "postcodesiodb" owned by "postcodesio"
  • +
  • Create the necessary extensions on "postcodesiodb"
  • +
  • Download, unzip and stream the Postcode directory into Postgres
  • +
+

To run,

$ npm run setup

+

Manual Installation from our pg_dump

+

To setup the database from our pg_dump simply download, gunzip and use either psql or pg_restore to import the data into Postgresql.

+

For example,

$ cat postcodeiodb.sql | psql postcodesiodb

+

Manual Installation

+

The install scripts can be linked to your shell (or can be found in the bin/ directory). To link, navigate to postcodes.io/ and run $ npm link

+

ONS Postcode Directory

+

Download the latest Office for National Statistic's "Postcode Lookup Dataset". Unzip the data locally. Run the import script $ postcodesio:import_onspd passing the path to the CSV data.

$ postcodesio:import_onspd /path/to/data/ONSPD/Data/data.csv

+

OS Open Names

+

Download the latest Ordnance Survey Open Names dataset. Unzip the data locally. Run the import script $ postcodesio:import_oson passing the path that contains the CSV data files.

$ postcodesio:import_oson /path/to/data/opname_csv_gb/Data/

+ + From 3495caffcab60e3a38e55109e701e6b0e4456b94 Mon Sep 17 00:00:00 2001 From: Chris Blanchard Date: Wed, 5 Dec 2018 19:32:35 +0100 Subject: [PATCH 23/36] Update install documentation and put in one place on the about page, Fixes #217 --- README.md | 70 ++-------------- .../contents/documentation_content.ejs | 81 ++++++++++++++----- 2 files changed, 66 insertions(+), 85 deletions(-) diff --git a/README.md b/README.md index 2e90456b..73343aa4 100644 --- a/README.md +++ b/README.md @@ -14,73 +14,13 @@ Please make sure you have the following available: - Postgres (9.5 or greater) - PostGIS extension [(Instructions)](http://postgis.net/install) -We also have an end-to-end guide to install Postcodes.io on a fresh virtual machine [in the wiki](https://github.com/ideal-postcodes/postcodes.io/wiki/Installing-Postcodes.io). +## Self Hosting -## Install & Download Data & Run +Instructions for installing and hosting postcodes.io yourself can be found at [postcodes.io/about](https://postcodes.io/about#Install-notes). Methods include: -**Download and install** - -```bash -$ git clone https://github.com/ideal-postcodes/postcodes.io.git - -$ cd postcodes.io/ && npm install -``` - -**Configure Postgres and Seed Database** - -Postcodes.io is packaged with a script to setup and download the ONS Postcode Directory. To run this, navigate into the repository directory and run: - -``` -$ npm run setup -``` - -This script will prompt you for Postgres superuser credentials. This privilege is required to create a new user, database, extensions and then to load the data. For the security conscious, you can find out how this works by [reading our installation notes](http://postcodes.io/docs#Install-notes) and [the script itself](/bin/setup.sh). Other install methods are available but require a bit of extra work. - -You can change the Postgres username/password yourself but you will need to update 'config/config.js' with the relevant credentials. - -The import process takes around 10 minutes to complete. - -**Run it** - -``` -node server.js // Default environment is development -``` - -## Docker - -This repository builds both the application and database services as docker containers. - -### Application - -The Postcodes.io application is packaged as a Docker container identified on the Docker Hub as `idealpostcodes/postcodes.io`. - -Example usage: - -``` -docker run -d -p 8000 idealpostcodes/postcodes.io -``` - -Configuration available via Environment Variables: - -- `POSTGRES_USER` -- `POSTGRES_PASSWORD` -- `POSTGRES_DATABASE` -- `POSTGRES_HOST` -- `POSTGRES_PORT` -- `PORT` (HTTP Port to run on) -- `MAPBOX_PUBLIC_KEY` - -### Database - -`idealpostcodes/postcodes.io.db` points to a [PostgreSQL & PostGIS](https://hub.docker.com/r/mdillon/postgis/) base image and preloads the latest `pg_dump` in the build process. Upon starting a container for the first time, PostGIS will be enabled and the `pg_dump` will take around a minute to restore. - -Example usage: - -``` -docker run -p 5432 -e POSTGRES_USER=postcodesio -e POSTGRES_DB=postcodesiodb -e POSTGRES_PASSWORD=password idealpostcodes/postcodes.io.db -``` - -As this contains the official postgres base image, the available configuration environment variables are documented here: [https://hub.docker.com/_/postgres/](https://hub.docker.com/_/postgres/) +- [Install as Docker containers](https://postcodes.io/about#docker-install) +- [Install on host with Node.js and Postgresql](https://postcodes.io/about#install-requirements) +- [Download and import the raw dataset to Postgresql](https://postcodes.io/about#import-from-pgdump) ## Testing diff --git a/app/views/includes/contents/documentation_content.ejs b/app/views/includes/contents/documentation_content.ejs index 852aa71e..056d4b47 100644 --- a/app/views/includes/contents/documentation_content.ejs +++ b/app/views/includes/contents/documentation_content.ejs @@ -434,28 +434,66 @@

limit= (not required) Limits number of places matches to return. Defaults to 10. Needs to be less than 100.

Random Place

Returns a random place and all associated data

GET
https://api.postcodes.io/random/places

+

Install Notes

+

There may be occasions where you want to host postcodes.io yourself. We've laid out instructions below on how to do this.

-

Installing Postcodes.io is matter of cloning the source. Simply run

$ git clone https://github.com/ideal-postcodes/postcodes.io.git

-

We currently provide 3 methods to import the necessary datasets

+

We currently provide 4 methods to import the necessary datasets and run the postcodes.io API application:

    -
  1. The easiest is to use our install script. This will require postgres superuser privileges to create and provision your database. It will then download the database and import the data from a pg_dump (hosted and created by us).
  2. -
  3. You can download the pg_dump and attempt to import the database yourself with psql or pg_restore.
  4. +
  5. Use our docker application and database images
  6. +
  7. Use our install script. This will require postgres superuser privileges to create and provision your database. It will then download the database and import the data from a pg_dump (hosted and created by us).
  8. +
  9. You can download the pg_dump and attempt to import the database yourself with psql or pg_restore.
  10. Lastly, you can retrieve the raw CSV data from the ONS and import it with our import script.
+ +

1. Docker Installation

+ +

Postcodes.io can be installed using two docker containers. The postcodes.io application container and the postcodes.io database container (a Postgresql image loaded with the dataset at runtime)

+ +

Requirements

+ + +

Postcodes.io Docker Application Container

+ +

The Postcodes.io application is packaged as a Docker container identified on the Docker Hub as idealpostcodes/postcodes.io.

+ +

Example usage:

+
docker run -d -p 8000 idealpostcodes/postcodes.io
+ +

Configuration available via Environment Variables:

+
    +
  • POSTGRES_USER
  • +
  • POSTGRES_PASSWORD
  • +
  • POSTGRES_DATABASE
  • +
  • POSTGRES_HOST
  • +
  • POSTGRES_PORT
  • +
  • PORT (HTTP Port to run on)
  • +
  • MAPBOX_PUBLIC_KEY
  • +
+ +

Postcodes.io Database Application

+

idealpostcodes/postcodes.io.db points to a PostgreSQL & PostGIS base image and preloads the latest pg_dump in the initialisation process. Upon starting a container for the first time, PostGIS will be enabled and the pg_dump will take around a minute to restore.

+ +

Example usage:

+ +
docker run -p 5432 -e POSTGRES_USER=postcodesio -e POSTGRES_DB=postcodesiodb -e POSTGRES_PASSWORD=password idealpostcodes/postcodes.io.db
+ +

As this contains the official postgres base image, the available configuration environment variables are documented here: hub.docker.com/_/postgres/

+ +
+

Classic Installation Methods (without Docker)

+

Requirements

+ +

For non-docker installations, running Postcodes.io begins with cloning the repository:

$ git clone https://github.com/ideal-postcodes/postcodes.io.git
+ -

Data Download

-

Data is sourced and can be downloaded from the Office for National Statistics Data Portal.The dataset is often not easy to find. The simplest way is to search - for "Postcode Directory".

-

Alternatively you can download a pg_dumpof the postcode database which is hosted by us. This is the same dump which is used by the install script. The link to the latest - dump can be found in our repository.

-

Install Script

+

2. Install Script

Currently the install process targets *nix systems. We've provisioned and tested on OS X Mavericks & Ubuntu 14.04. Please let us know if you have issues on other operating systems.

-

Postcodes.io is packaged with a script to provision postgres and download a dump (pg_dump) of the ONS Postcode Directory (hosted by us). The script will +

Postcodes.io is packaged with a script to provision postgres and download a dump of the ONS Postcode Directory (hosted by us). The script will do the following,

  • Prompt you for your postgres superuser credentials (most of the setup steps require this level of privilege)
  • @@ -464,16 +502,19 @@
  • Create the necessary extensions on "postcodesiodb"
  • Download, unzip and stream the Postcode directory into Postgres
-

To run,

$ npm run setup

-

Manual Installation from our pg_dump

-

To setup the database from our pg_dump simply download, gunzip and use either psql or pg_restore to import the data into Postgresql.

-

For example,

$ cat postcodeiodb.sql | psql postcodesiodb

-

Manual Installation

+

To run,

+
$ npm run setup
+

3. Manual Installation from our pg_dump

+

You can download a pg_dump of the postcode database which is hosted by us. This is the same dump which is used by the install script. The link to the latest dump can be found in our repository.

+

To setup the database from our pg_dump simply download, gunzip and use either psql or pg_restore to import the data into Postgresql.

+

For example,

$ cat postcodeiodb.sql | psql postcodesiodb
+ +

4. Manual Installation

The install scripts can be linked to your shell (or can be found in the bin/ directory). To link, navigate to postcodes.io/ and run $ npm link

ONS Postcode Directory

-

Download the latest Office for National Statistic's "Postcode Lookup Dataset". Unzip the data locally. Run the import script $ postcodesio:import_onspd passing the path to the CSV data.

$ postcodesio:import_onspd /path/to/data/ONSPD/Data/data.csv

+

Download the latest Office for National Statistic's "Postcode Lookup Dataset". Unzip the data locally. Run the import script $ postcodesio:import_onspd passing the path to the CSV data.

$ postcodesio:import_onspd /path/to/data/ONSPD/Data/data.csv

OS Open Names

-

Download the latest Ordnance Survey Open Names dataset. Unzip the data locally. Run the import script $ postcodesio:import_oson passing the path that contains the CSV data files.

$ postcodesio:import_oson /path/to/data/opname_csv_gb/Data/

+

Download the latest Ordnance Survey Open Names dataset. Unzip the data locally. Run the import script $ postcodesio:import_oson passing the path that contains the CSV data files.

$ postcodesio:import_oson /path/to/data/opname_csv_gb/Data/
From 36245329cf41138b4137bbf9d6294556d1fbdf19 Mon Sep 17 00:00:00 2001 From: Chris Blanchard Date: Thu, 6 Dec 2018 15:36:48 +0100 Subject: [PATCH 24/36] Increase minimum required version of Node.js and PostgreSQL to 8 and 10 respectively --- .circleci/config.yml | 6 +++--- CHANGELOG.md | 4 +++- README.md | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index b195df83..d50e364f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -26,7 +26,7 @@ jobs: - PGHOST=localhost - PGUSER=postgres image: "circleci/node:10" - - image: "circleci/postgres:9.6-alpine-postgis" + - image: "circleci/postgres:10-alpine-postgis" node11: <<: *default_steps docker: @@ -34,7 +34,7 @@ jobs: - PGHOST=localhost - PGUSER=postgres image: "circleci/node:11" - - image: "circleci/postgres:9.6-alpine-postgis" + - image: "circleci/postgres:10-alpine-postgis" node8: <<: *default_steps docker: @@ -42,7 +42,7 @@ jobs: - PGHOST=localhost - PGUSER=postgres image: "circleci/node:8" - - image: "circleci/postgres:9.6-alpine-postgis" + - image: "circleci/postgres:10-alpine-postgis" version: 2 workflows: supported_nodes: diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fa123a6..65f474ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,9 @@ Any changes, including backwards incompatible changes will be listed here ## 10.0.0 (29/11/2018) -- *Breaking Changes* Only node.js 8 LTS and above supported +- *Breaking Change* Install dependencies upgraded. Going forward, only the following minimum versions will be tested: + - Node.js 8 + - PostgreSQL 10 - Postcode responses now include County Electoral Districts (ceds) - Updated documentation (outcodes, installation) - Updated dependencies diff --git a/README.md b/README.md index 73343aa4..15cb0193 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Documentation can be found at [postcodes.io](http://postcodes.io) Please make sure you have the following available: - Node.js v8.9 [(Instructions)](http://nodejs.org/) -- Postgres (9.5 or greater) +- PostgreSQL (10 or greater) - PostGIS extension [(Instructions)](http://postgis.net/install) ## Self Hosting From f91871651d4fff3176a8776be1ed3f2e3268f377 Mon Sep 17 00:00:00 2001 From: Chris Blanchard Date: Thu, 6 Dec 2018 15:38:54 +0100 Subject: [PATCH 25/36] Application docker file improvements - Bump node to v10 LTS - Using alpine version - Add build tools (bunyan currently requires make to install) --- Dockerfile | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 515effcf..b9531530 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:9 +FROM node:10-alpine WORKDIR /usr/src/app @@ -10,8 +10,10 @@ HEALTHCHECK --interval=5s CMD node healthcheck.js COPY package.json . -RUN npm install --only=production --no-package-lock && \ - npm cache clean --force +RUN apk --no-cache add --virtual build-dependencies build-base gcc python && \ + npm install --only=production --no-package-lock && \ + npm cache clean --force && \ + apk del build-dependencies COPY . . From f70048ada728a6b1b742c6ba0e73d654536482f6 Mon Sep 17 00:00:00 2001 From: Chris Blanchard Date: Thu, 6 Dec 2018 15:52:06 +0100 Subject: [PATCH 26/36] Improvements to PostgreSQL docker image - Bump PostgreSQL to v10 - Use alpine image - Switch to curl --- Dockerfile.pg | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Dockerfile.pg b/Dockerfile.pg index 8a1190a1..8ce8f50c 100644 --- a/Dockerfile.pg +++ b/Dockerfile.pg @@ -1,10 +1,8 @@ -FROM mdillon/postgis:9.6 +FROM mdillon/postgis:10-alpine -# Copy the pointer to the latest pg_dump COPY latest /tmp/latest -# Download and decompress latest pg_dump into image to be loaded on container startup -RUN apt-get update && \ - apt-get install wget -y && \ - wget -nv -O - $(cat /tmp/latest) > /docker-entrypoint-initdb.d/_postcodesiodata.sql.gz +RUN mkdir -p /docker-entrypoint-initdb.d/ && \ + apk add --no-cache curl && \ + curl -o /docker-entrypoint-initdb.d/_postcodesiodata.sql.gz $(cat /tmp/latest) From 8b66ec01ca6f337550d78543e808efb9c568a249 Mon Sep 17 00:00:00 2001 From: Chris Blanchard Date: Thu, 6 Dec 2018 15:56:35 +0100 Subject: [PATCH 27/36] Keep record of external libraries in one place --- README.md | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/README.md b/README.md index 15cb0193..e6ac3158 100644 --- a/README.md +++ b/README.md @@ -32,29 +32,7 @@ $ npm test ## External Libraries -1. [Java Library](https://github.com/spdeepak/postcodes-io-java) by [Deepak Sunanda Prabhakar](https://github.com/spdeepak) - -2. PHP Libraries - * [postcodes-io-bundle](https://github.com/boxuk/postcodes-io-bundle) by the people at [Box UK](https://www.boxuk.com/). Read the [Blog post](https://www.boxuk.com/insight/tech-posts/geocoding-postcodes-symfony2)
- * [postcodes-io laravel](https://github.com/adityamenon/postcodes-io-laravel) package by [Aditya Menon](http://adityamenon.co)
- * [Postcodes Laravel 5+](https://github.com/codescheme/postcodes) package by [Codescheme](https://github.com/codescheme)
- * [PHP Class for Postcodes.io](https://github.com/hart1994/Postcodes-IO-PHP) by [Ryan](https://github.com/hart1994/)
- -3. [Ruby Library](https://github.com/jamesruston/postcodes_io) by [James Ruston](https://github.com/jamesruston) - -4. [Node.JS Library](https://github.com/cuvva/postcodesio-client-node) by [billinghamj](https://github.com/billinghamj) - -5. [Python Library](https://github.com/previousdeveloper/PythonPostcodesWrapper) by [Gokhan Karadas](https://github.com/previousdeveloper) - -6. [C# Library](https://github.com/markembling/MarkEmbling.PostcodesIO) by [Mark Embling](https://github.com/markembling) - -7. [R Library](https://github.com/erzk/PostcodesioR) by [Eryk Walczak](http://walczak.org). Read the [blog post](http://walczak.org/2016/07/postcode-and-geolocation-api-for-the-uk/). - -8. [Hack Library](https://github.com/Matt-Barber/HackPostcodes) HackPostcodes by [Matt Barber](https://recursiveiterator.wordpress.com/) - -9. [Wolfram Language Library](https://github.com/arnoudbuzing/postcode) Postcode by [Arnoud Buzing](https://github.com/arnoudbuzing) - -10. [Google Sheets Addon](https://chrome.google.com/webstore/detail/uk-postcode-geocoder/bjkecdilmiedfkihpgfhfikchkghliia?utm_source=permalink) by [Ed Patrick](http://edwebdeveloper.com/) +A list of external libraries can be found on the [about page](https://postcodes.io/about) ## License From 07cb1a743854570f761fe1e4cdefe575234b3074 Mon Sep 17 00:00:00 2001 From: Chris Blanchard Date: Thu, 6 Dec 2018 15:57:32 +0100 Subject: [PATCH 28/36] Add docker-compose file --- .env | 4 ++++ README.md | 6 ++++++ docker-compose.yml | 22 ++++++++++++++++++++++ 3 files changed, 32 insertions(+) create mode 100644 .env create mode 100644 docker-compose.yml diff --git a/.env b/.env new file mode 100644 index 00000000..e7dc5358 --- /dev/null +++ b/.env @@ -0,0 +1,4 @@ +POSTGRES_DB=postcodesiodb +POSTGRES_PASSWORD=secret +POSTGRES_USER=postcodesio +POSTGRES_HOST=db diff --git a/README.md b/README.md index e6ac3158..b8604b55 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,12 @@ Please make sure you have the following available: - PostgreSQL (10 or greater) - PostGIS extension [(Instructions)](http://postgis.net/install) +## Run with Docker + +``` +docker-compose up +``` + ## Self Hosting Instructions for installing and hosting postcodes.io yourself can be found at [postcodes.io/about](https://postcodes.io/about#Install-notes). Methods include: diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..1cc26982 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,22 @@ +--- +version: '3' +services: + api: + image: idealpostcodes/postcodes.io:latest + depends_on: + - db + environment: + - POSTGRES_DB + - POSTGRES_PASSWORD + - POSTGRES_USER + - POSTGRES_HOST + ports: + - 8000:8000 + db: + image: idealpostcodes/postcodes.io.db:latest + environment: + - POSTGRES_DB + - POSTGRES_PASSWORD + - POSTGRES_HOST + - POSTGRES_USER + From da4f6d1615cb8aca047ddac6cced3c79f3569031 Mon Sep 17 00:00:00 2001 From: Chris Blanchard Date: Thu, 6 Dec 2018 16:13:46 +0100 Subject: [PATCH 29/36] Add county electoral districts --- bin/find_missing.js | 1 + 1 file changed, 1 insertion(+) diff --git a/bin/find_missing.js b/bin/find_missing.js index b224f2bc..18ceaa5c 100755 --- a/bin/find_missing.js +++ b/bin/find_missing.js @@ -32,6 +32,7 @@ const codeTypeToOnspd = Object.freeze({ "msoa": "msoa11", "nuts": "nuts", "ccgs": "ccg", + "ceds": "ced", }); // List of types to be searched From d869265adbd1d9d7e00ea98ccac535eb673a0b4c Mon Sep 17 00:00:00 2001 From: Chris Blanchard Date: Thu, 6 Dec 2018 16:13:56 +0100 Subject: [PATCH 30/36] Add missing gss codes for wards --- data/scripts/wards.js | 2 +- data/wards.json | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/data/scripts/wards.js b/data/scripts/wards.js index ffc647d4..7e38a328 100644 --- a/data/scripts/wards.js +++ b/data/scripts/wards.js @@ -20,7 +20,7 @@ const transform = row => { const configs = [ { - file: "Ward names and codes UK as at 05_18.csv", + file: "Ward names and code UK as at 12_18.csv", transform, parseOptions: { delimiter: ",", diff --git a/data/wards.json b/data/wards.json index 47617a5d..b6e293bd 100644 --- a/data/wards.json +++ b/data/wards.json @@ -7297,7 +7297,6 @@ "E05011395": "Guiseley & Rawdon", "E05011396": "Harewood", "E05011397": "Headingley & Hyde Park", - "E05011398": "Horsforth", "E05011399": "Hunslet & Riverside", "E05011400": "Killingbeck & Seacroft", "E05011401": "Kippax & Methley", @@ -7307,7 +7306,6 @@ "E05011405": "Moortown", "E05011406": "Morley North", "E05011407": "Morley South", - "E05011408": "Otley & Yeadon", "E05011409": "Pudsey", "E05011410": "Rothwell", "E05011411": "Roundhay", @@ -7445,6 +7443,8 @@ "E05011543": "Sutton", "E05011544": "University", "E05011545": "West Carr", + "E05011547": "Horsforth", + "E05011549": "Otley & Yeadon", "N08000101": "Abbey", "N08000102": "Aldergrove", "N08000103": "Antrim Centre", @@ -8078,13 +8078,11 @@ "S13002942": "Falkirk South", "S13002943": "Lower Braes", "S13002944": "Upper Braes", - "S13002945": "West Fife and Coastal Villages", "S13002946": "Dunfermline North", "S13002947": "Dunfermline Central", "S13002948": "Dunfermline South", "S13002949": "Rosyth", "S13002950": "Inverkeithing and Dalgety Bay", - "S13002952": "Lochgelly, Cardenden and Benarty", "S13002953": "Burntisland, Kinghorn and Western Kirkcaldy", "S13002954": "Kirkcaldy North", "S13002955": "Kirkcaldy Central", @@ -8201,7 +8199,6 @@ "S13003067": "Strathtay", "S13003068": "Strathearn", "S13003069": "Strathallan", - "S13003070": "Kinross-shire", "S13003071": "Almond and Earn", "S13003072": "Perth City South", "S13003073": "Perth City North", @@ -8261,6 +8258,9 @@ "S13003127": "Clydebank Waterfront", "S13003128": "Cowdenbeath", "S13003129": "Glenrothes West and Kinglassie", + "S13003130": "Lochgelly, Cardenden and Benarty", + "S13003131": "West Fife and Coastal Villages", + "S13003132": "Kinross-shire", "W05000041": "Aberdaron", "W05000042": "Aberdovey", "W05000043": "Abererch", From 482d97c8500af17830315ba64e47bd44452a664f Mon Sep 17 00:00:00 2001 From: Chris Blanchard Date: Thu, 6 Dec 2018 16:39:12 +0100 Subject: [PATCH 31/36] Fix broken import: outcode table generation fails first time due to unavailable support table --- bin/import.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/import.js b/bin/import.js index 537cfb3d..08fae1c6 100755 --- a/bin/import.js +++ b/bin/import.js @@ -94,8 +94,8 @@ prompt.get([{ dropTempTables, setupPostcodesTable, setupTerminatedPostcodesTable, - setupOutcodeTable, setupDataTables, + setupOutcodeTable, ], error => { if (error) { console.log("Unable to complete import process due to error:", JSON.stringify(error, 2, 2)); From 29daeda9b6ea125df83bb273c5ba30a4642addcc Mon Sep 17 00:00:00 2001 From: Chris Blanchard Date: Fri, 7 Dec 2018 11:26:07 +0100 Subject: [PATCH 32/36] Switch to promises. async/await doesn't play well with async.series --- bin/import.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/bin/import.js b/bin/import.js index 08fae1c6..9071231b 100755 --- a/bin/import.js +++ b/bin/import.js @@ -62,14 +62,11 @@ const setupPostcodesTable = callback => { setupWithTableSwap(Postcode, sourceFile)(callback); }; -const setupDataTables = callback => { +const setupDataTables = (callback) => { console.log("Setting up support tables..."); - try { - const result = setupSupportTables(); - callback(null, result); - } catch (e) { - callback(e); - } + setupSupportTables() + .then(result => callback(null, result)) + .catch(error => callback(error)); }; prompt.start(); From 79c9855d1278a11a03780f7480bd748bf16a7a8f Mon Sep 17 00:00:00 2001 From: Chris Blanchard Date: Fri, 7 Dec 2018 11:45:14 +0100 Subject: [PATCH 33/36] Update stats for ONSPD Dec 2018 --- app/views/includes/contents/about_content.ejs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/includes/contents/about_content.ejs b/app/views/includes/contents/about_content.ejs index fac3b49b..858deefb 100644 --- a/app/views/includes/contents/about_content.ejs +++ b/app/views/includes/contents/about_content.ejs @@ -83,13 +83,13 @@ Release Date - August 2018 - July 2018 + November 2018 + October 2018 Data Points - 1,765,484 (+0.13%) - 42,204 (-2) + 1,766,803 (+0.07%) + 42,204 (+0) From 85c9cff7d054b5c33d10ac4144537349662e0423 Mon Sep 17 00:00:00 2001 From: Chris Blanchard Date: Fri, 7 Dec 2018 11:59:18 +0100 Subject: [PATCH 34/36] Lint --- .circleci/config.yml | 40 ++++++++++++++-------------- bin/import.js | 1 - docker-compose.yml | 4 +-- tests/base.unit.js | 4 --- tests/helper/attribute_base.suite.js | 2 +- tests/helper/type_checking.js | 1 - 6 files changed, 23 insertions(+), 29 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index d50e364f..3906dcb2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,52 +1,52 @@ ---- +--- default_steps: &default_steps - steps: - - run: + steps: + - run: command: "dockerize -wait tcp://localhost:5432 -timeout 2m" name: "Wait for db" - checkout - run: "sudo apt-get update" - run: "sudo apt install postgresql-client" - run: "POSTGRES_USER=postgres npm run setup_test_db" - - restore_cache: - keys: + - restore_cache: + keys: - "nodemodules-deps-{{ checksum \"package-lock.json\" }}" - run: "npm install" - - save_cache: + - save_cache: key: "nodemodules-deps-{{ checksum \"package-lock.json\" }}" - paths: + paths: - node_modules/ - run: "npm run test" - run: "npm run coverage" -jobs: - node10: +jobs: + node10: <<: *default_steps - docker: - - environment: + docker: + - environment: - PGHOST=localhost - PGUSER=postgres image: "circleci/node:10" - image: "circleci/postgres:10-alpine-postgis" - node11: + node11: <<: *default_steps - docker: - - environment: + docker: + - environment: - PGHOST=localhost - PGUSER=postgres image: "circleci/node:11" - image: "circleci/postgres:10-alpine-postgis" - node8: + node8: <<: *default_steps - docker: - - environment: + docker: + - environment: - PGHOST=localhost - PGUSER=postgres image: "circleci/node:8" - image: "circleci/postgres:10-alpine-postgis" version: 2 -workflows: - supported_nodes: - jobs: +workflows: + supported_nodes: + jobs: - node8 - node10 - node11 diff --git a/bin/import.js b/bin/import.js index 9071231b..08b5792a 100755 --- a/bin/import.js +++ b/bin/import.js @@ -24,7 +24,6 @@ const { Postcode, TerminatedPostcode, Outcode, - Base, } = require("../app/models/index.js"); const { toTempName, setupWithTableSwap } = require("../app/models/base.js"); const { SUPPORT_TABLES, setupSupportTables } = require("../app/lib/setup.js"); diff --git a/docker-compose.yml b/docker-compose.yml index 1cc26982..1d95b78b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,9 +1,9 @@ --- version: '3' services: - api: + api: image: idealpostcodes/postcodes.io:latest - depends_on: + depends_on: - db environment: - POSTGRES_DB diff --git a/tests/base.unit.js b/tests/base.unit.js index f5d309bf..b9b9303f 100644 --- a/tests/base.unit.js +++ b/tests/base.unit.js @@ -3,10 +3,6 @@ const helper = require("./helper"); const { assert } = require("chai"); const { - toTempName, - toArchiveName, - setupWithTableSwap, - getLocation, Base, extractOnspdVal } = require("../app/models/base.js"); diff --git a/tests/helper/attribute_base.suite.js b/tests/helper/attribute_base.suite.js index 62a3220d..aa3af2dc 100644 --- a/tests/helper/attribute_base.suite.js +++ b/tests/helper/attribute_base.suite.js @@ -38,7 +38,7 @@ const rigCoreSpecs = model => { }); }); - describe("_setupTable", done => { + describe("_setupTable", () => { it ("creates a table, associated indexes and populates with data", function (done) { this.timeout(0); model._setupTable(error => { diff --git a/tests/helper/type_checking.js b/tests/helper/type_checking.js index f8712dda..b4887018 100644 --- a/tests/helper/type_checking.js +++ b/tests/helper/type_checking.js @@ -161,6 +161,5 @@ module.exports = { isRawPlaceObject, isPostcodeWithDistanceObject, isOutcodeObject, - isRawOutcodeObject, }; From 0f70c4fa3f8d43216f3f476f919395fa1876119d Mon Sep 17 00:00:00 2001 From: Chris Blanchard Date: Fri, 7 Dec 2018 12:42:30 +0100 Subject: [PATCH 35/36] Bump pg to 7.7.0 --- package-lock.json | 36 ++++++++++++++++++------------------ package.json | 2 +- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/package-lock.json b/package-lock.json index 81ef38c2..6b73ff63 100644 --- a/package-lock.json +++ b/package-lock.json @@ -364,9 +364,9 @@ "optional": true }, "buffer-writer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-1.0.1.tgz", - "integrity": "sha1-Iqk2kB4wKa/NdUfrRIfOtpejvwg=" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", + "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==" }, "bunyan": { "version": "1.8.12", @@ -3812,14 +3812,14 @@ "dev": true }, "pg": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/pg/-/pg-7.4.3.tgz", - "integrity": "sha1-97b5P1NA7MJZavu5ShPj1rYJg0s=", + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/pg/-/pg-7.7.1.tgz", + "integrity": "sha512-p3I0mXOmUvCoVlCMFW6iYSrnguPol6q8He15NGgSIdM3sPGjFc+8JGCeKclw8ZR4ETd+Jxy2KNiaPUcocHZeMw==", "requires": { - "buffer-writer": "1.0.1", + "buffer-writer": "2.0.0", "packet-reader": "0.3.1", "pg-connection-string": "0.1.3", - "pg-pool": "~2.0.3", + "pg-pool": "^2.0.4", "pg-types": "~1.12.1", "pgpass": "1.x", "semver": "4.3.2" @@ -3841,9 +3841,9 @@ "integrity": "sha1-siDxkIl2t7QNqjc8etpfyoI6sNk=" }, "pg-pool": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-2.0.3.tgz", - "integrity": "sha1-wCIDLIlJ8xKk+R+2QJzgQHa+Mlc=" + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-2.0.4.tgz", + "integrity": "sha512-Mi2AsmlFkVMpI28NreaDkz5DkfxLOG16C/HNwi091LDlOiDiQACtAroLxSd1vIS2imBqxdjjO9cQZg2CwsOPbw==" }, "pg-query-stream": { "version": "1.1.2", @@ -3988,9 +3988,9 @@ "integrity": "sha1-HBiTp7baLqXI8Zv/FYWTrIFQLHg=" }, "postgres-array": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-1.0.2.tgz", - "integrity": "sha1-jgsy6wO/d6XAp4UeBEHBaaJWojg=" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-1.0.3.tgz", + "integrity": "sha512-5wClXrAP0+78mcsNX3/ithQ5exKvCyK5lr5NEEEeGwwM6NJdQgzIJBVxLvRW+huFpX92F2QnZ5CcokH0VhK2qQ==" }, "postgres-bytea": { "version": "1.0.0", @@ -4003,9 +4003,9 @@ "integrity": "sha1-4tiXAu/bJY/52c7g/pG9BpdSV6g=" }, "postgres-interval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.1.1.tgz", - "integrity": "sha512-OkuCi9t/3CZmeQreutGgx/OVNv9MKHGIT5jH8KldQ4NLYXkvmT9nDVxEuCENlNwhlGPE374oA/xMqn05G49pHA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.1.2.tgz", + "integrity": "sha512-fC3xNHeTskCxL1dC8KOtxXt7YeFmlbTYtn7ul8MkVERuTmf7pI4DrkAxcw3kh1fQ9uz4wQmd03a1mRiXUZChfQ==", "requires": { "xtend": "^4.0.0" } @@ -4222,7 +4222,7 @@ }, "semver": { "version": "4.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.2.tgz", + "resolved": "http://registry.npmjs.org/semver/-/semver-4.3.2.tgz", "integrity": "sha1-x6BxWKgL7dBSNVt3DYLWZA+AO+c=" }, "send": { diff --git a/package.json b/package.json index bf77494e..2824f303 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "morgan": "~1.9.1", "node.extend": "~2.0.2", "ospoint": "~0.2.0", - "pg": "~7.4.3", + "pg": "~7.7.0", "pg-copy-streams": "~1.2.0", "pg-query-stream": "~1.1.2", "pmx": "~1.6.7", From 53adc899a62a38684450c0dd764fc0726ca28b4f Mon Sep 17 00:00:00 2001 From: Chris Blanchard Date: Mon, 10 Dec 2018 13:45:26 +0100 Subject: [PATCH 36/36] Update latest pg_dump --- CHANGELOG.md | 3 +++ latest | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 65f474ab..68aafdf0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,9 @@ Any changes, including backwards incompatible changes will be listed here - Postcode responses now include County Electoral Districts (ceds) - Updated documentation (outcodes, installation) - Updated dependencies +- Updated GSS codes for missing wards +- Updated ONSPD dataset to November 2018 +- Updated OS Names dataset to October 2018 ## 9.0.3 (26/10/2018) diff --git a/latest b/latest index daa8590f..89e5ea08 100644 --- a/latest +++ b/latest @@ -1 +1 @@ -https://postcodesio.s3.amazonaws.com/public/postcodesio-2018-08-31-1146.sql.gz +https://postcodesio.s3.amazonaws.com/public/postcodesio-2018-12-10-1053.sql.gz