diff --git a/.travis.yml b/.travis.yml index 8ceb64ee..7449d2ac 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: node_js node_js: - - 8 + - 12 cache: npm before_script: - cd $TRAVIS_BUILD_DIR/app && npm ci diff --git a/app/.babelrc b/app/.babelrc deleted file mode 100644 index 1578f26a..00000000 --- a/app/.babelrc +++ /dev/null @@ -1,13 +0,0 @@ -{ - "plugins": [ - "@babel/plugin-syntax-jsx", - "@babel/plugin-syntax-typescript", - [ - "babel-plugin-typescript-to-proptypes", - { - "implicitChildren": true, - "typeCheck": true - } - ] - ] -} \ No newline at end of file diff --git a/app/map_styles/polygon.xml b/app/map_styles/polygon.xml index a2f88975..0e23cc7c 100644 --- a/app/map_styles/polygon.xml +++ b/app/map_styles/polygon.xml @@ -89,6 +89,40 @@ + + diff --git a/app/package-lock.json b/app/package-lock.json index 8913e572..d886d083 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -399,9 +399,9 @@ } }, "@babel/plugin-syntax-jsx": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.2.0.tgz", - "integrity": "sha512-VyN4QANJkRW6lDBmENzRszvZf3/4AXaj9YR7GwrWeeN9tEBPuXbmDYVU9bYBN0D70zCWVwUy0HWq2553VCb6Hw==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.7.4.tgz", + "integrity": "sha512-wuy6fiMe9y7HeZBWXYCGt2RGxZOj0BImZ9EyXJVnVGBKO/Br592rbR3rtIQn0eQhAk9vqaKP5n8tVqEFBQMfLg==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" @@ -425,15 +425,6 @@ "@babel/helper-plugin-utils": "^7.0.0" } }, - "@babel/plugin-syntax-typescript": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.3.3.tgz", - "integrity": "sha512-dGwbSMA1YhVS8+31CnPR7LB4pcbrzcV99wQzby4uAfrkZPYZlQ7ImwdpzLqi6Z6IL02b8IAL379CaMwo0x5Lag==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0" - } - }, "@babel/plugin-transform-arrow-functions": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.2.0.tgz", @@ -938,35 +929,45 @@ "dev": true }, "@fortawesome/fontawesome-common-types": { - "version": "0.2.21", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.21.tgz", - "integrity": "sha512-iJtcrU2BtF9Wyr0zm3tHEJy3HqA6sADExhCqCv3SKaJJKKp4ORJ40t4nyHvcWXSVFtd7r1gcdqcRsAfoREGTFA==" + "version": "0.2.25", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.25.tgz", + "integrity": "sha512-3RuZPDuuPELd7RXtUqTCfed14fcny9UiPOkdr2i+cYxBoTOfQgxcDoq77fHiiHcgWuo1LoBUpvGxFF1H/y7s3Q==" }, "@fortawesome/fontawesome-svg-core": { - "version": "1.2.21", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.21.tgz", - "integrity": "sha512-EhrgMZLJS0tTYZhUbodurZBqDgAFLDNdxJP/q5unrZJwiFo8Dd7xGvJdhAhY5WcX4khzkPQcbLTCMPHBtutD7Q==", + "version": "1.2.25", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.25.tgz", + "integrity": "sha512-MotKnn53JKqbkLQiwcZSBJVYtTgIKFbh7B8+kd05TSnfKYPFmjKKI59o2fpz5t0Hzl35vVGU6+N4twoOpZUrqA==", "requires": { - "@fortawesome/fontawesome-common-types": "^0.2.21" + "@fortawesome/fontawesome-common-types": "^0.2.25" } }, "@fortawesome/free-solid-svg-icons": { - "version": "5.10.1", - "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.10.1.tgz", - "integrity": "sha512-MKH+SCt0DnVoXdemxf6JEdTRtCPwYLMCWZcwgGccYU/ab6QcDtbAMn6Xm4Zub6YqQCcaiy0hU294YdHOldSBRA==", + "version": "5.11.2", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.11.2.tgz", + "integrity": "sha512-zBue4i0PAZJUXOmLBBvM7L0O7wmsDC8dFv9IhpW5QL4kT9xhhVUsYg/LX1+5KaukWq4/cbDcKT+RT1aRe543sg==", "requires": { - "@fortawesome/fontawesome-common-types": "^0.2.21" + "@fortawesome/fontawesome-common-types": "^0.2.25" } }, "@fortawesome/react-fontawesome": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.4.tgz", - "integrity": "sha512-GwmxQ+TK7PEdfSwvxtGnMCqrfEm0/HbRHArbUudsYiy9KzVCwndxa2KMcfyTQ8El0vROrq8gOOff09RF1oQe8g==", + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.8.tgz", + "integrity": "sha512-I5h9YQg/ePA3Br9ISS18fcwOYmzQYDSM1ftH03/8nHkiqIVHtUyQBw482+60dnzvlr82gHt3mGm+nDUp159FCw==", "requires": { - "humps": "^2.0.1", "prop-types": "^15.5.10" } }, + "@jest/types": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", + "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^13.0.0" + } + }, "@mapbox/sphericalmercator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@mapbox/sphericalmercator/-/sphericalmercator-1.1.0.tgz", @@ -989,9 +990,9 @@ "dev": true }, "@types/body-parser": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.17.0.tgz", - "integrity": "sha512-a2+YeUjPkztKJu5aIF2yArYFQQp8d51wZ7DavSHjFuY1mqVgidGyzEQ41JIVNy82fXj8yPgy2vJmfIywgESW6w==", + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.17.1.tgz", + "integrity": "sha512-RoX2EZjMiFMjZh9lmYrwgoP9RTpAjSHiJxdp4oidAQVO02T7HER3xj9UKue5534ULWeqVEkujhWcyvUce+d68w==", "dev": true, "requires": { "@types/connect": "*", @@ -1014,9 +1015,9 @@ "dev": true }, "@types/express": { - "version": "4.17.0", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.0.tgz", - "integrity": "sha512-CjaMu57cjgjuZbh9DpkloeGxV45CnMGlVd+XpG7Gm9QgVrd7KFq+X4HY0vM+2v0bczS48Wg7bvnMY5TN+Xmcfw==", + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.2.tgz", + "integrity": "sha512-5mHFNyavtLoJmnusB8OKJ5bshSzw+qkMIBAobLrIM48HJvunFva9mOa6aBwh64lBFyNwBbs0xiEFuj4eU/NjCA==", "dev": true, "requires": { "@types/body-parser": "*", @@ -1025,9 +1026,9 @@ } }, "@types/express-serve-static-core": { - "version": "4.16.7", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.16.7.tgz", - "integrity": "sha512-847KvL8Q1y3TtFLRTXcVakErLJQgdpFSaq+k043xefz9raEf0C7HalpSY7OW5PyjCnY8P7bPW5t/Co9qqp+USg==", + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.0.tgz", + "integrity": "sha512-Xnub7w57uvcBqFdIGoRg1KhNOeEj0vB6ykUM7uFWyxvbdE89GFyqgmUcanAriMr4YOxNFZBAWkfcWIb4WBPt3g==", "dev": true, "requires": { "@types/node": "*", @@ -1035,9 +1036,9 @@ } }, "@types/express-session": { - "version": "1.15.13", - "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.15.13.tgz", - "integrity": "sha512-BLRzO/ZfjTTLSRakUJxB0p5I5NmBHuyHkXDyh8sezdCMYxpqXrvMljKwle81I9AeCAzdq6nfz6qafmYLQ/rU9A==", + "version": "1.15.16", + "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.15.16.tgz", + "integrity": "sha512-vWQpNt9t/zc4bTX+Ow5powZb9n3NwOM0SYsAJ7PYj5vliB6FA40ye5sW5fZTw8+ekbzJf/sgvtQocf7IryJBJw==", "dev": true, "requires": { "@types/express": "*", @@ -1062,25 +1063,82 @@ } }, "@types/history": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.2.tgz", - "integrity": "sha512-ui3WwXmjTaY73fOQ3/m3nnajU/Orhi6cEu5rzX+BrAAJxa3eITXZ5ch9suPqtM03OWhAHhPSyBGCN4UKoxO20Q==", + "version": "4.7.3", + "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.3.tgz", + "integrity": "sha512-cS5owqtwzLN5kY+l+KgKdRJ/Cee8tlmQoGQuIE9tWnSmS3JMKzmxo2HIAk2wODMifGwO20d62xZQLYz+RLfXmw==", "dev": true }, - "@types/jest": { - "version": "24.0.17", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-24.0.17.tgz", - "integrity": "sha512-1cy3xkOAfSYn78dsBWy4M3h/QF/HeWPchNFDjysVtp3GHeTdSmtluNnELfCmfNRRHo0OWEcpf+NsEJQvwQfdqQ==", + "@types/istanbul-lib-coverage": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz", + "integrity": "sha512-hRJD2ahnnpLgsj6KWMYSrmXkM3rm2Dl1qkx6IOFD5FnuNPXJIG5L0dhgKXCYTRMGzU4n0wImQ/xfmRc4POUFlg==", + "dev": true + }, + "@types/istanbul-lib-report": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-1.1.1.tgz", + "integrity": "sha512-3BUTyMzbZa2DtDI2BkERNC6jJw2Mr2Y0oGI7mRxYNBPxppbtEK1F66u3bKwU2g+wxwWI7PAoRpJnOY1grJqzHg==", "dev": true, "requires": { - "@types/jest-diff": "*" + "@types/istanbul-lib-coverage": "*" } }, - "@types/jest-diff": { - "version": "20.0.1", - "resolved": "https://registry.npmjs.org/@types/jest-diff/-/jest-diff-20.0.1.tgz", - "integrity": "sha512-yALhelO3i0hqZwhjtcr6dYyaLoCHbAMshwtj6cGxTvHZAKXHsYGdff6E8EPw3xLKY0ELUTQ69Q1rQiJENnccMA==", - "dev": true + "@types/istanbul-reports": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-1.1.1.tgz", + "integrity": "sha512-UpYjBi8xefVChsCoBpKShdxTllC9pwISirfoZsUa2AAdQg/Jd2KQGtSbw+ya7GPo7x/wAPlH6JBhKhAsXUEZNA==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "*", + "@types/istanbul-lib-report": "*" + } + }, + "@types/jest": { + "version": "24.0.23", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-24.0.23.tgz", + "integrity": "sha512-L7MBvwfNpe7yVPTXLn32df/EK+AMBFAFvZrRuArGs7npEWnlziUXK+5GMIUTI4NIuwok3XibsjXCs5HxviYXjg==", + "dev": true, + "requires": { + "jest-diff": "^24.3.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "jest-diff": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-24.9.0.tgz", + "integrity": "sha512-qMfrTs8AdJE2iqrTp0hzh7kTd2PQWrsFyj9tORoKmu32xjPjeE4NyjVRDz8ybYwqS2ik8N4hsIpiVTyFeo2lBQ==", + "dev": true, + "requires": { + "chalk": "^2.0.1", + "diff-sequences": "^24.9.0", + "jest-get-type": "^24.9.0", + "pretty-format": "^24.9.0" + } + }, + "jest-get-type": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.9.0.tgz", + "integrity": "sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q==", + "dev": true + }, + "pretty-format": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz", + "integrity": "sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==", + "dev": true, + "requires": { + "@jest/types": "^24.9.0", + "ansi-regex": "^4.0.0", + "ansi-styles": "^3.2.0", + "react-is": "^16.8.4" + } + } + } }, "@types/json-schema": { "version": "7.0.3", @@ -1089,14 +1147,20 @@ "dev": true }, "@types/leaflet": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.5.1.tgz", - "integrity": "sha512-E5k+vyE2Tv9wQsO6ZsEy08Pjd8RjHPkCzz3Ubt7feMc+5+VkbXtcZMcciczRWuMN5rFIsVywLxRhvTp7fAbbzg==", + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.5.6.tgz", + "integrity": "sha512-a9gVDwmNNalKrsU124kS7Lv9eo0z95CCMJu1Fp7l+A+EQ7Vv0UJ7LFkjaxu176ebUOBDEqvjn7A2vrlq5kLtkw==", "dev": true, "requires": { "@types/geojson": "*" } }, + "@types/lodash": { + "version": "4.14.149", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.149.tgz", + "integrity": "sha512-ijGqzZt/b7BfzcK9vTrS6MFljQRPn5BFWOx8oE0GYxribu6uV+aA9zZuXI1zc/etK9E8nrgdoF2+LgUw7+9tJQ==", + "dev": true + }, "@types/mapbox__sphericalmercator": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@types/mapbox__sphericalmercator/-/mapbox__sphericalmercator-1.1.3.tgz", @@ -1116,24 +1180,24 @@ "dev": true }, "@types/node": { - "version": "8.10.52", - "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.52.tgz", - "integrity": "sha512-2RbW7WXeLex6RI+kQSxq6Ym0GiVcODeQ4Km7MnnTX5BHdOGQnqVa+s6AUmAW+OFYAJ8wv9QxvNZXm7/kBdGTVw==", + "version": "12.12.25", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.25.tgz", + "integrity": "sha512-nf1LMGZvgFX186geVZR1xMZKKblJiRfiASTHw85zED2kI1yDKHDwTKMdkaCbTlXoRKlGKaDfYywt+V0As30q3w==", "dev": true }, "@types/nodemailer": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.2.1.tgz", - "integrity": "sha512-6f46rxxaFwyOW39psPoQiM7jHjL7apDRNT5WPHIuv+TZFv+7sBGSI9J7blIC3/NWff4O9/VSzgoQtO6aPLUdvQ==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.2.2.tgz", + "integrity": "sha512-vDbSSe3+bBXYgibKs8duOrH7bhAv1hMHl3Vtdr3KmXZWLfi5WtIg3X6D/+K4SMK1RbNlm5QZBQCOldST/sVjjg==", "dev": true, "requires": { "@types/node": "*" } }, "@types/prop-types": { - "version": "15.7.1", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.1.tgz", - "integrity": "sha512-CFzn9idOEpHrgdw8JsoTkaDDyRWk1jrzIV8djzcgpq0y9tG4B4lFT+Nxh52DVpDXV+n4+NPNv7M1Dj5uMp6XFg==", + "version": "15.7.3", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", + "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==", "dev": true }, "@types/q": { @@ -1149,9 +1213,9 @@ "dev": true }, "@types/react": { - "version": "16.9.1", - "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.1.tgz", - "integrity": "sha512-jGM2x8F7m7/r+81N/BOaUKVwbC5Cdw6ExlWEUpr77XPwVeNvAppnPEnMMLMfxRDYL8FPEX8MHjwtD2NQMJ0yyQ==", + "version": "16.9.16", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.16.tgz", + "integrity": "sha512-dQ3wlehuBbYlfvRXfF5G+5TbZF3xqgkikK7DWAsQXe2KnzV+kjD4W2ea+ThCrKASZn9h98bjjPzoTYzfRqyBkw==", "dev": true, "requires": { "@types/prop-types": "*", @@ -1159,18 +1223,18 @@ } }, "@types/react-dom": { - "version": "16.8.5", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.8.5.tgz", - "integrity": "sha512-idCEjROZ2cqh29+trmTmZhsBAUNQuYrF92JHKzZ5+aiFM1mlSk3bb23CK7HhYuOY75Apgap5y2jTyHzaM2AJGA==", + "version": "16.9.4", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.4.tgz", + "integrity": "sha512-fya9xteU/n90tda0s+FtN5Ym4tbgxpq/hb/Af24dvs6uYnYn+fspaxw5USlw0R8apDNwxsqumdRoCoKitckQqw==", "dev": true, "requires": { "@types/react": "*" } }, "@types/react-leaflet": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@types/react-leaflet/-/react-leaflet-2.4.0.tgz", - "integrity": "sha512-kDZ2Ky6FQxXRODBEFlq25Lu80Nc7UsDSHCmHTa22UQn2RIJxe3O443K0vzOrFyzWPpVEOmqBpfDkX9QSTBoFxg==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@types/react-leaflet/-/react-leaflet-2.5.0.tgz", + "integrity": "sha512-oS0rVwRLnTK0WbvLYqQaZoUw7CBHBe3Z4yop4GxbzX3DVYroo9fQQNzowmxASD0TExSXn1DLNGm7EywgC3zkPg==", "dev": true, "requires": { "@types/leaflet": "*", @@ -1178,9 +1242,9 @@ } }, "@types/react-router": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.0.3.tgz", - "integrity": "sha512-j2Gge5cvxca+5lK9wxovmGPgpVJMwjyu5lTA/Cd6fLGoPq7FXcUE1jFkEdxeyqGGz8VfHYSHCn5Lcn24BzaNKA==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.3.tgz", + "integrity": "sha512-0gGhmerBqN8CzlnDmSgGNun3tuZFXerUclWkqEhozdLaJtfcJRUTGkKaEKk+/MpHd1KDS1+o2zb/3PkBUiv2qQ==", "dev": true, "requires": { "@types/history": "*", @@ -1188,9 +1252,9 @@ } }, "@types/react-router-dom": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-4.3.4.tgz", - "integrity": "sha512-xrwaWHpnxKk/TTRe7pmoGy3E4SyF/ojFqNfFJacw7OLdfLXRvGfk4r/XePVaZNVfeJzL8fcnNilPN7xOdJ/vGw==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-4.3.5.tgz", + "integrity": "sha512-eFajSUASYbPHg2BDM1G8Btx+YqGgvROPIg6sBhl3O4kbDdYXdFdfrgQFf/pcBuQVObjfT9AL/dd15jilR5DIEA==", "dev": true, "requires": { "@types/history": "*", @@ -1199,9 +1263,9 @@ } }, "@types/serve-static": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.2.tgz", - "integrity": "sha512-/BZ4QRLpH/bNYgZgwhKEh+5AsboDBcUdlBYgzoLX0fpj3Y2gp6EApyOlM3bK53wQS/OE1SrdSYBAbux2D1528Q==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.3.tgz", + "integrity": "sha512-oprSwp094zOglVrXdlo/4bAHtKTAxX6VT8FOZlBKrmyLbNvE1zxZyJ6yikMVtHIvwP45+ZQGJn+FdXGKTozq0g==", "dev": true, "requires": { "@types/express-serve-static-core": "*", @@ -1209,18 +1273,33 @@ } }, "@types/sharp": { - "version": "0.22.2", - "resolved": "https://registry.npmjs.org/@types/sharp/-/sharp-0.22.2.tgz", - "integrity": "sha512-oH49f42h3nf/qys0weYsaTGiMv67wPB769ynCoPfBAVwjjxFF3QtIPEe3MfhwyNjQAhQhTEfnmMKvVZfcFkhIw==", + "version": "0.22.3", + "resolved": "https://registry.npmjs.org/@types/sharp/-/sharp-0.22.3.tgz", + "integrity": "sha512-4fP7DGvNMlk6qy3rtsC24rLttg7oa0yVugFXPRmD8kGxHLvoqiRTVUbcK3qB0eYK6pO9mBWkzAAL7lV1htig3A==", "dev": true, "requires": { "@types/node": "*" } }, "@types/webpack-env": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@types/webpack-env/-/webpack-env-1.14.0.tgz", - "integrity": "sha512-Fv+0gYJzE/czLoRKq+gnXWr4yBpPM3tO3C8pDLFwqVKlMICQUq5OsxwwFZYDaVr7+L6mgNDp16iOcJHEz3J5RQ==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@types/webpack-env/-/webpack-env-1.14.1.tgz", + "integrity": "sha512-0Ki9jAAhKDSuLDXOIMADg54Hu60SuBTEsWaJGGy5cV+SSUQ63J2a+RrYYGrErzz39fXzTibhKrAQJAb8M7PNcA==", + "dev": true + }, + "@types/yargs": { + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.3.tgz", + "integrity": "sha512-K8/LfZq2duW33XW/tFwEAfnZlqIfVsoyRB3kfXdPXYhl0nfM8mmh7GS0jg7WrX2Dgq/0Ha/pR1PaR+BvmWwjiQ==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "@types/yargs-parser": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-13.1.0.tgz", + "integrity": "sha512-gCubfBUZ6KxzoibJ+SCUc/57Ms1jz5NjHe4+dI2krNmU5zCPAphyLJYyTOg06ueIyfj+SaCUqmzun7ImlxDcKg==", "dev": true }, "@typescript-eslint/experimental-utils": { @@ -1232,18 +1311,6 @@ "@types/json-schema": "^7.0.3", "@typescript-eslint/typescript-estree": "1.13.0", "eslint-scope": "^4.0.0" - }, - "dependencies": { - "eslint-scope": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", - "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", - "dev": true, - "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - } - } } }, "@typescript-eslint/typescript-estree": { @@ -2208,17 +2275,28 @@ "dev": true }, "babel-eslint": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.0.2.tgz", - "integrity": "sha512-UdsurWPtgiPgpJ06ryUnuaSXC2s0WoSZnQmEpbAH65XZSdwowgN5MvyP7e88nW07FYXv72erVtpBkxyDVKhH1Q==", + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.0.3.tgz", + "integrity": "sha512-z3U7eMY6r/3f3/JB9mTsLjyxrv0Yb1zb8PCWCLpguxfCzBIZUwy23R1t/XKewP+8mEN2Ck8Dtr4q20z6ce6SoA==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", "@babel/parser": "^7.0.0", "@babel/traverse": "^7.0.0", "@babel/types": "^7.0.0", - "eslint-scope": "3.7.1", - "eslint-visitor-keys": "^1.0.0" + "eslint-visitor-keys": "^1.0.0", + "resolve": "^1.12.0" + }, + "dependencies": { + "resolve": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.13.1.tgz", + "integrity": "sha512-CxqObCX8K8YtAhOBRg+lrcdn+LK+WYOS8tSjqSFbjtrI5PnS63QPhZl4+yKfrU9tdsbMu9Anr/amegT87M9Z6w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + } } }, "babel-generator": { @@ -2333,17 +2411,6 @@ "integrity": "sha512-f49NsaohQ1ByY20nUrpc30QFdbeT4ntV4PAL2vSZe6uCB5nqAcqXS/qzU+aI6ZfYhWASx5eIsTFvFrs1B2ffGg==", "dev": true }, - "babel-plugin-typescript-to-proptypes": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/babel-plugin-typescript-to-proptypes/-/babel-plugin-typescript-to-proptypes-0.17.1.tgz", - "integrity": "sha512-yREUfvDlmn6QjM0QbywXUkXBQMD/iFfLVTl+jig4X7ZLUg9lq8ZLuex8HIM2SQ4X3vcjGnWPFowodlMcXhwxdQ==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.0.0", - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-typescript": "^7.2.0" - } - }, "babel-preset-jest": { "version": "23.2.0", "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-23.2.0.tgz", @@ -2732,9 +2799,9 @@ "dev": true }, "bootstrap": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.3.1.tgz", - "integrity": "sha512-rXqOmH1VilAt2DyPzluTi2blhk17bO7ef+zLLPlWvG494pDxcM234pJ8wTc/6R40UWizAIIMgxjvxZg5kmsbag==" + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.4.1.tgz", + "integrity": "sha512-tbx5cHubwE6e2ZG7nqM3g/FZ5PQEDMWmMGNrCUBVRPHXTJaH7CBDdsLeu3eCh3B1tzAxTnAbtmrzvWEvT2NNEA==" }, "brace-expansion": { "version": "1.1.11", @@ -3232,9 +3299,9 @@ } }, "chownr": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.2.tgz", - "integrity": "sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A==" + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.3.tgz", + "integrity": "sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==" }, "chrome-trace-event": { "version": "1.0.2", @@ -3669,12 +3736,14 @@ } }, "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, "requires": { - "lru-cache": "^4.0.1", + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", "shebang-command": "^1.2.0", "which": "^1.2.9" } @@ -4175,9 +4244,9 @@ } }, "csstype": { - "version": "2.6.6", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.6.tgz", - "integrity": "sha512-RpFbQGUE74iyPgvr46U9t1xoQBM8T4BL8SxrN66Le2xYAPSaDJJKeztV3awugusb3g3G9iL8StmkBBXhcbbXhg==", + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.7.tgz", + "integrity": "sha512-9Mcn9sFbGBAdmimWb2gLVDtFJzeKtDGIr76TUqmjZrw9LFXBMSU70lcs+C0/7fyCd6iBDqmksUcCOUIkisPHsQ==", "dev": true }, "cyclist": { @@ -4245,11 +4314,11 @@ "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" }, "decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", "requires": { - "mimic-response": "^1.0.0" + "mimic-response": "^2.0.0" } }, "deep-equal": { @@ -4525,6 +4594,12 @@ "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", "dev": true }, + "diff-sequences": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-24.9.0.tgz", + "integrity": "sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew==", + "dev": true + }, "diffie-hellman": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", @@ -5000,36 +5075,54 @@ } } }, + "eslint-plugin-eslint-plugin": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-eslint-plugin/-/eslint-plugin-eslint-plugin-2.1.0.tgz", + "integrity": "sha512-kT3A/ZJftt28gbl/Cv04qezb/NQ1dwYIbi8lyf806XMxkus7DvOVCLIfTXMrorp322Pnoez7+zabXH29tADIDg==", + "dev": true + }, "eslint-plugin-jest": { - "version": "22.15.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-22.15.0.tgz", - "integrity": "sha512-hgnPbSqAIcLLS9ePb12hNHTRkXnkVaCfOwCt2pzQ8KpOKPWGA4HhLMaFN38NBa/0uvLfrZpcIRjT+6tMAfr58Q==", + "version": "22.21.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-22.21.0.tgz", + "integrity": "sha512-OaqnSS7uBgcGiqXUiEnjoqxPNKvR4JWG5mSRkzVoR6+vDwlqqp11beeql1hYs0HTbdhiwrxWLxbX0Vx7roG3Ew==", "dev": true, "requires": { "@typescript-eslint/experimental-utils": "^1.13.0" } }, "eslint-plugin-react": { - "version": "7.14.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.14.3.tgz", - "integrity": "sha512-EzdyyBWC4Uz2hPYBiEJrKCUi2Fn+BJ9B/pJQcjw5X+x/H2Nm59S4MJIvL4O5NEE0+WbnQwEBxWY03oUk+Bc3FA==", + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.17.0.tgz", + "integrity": "sha512-ODB7yg6lxhBVMeiH1c7E95FLD4E/TwmFjltiU+ethv7KPdCwgiFuOZg9zNRHyufStTDLl/dEFqI2Q1VPmCd78A==", "dev": true, "requires": { "array-includes": "^3.0.3", "doctrine": "^2.1.0", + "eslint-plugin-eslint-plugin": "^2.1.0", "has": "^1.0.3", - "jsx-ast-utils": "^2.1.0", + "jsx-ast-utils": "^2.2.3", "object.entries": "^1.1.0", - "object.fromentries": "^2.0.0", + "object.fromentries": "^2.0.1", "object.values": "^1.1.0", "prop-types": "^15.7.2", - "resolve": "^1.10.1" + "resolve": "^1.13.1" + }, + "dependencies": { + "resolve": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.13.1.tgz", + "integrity": "sha512-CxqObCX8K8YtAhOBRg+lrcdn+LK+WYOS8tSjqSFbjtrI5PnS63QPhZl4+yKfrU9tdsbMu9Anr/amegT87M9Z6w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + } } }, "eslint-scope": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.1.tgz", - "integrity": "sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", + "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", "dev": true, "requires": { "esrecurse": "^4.1.0", @@ -5152,13 +5245,13 @@ } }, "execa": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", "dev": true, "requires": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.0.0", + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", "is-stream": "^1.1.0", "npm-run-path": "^2.0.0", "p-finally": "^1.0.0", @@ -5315,25 +5408,20 @@ } }, "express-session": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.16.2.tgz", - "integrity": "sha512-oy0sRsdw6n93E9wpCNWKRnSsxYnSDX9Dnr9mhZgqUEEorzcq5nshGYSZ4ZReHFhKQ80WI5iVUUSPW7u3GaKauw==", + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.0.tgz", + "integrity": "sha512-t4oX2z7uoSqATbMfsxWMbNjAL0T5zpvcJCk3Z9wnPPN7ibddhnmDZXHfEcoBMG2ojKXZoCyPMc5FbtK+G7SoDg==", "requires": { - "cookie": "0.3.1", + "cookie": "0.4.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "~2.0.0", "on-headers": "~1.0.2", "parseurl": "~1.3.3", - "safe-buffer": "5.1.2", + "safe-buffer": "5.2.0", "uid-safe": "~2.1.5" }, "dependencies": { - "cookie": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" - }, "depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -5345,9 +5433,9 @@ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" }, "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==" + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" } } }, @@ -6969,10 +7057,25 @@ "dev": true }, "get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", - "dev": true + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + }, + "dependencies": { + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } }, "get-value": { "version": "2.0.6", @@ -7127,9 +7230,9 @@ "dev": true }, "handlebars": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.2.tgz", - "integrity": "sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw==", + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.3.tgz", + "integrity": "sha512-3yPecJoJHK/4c6aZhSvxOyG4vJKDshV36VHp0iVCDVh7o9w2vwi3NSnL2MMPj3YdduqaBcu7cGbggJQM0br9xA==", "dev": true, "requires": { "neo-async": "^2.6.0", @@ -7284,16 +7387,16 @@ "dev": true }, "history": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/history/-/history-4.9.0.tgz", - "integrity": "sha512-H2DkjCjXf0Op9OAr6nJ56fcRkTSNrUiv41vNJ6IswJjif6wlpZK0BTfFbi7qK9dXLSYZxkq5lBsj3vUjlYBYZA==", + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", + "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", "requires": { "@babel/runtime": "^7.1.2", "loose-envify": "^1.2.0", - "resolve-pathname": "^2.2.0", + "resolve-pathname": "^3.0.0", "tiny-invariant": "^1.0.2", "tiny-warning": "^1.0.0", - "value-equal": "^0.4.0" + "value-equal": "^1.0.1" } }, "hmac-drbg": { @@ -7308,9 +7411,9 @@ } }, "hoist-non-react-statics": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.0.tgz", - "integrity": "sha512-0XsbTXxgiaCDYDIWFcwkmerZPSwywfUqYmwT4jzewKTQSWoE6FCMoUVOeBJWK3E/CrWbxRG3m5GzY4lnIwGRBA==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", + "integrity": "sha512-wbg3bpgA/ZqWrZuMOeJi8+SKMhr7X9TesL/rXMjTzh0p0JUBo3II8DHboYbuIXWRlttrUFxwcu/5kygrCw8fJw==", "requires": { "react-is": "^16.7.0" } @@ -7740,15 +7843,13 @@ "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", "dev": true }, - "humps": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/humps/-/humps-2.0.1.tgz", - "integrity": "sha1-3QLqYIG9BWjcXQcxhEY5V7qe+ao=" - }, "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==" + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } }, "icss-replace-symbols": { "version": "1.1.0", @@ -7784,9 +7885,9 @@ "dev": true }, "ignore-walk": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", - "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", + "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", "requires": { "minimatch": "^3.0.4" } @@ -7941,9 +8042,9 @@ } }, "invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", "dev": true }, "ip": { @@ -9026,9 +9127,9 @@ } }, "jsx-ast-utils": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.2.1.tgz", - "integrity": "sha512-v3FxCcAf20DayI+uxnCuw795+oOIkVu6EnJ1+kSzhqqTZHNkTZ7B66ZgLp4oLJ/gbA64cI0B7WRoHZMSRdyVRQ==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.2.3.tgz", + "integrity": "sha512-EdIHFMm+1BPynpKOpdPqiOsvnIrInRGJD7bzPZdPkjitQEqpdpUuFpq4T0npZFKTiB3RhWFdGN+oqOJIdhDhQA==", "dev": true, "requires": { "array-includes": "^3.0.3", @@ -9067,18 +9168,18 @@ } }, "lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", "dev": true, "requires": { - "invert-kv": "^1.0.0" + "invert-kv": "^2.0.0" } }, "leaflet": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.5.1.tgz", - "integrity": "sha512-ekM9KAeG99tYisNBg0IzEywAlp0hYI5XRipsqRXyRTeuU8jcuntilpp+eFf5gaE0xubc9RuSNIVtByEKwqFV0w==" + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.6.0.tgz", + "integrity": "sha512-CPkhyqWUKZKFJ6K8umN5/D2wrJ2+/8UIpXppY7QDnUZW5bZL5+SEI2J7GBpwh4LIupOKqbNSQXgqmrEJopHVNQ==" }, "left-pad": { "version": "1.3.0", @@ -9232,16 +9333,6 @@ "js-tokens": "^3.0.0 || ^4.0.0" } }, - "lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dev": true, - "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, "make-dir": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", @@ -9317,19 +9408,19 @@ } }, "mapnik": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/mapnik/-/mapnik-4.2.1.tgz", - "integrity": "sha512-xRGIzwPu3n9T1L8V0izZ7694nWMiHxe9PGy4ghL4PDr/koIqCN0JDD9wsrvt52m53ZZC+zdeXFtuS84JqU74lQ==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/mapnik/-/mapnik-4.3.1.tgz", + "integrity": "sha512-ialXG9fqjR/M+c7Duq6kRI5CAU2L/YWYNYOjW+tatwgZeU9hTqyZkuXhDAL7G3Kk5vE/SqDRaQ9Cxe3N+bOUXA==", "requires": { "mapnik-vector-tile": "2.2.1", - "nan": "~2.10.0", - "node-pre-gyp": "~0.10.0" + "nan": "~2.14.0", + "node-pre-gyp": "~0.13.0" }, "dependencies": { "nan": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", - "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==" + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" } } }, @@ -9375,12 +9466,22 @@ "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" }, "mem": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", - "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", + "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", "dev": true, "requires": { - "mimic-fn": "^1.0.0" + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^2.0.0", + "p-is-promise": "^2.0.0" + }, + "dependencies": { + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + } } }, "memory-fs": { @@ -9482,9 +9583,9 @@ "dev": true }, "mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.0.0.tgz", + "integrity": "sha512-8ilDoEapqA4uQ3TwS0jakGONKXVJqpy+RpM+3b7pLdOjghCrEiGp9SRkFbUHAmZW9vdnrENWHjaweIoTIJExSQ==" }, "mini-create-react-context": { "version": "0.3.2", @@ -9529,7 +9630,7 @@ }, "minimist": { "version": "0.0.8", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, "minipass": { @@ -9719,13 +9820,28 @@ "dev": true }, "needle": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/needle/-/needle-2.2.4.tgz", - "integrity": "sha512-HyoqEb4wr/rsoaIDfTH2aVL9nWtQqba2/HvMv+++m8u0dz808MaagKILxtfeSN7QU7nvbQ79zk3vYOJp9zsNEA==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.4.0.tgz", + "integrity": "sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==", "requires": { - "debug": "^2.1.2", + "debug": "^3.2.6", "iconv-lite": "^0.4.4", "sax": "^1.2.4" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } } }, "negotiator": { @@ -9747,9 +9863,9 @@ "dev": true }, "node-abi": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.11.0.tgz", - "integrity": "sha512-kuy/aEg75u40v378WRllQ4ZexaXJiCvB68D2scDXclp/I4cRq6togpbOoKhmN07tns9Zldu51NNERo0wehfX9g==", + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.13.0.tgz", + "integrity": "sha512-9HrZGFVTR5SOu3PZAnAY2hLO36aW1wmA+FDsVkr85BTST32TLCA1H/AEcatVRAsWLyXS3bqUDYCAjq5/QGuSTA==", "requires": { "semver": "^5.4.1" } @@ -9824,9 +9940,9 @@ } }, "node-pre-gyp": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.10.3.tgz", - "integrity": "sha512-d1xFs+C/IPS8Id0qPTZ4bUT8wWryfR/OzzAFxweG+uLN85oPzyo2Iw6bVlLQ/JOdgNonXLCoRyqDzDWq4iw72A==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.13.0.tgz", + "integrity": "sha512-Md1D3xnEne8b/HGVQkZZwV27WUi1ZRuZBij24TNaZwUPU3ZAFtvT6xxJGaUVillfmMKnn5oD1HoGsp2Ftik7SQ==", "requires": { "detect-libc": "^1.0.2", "mkdirp": "^0.5.1", @@ -9850,9 +9966,9 @@ } }, "nodemailer": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.3.0.tgz", - "integrity": "sha512-TEHBNBPHv7Ie/0o3HXnb7xrPSSQmH1dXwQKRaMKDBGt/ZN54lvDVujP6hKkO/vjkIYL9rK8kHSG11+G42Nhxuw==" + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.4.1.tgz", + "integrity": "sha512-mSQAzMim8XIC1DemK9TifDTIgASfoJEllG5aC1mEtZeZ+FQyrSOdGBRth6JRA1ERzHQCET3QHVSd9Kc6mh356g==" }, "noop-logger": { "version": "0.1.1", @@ -9902,14 +10018,22 @@ "dev": true }, "npm-bundled": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.6.tgz", - "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz", + "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==", + "requires": { + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npm-normalize-package-bin": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", + "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==" }, "npm-packlist": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.1.tgz", - "integrity": "sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw==", + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.7.tgz", + "integrity": "sha512-vAj7dIkp5NhieaGZxBJB8fF4R0078rqsmhJcAfXZ6O7JJhjhPK96n5Ry1oZcfLXgfun0GWTZPOxaEyqv8GBykQ==", "requires": { "ignore-walk": "^3.0.1", "npm-bundled": "^1.0.1" @@ -9994,6 +10118,12 @@ } } }, + "object-inspect": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", + "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", + "dev": true + }, "object-keys": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.0.tgz", @@ -10042,15 +10172,58 @@ } }, "object.fromentries": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.0.tgz", - "integrity": "sha512-9iLiI6H083uiqUuvzyY6qrlmc/Gz8hLQFOcb/Ri/0xXFkSNS3ctV+CbE6yM2+AnkYfOB3dGjdzC0wrMLIhQICA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.1.tgz", + "integrity": "sha512-PUQv8Hbg3j2QX0IQYv3iAGCbGcu4yY4KQ92/dhA4sFSixBmSmp13UpDLs6jGK8rBtbmhNNIK99LD2k293jpiGA==", "dev": true, "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.11.0", + "define-properties": "^1.1.3", + "es-abstract": "^1.15.0", "function-bind": "^1.1.1", - "has": "^1.0.1" + "has": "^1.0.3" + }, + "dependencies": { + "es-abstract": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.16.3.tgz", + "integrity": "sha512-WtY7Fx5LiOnSYgF5eg/1T+GONaGmpvpPdCpSnYij+U2gDTL0UPfWrhDw7b2IYb+9NQJsYpCA0wOQvZfsd6YwRw==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.1.4", + "is-regex": "^1.0.4", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "string.prototype.trimleft": "^2.1.0", + "string.prototype.trimright": "^2.1.0" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + } } }, "object.getownpropertydescriptors": { @@ -10210,14 +10383,14 @@ "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" }, "os-locale": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", - "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", "dev": true, "requires": { - "execa": "^0.7.0", - "lcid": "^1.0.0", - "mem": "^1.1.0" + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" } }, "os-tmpdir": { @@ -10457,14 +10630,14 @@ "dev": true }, "pg": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/pg/-/pg-7.12.1.tgz", - "integrity": "sha512-l1UuyfEvoswYfcUe6k+JaxiN+5vkOgYcVSbSuw3FvdLqDbaoa2RJo1zfJKfPsSYPFVERd4GHvX3s2PjG1asSDA==", + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-7.14.0.tgz", + "integrity": "sha512-TLsdOWKFu44vHdejml4Uoo8h0EwCjdIj9Z9kpz7pA5i8iQxOTwVb1+Fy+X86kW5AXKxQpYpYDs4j/qPDbro/lg==", "requires": { "buffer-writer": "2.0.0", "packet-reader": "1.0.0", "pg-connection-string": "0.1.3", - "pg-pool": "^2.0.4", + "pg-pool": "^2.0.7", "pg-types": "^2.1.0", "pgpass": "1.x", "semver": "4.3.2" @@ -10509,16 +10682,6 @@ "spex": "2.2.0" }, "dependencies": { - "buffer-writer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", - "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==" - }, - "packet-reader": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", - "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" - }, "pg": { "version": "7.11.0", "resolved": "https://registry.npmjs.org/pg/-/pg-7.11.0.tgz", @@ -10533,11 +10696,6 @@ "semver": "4.3.2" } }, - "pg-pool": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-2.0.7.tgz", - "integrity": "sha512-UiJyO5B9zZpu32GSlP0tXy8J2NsJ9EFGFfz5v6PSbdz/1hBLX1rNiiy5+mAm5iJJYwfCv4A0EBcQLGWwjbpzZw==" - }, "pg-types": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.0.1.tgz", @@ -10550,16 +10708,6 @@ "postgres-interval": "^1.1.0" } }, - "postgres-array": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", - "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==" - }, - "postgres-date": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.4.tgz", - "integrity": "sha512-bESRvKVuTrjoBluEcpv2346+6kgB7UlnqWZsnbnCccTNq/pqfj1j6oBaN5+b/NrDXepYUT/HKadqv3iS9lJuVA==" - }, "semver": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.2.tgz", @@ -13207,17 +13355,17 @@ "integrity": "sha512-bESRvKVuTrjoBluEcpv2346+6kgB7UlnqWZsnbnCccTNq/pqfj1j6oBaN5+b/NrDXepYUT/HKadqv3iS9lJuVA==" }, "postgres-interval": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.1.2.tgz", - "integrity": "sha512-fC3xNHeTskCxL1dC8KOtxXt7YeFmlbTYtn7ul8MkVERuTmf7pI4DrkAxcw3kh1fQ9uz4wQmd03a1mRiXUZChfQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", "requires": { "xtend": "^4.0.0" } }, "prebuild-install": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.2.tgz", - "integrity": "sha512-INDfXzTPnhT+WYQemqnAXlP7SvfiFMopMozSgXCZ+RDLb279gKfIuLk4o7PgEawLp3WrMgIYGBpkxpraROHsSA==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.3.tgz", + "integrity": "sha512-GV+nsUXuPW2p8Zy7SarF/2W/oiK8bFQgJcncoJ0d7kRpekEA0ftChjfEaF9/Y+QJEc/wFR7RAEa8lYByuUIe2g==", "requires": { "detect-libc": "^1.0.3", "expand-template": "^2.0.3", @@ -13352,12 +13500,6 @@ "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", "dev": true }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true - }, "psl": { "version": "1.1.32", "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.32.tgz", @@ -13426,9 +13568,9 @@ "dev": true }, "query-string": { - "version": "6.8.2", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.8.2.tgz", - "integrity": "sha512-J3Qi8XZJXh93t2FiKyd/7Ec6GNifsjKXUsVFkSBj/kjLsDylWhnCz4NT1bkPcKotttPW+QbKGqqPH8OoI2pdqw==", + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.9.0.tgz", + "integrity": "sha512-KG4bhCFYapExLsUHrFt+kQVEegF2agm4cpF/VNc6pZVthIfCc/GK8t8VyNIE3nyXG9DK3Tf2EGkxjR6/uRdYsA==", "requires": { "decode-uri-component": "^0.2.0", "split-on-first": "^1.0.0", @@ -13659,15 +13801,15 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" } } }, "react": { - "version": "16.9.0", - "resolved": "https://registry.npmjs.org/react/-/react-16.9.0.tgz", - "integrity": "sha512-+7LQnFBwkiw+BobzOF6N//BdoNw0ouwmSJTEm9cglOOmsg/TMiFHZLe2sEoN5M7LgJTj9oHH0gxklfnQe66S1w==", + "version": "16.12.0", + "resolved": "https://registry.npmjs.org/react/-/react-16.12.0.tgz", + "integrity": "sha512-fglqy3k5E+81pA8s+7K0/T3DBCF0ZDOher1elBFzF7O6arXJgzyu/FW+COxFvAWXJoJN9KIZbT2LXlukwphYTA==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", @@ -13839,14 +13981,14 @@ } }, "react-dom": { - "version": "16.9.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.9.0.tgz", - "integrity": "sha512-YFT2rxO9hM70ewk9jq0y6sQk8cL02xm4+IzYBz75CQGlClQQ1Bxq0nhHF6OtSbit+AIahujJgb/CPRibFkMNJQ==", + "version": "16.12.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.12.0.tgz", + "integrity": "sha512-LMxFfAGrcS3kETtQaCkTKjMiifahaMySFDn71fZUNpPHZQEzmk/GiAeIT8JSOrHB23fnuCOMruL2a8NYlw+8Gw==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", "prop-types": "^15.6.2", - "scheduler": "^0.15.0" + "scheduler": "^0.18.0" } }, "react-error-overlay": { @@ -13886,9 +14028,9 @@ "integrity": "sha512-Wl0p9HonxGnyTly+gfiotj5f0Su/ZD7omZ5ko0ji3lEuJe4nsokbz9UcLt9CaR/C33Ha4OmzPn8I/XqwKJUs7g==" }, "react-router": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.0.1.tgz", - "integrity": "sha512-EM7suCPNKb1NxcTZ2LEOWFtQBQRQXecLxVpdsP4DW4PbbqYWeRiLyV/Tt1SdCrvT2jcyXAXmVTmzvSzrPR63Bg==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.1.2.tgz", + "integrity": "sha512-yjEuMFy1ONK246B+rsa0cUam5OeAQ8pyclRDgpxuSCrAlJ1qN9uZ5IgyKC7gQg0w8OM50NXHEegPh/ks9YuR2A==", "requires": { "@babel/runtime": "^7.1.2", "history": "^4.9.0", @@ -13908,9 +14050,9 @@ "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" }, "path-to-regexp": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", - "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", "requires": { "isarray": "0.0.1" } @@ -13918,15 +14060,15 @@ } }, "react-router-dom": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.0.1.tgz", - "integrity": "sha512-zaVHSy7NN0G91/Bz9GD4owex5+eop+KvgbxXsP/O+iW1/Ln+BrJ8QiIR5a6xNPtrdTvLkxqlDClx13QO1uB8CA==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.1.2.tgz", + "integrity": "sha512-7BPHAaIwWpZS074UKaw1FjVdZBSVWEk8IuDXdB+OkLb8vd/WRQIpA4ag9WQk61aEfQs47wHyjWUoUGGZxpQXew==", "requires": { "@babel/runtime": "^7.1.2", "history": "^4.9.0", "loose-envify": "^1.3.1", "prop-types": "^15.6.2", - "react-router": "5.0.1", + "react-router": "5.1.2", "tiny-invariant": "^1.0.2", "tiny-warning": "^1.0.0" } @@ -14548,9 +14690,9 @@ "dev": true }, "resolve-pathname": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-2.2.0.tgz", - "integrity": "sha512-bAFz9ld18RzJfddgrO2e/0S2O81710++chRMUxHjXOYKF6jTAMrUNZrEZ1PvV0zlhfjidm08iRPdTLPno1FuRg==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", + "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" }, "resolve-url": { "version": "0.2.1", @@ -14961,9 +15103,9 @@ "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" }, "scheduler": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.15.0.tgz", - "integrity": "sha512-xAefmSfN6jqAa7Kuq7LIJY0bwAPG3xlCj0HMEBQk1lxYiDKZscY2xJ5U/61ZTrYbmNQbXa+gc7czPkVo11tnCg==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.18.0.tgz", + "integrity": "sha512-agTSHR1Nbfi6ulI0kYNK0203joW2Y5W4po4l+v03tOoiJKpTBbxpNhWDvqc/4IcOw+KLmSiQLTasZ4cab2/UWQ==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" @@ -15055,9 +15197,9 @@ } }, "serialize-javascript": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.7.0.tgz", - "integrity": "sha512-ke8UG8ulpFOxO8f8gRYabHQe/ZntKlcig2Mp+8+URDP1D8vJZ0KUt7LYo07q25Z/+JVSgpr/cui9PIp5H6/+nA==" + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz", + "integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==" }, "serve-index": { "version": "1.9.1", @@ -15215,11 +15357,11 @@ "integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY=" }, "simple-get": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.0.3.tgz", - "integrity": "sha512-Wvre/Jq5vgoz31Z9stYWPLn0PqRqmBDpFSdypAnHu5AvRVCYPRYGnvryNLiXu8GOBNDH82J2FRHUGMjjHUpXFw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.0.tgz", + "integrity": "sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==", "requires": { - "decompress-response": "^3.3.0", + "decompress-response": "^4.2.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } @@ -15798,6 +15940,26 @@ } } }, + "string.prototype.trimleft": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz", + "integrity": "sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, + "string.prototype.trimright": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz", + "integrity": "sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -16091,6 +16253,12 @@ "worker-farm": "^1.7.0" }, "dependencies": { + "serialize-javascript": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.9.1.tgz", + "integrity": "sha512-0Vb/54WJ6k5v8sSWN09S0ora+Hnr+cX40r9F170nT+mSkaxltoE/7R3OrIdBSUv1OoiobH1QoWQbCnAO+e8J1A==", + "dev": true + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -16270,15 +16438,16 @@ "dev": true }, "ts-jest": { - "version": "24.0.2", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-24.0.2.tgz", - "integrity": "sha512-h6ZCZiA1EQgjczxq+uGLXQlNgeg02WWJBbeT8j6nyIBRQdglqbvzDoHahTEIiS6Eor6x8mK6PfZ7brQ9Q6tzHw==", + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-24.2.0.tgz", + "integrity": "sha512-Yc+HLyldlIC9iIK8xEN7tV960Or56N49MDP7hubCZUeI7EbIOTsas6rXCMB4kQjLACJ7eDOF4xWEO5qumpKsag==", "dev": true, "requires": { "bs-logger": "0.x", "buffer-from": "1.x", "fast-json-stable-stringify": "2.x", "json5": "2.x", + "lodash.memoize": "4.x", "make-error": "1.x", "mkdirp": "0.x", "resolve": "1.x", @@ -16599,16 +16768,16 @@ "dev": true }, "tslint": { - "version": "5.18.0", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.18.0.tgz", - "integrity": "sha512-Q3kXkuDEijQ37nXZZLKErssQVnwCV/+23gFEMROi8IlbaBG6tXqLPQJ5Wjcyt/yHPKBC+hD5SzuGaMora+ZS6w==", + "version": "5.20.1", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.20.1.tgz", + "integrity": "sha512-EcMxhzCFt8k+/UP5r8waCf/lzmeSyVlqxqMEDQE7rWYiQky8KpIBz1JAoYXfROHrPZ1XXd43q8yQnULOLiBRQg==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", "builtin-modules": "^1.1.1", "chalk": "^2.3.0", "commander": "^2.12.1", - "diff": "^3.2.0", + "diff": "^4.0.1", "glob": "^7.1.1", "js-yaml": "^3.13.1", "minimatch": "^3.0.4", @@ -16617,12 +16786,20 @@ "semver": "^5.3.0", "tslib": "^1.8.0", "tsutils": "^2.29.0" + }, + "dependencies": { + "diff": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.1.tgz", + "integrity": "sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==", + "dev": true + } } }, "tslint-react": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/tslint-react/-/tslint-react-4.0.0.tgz", - "integrity": "sha512-9fNE0fm9zNDx1+b6hgy8rgDN2WsQLRiIrn3+fbqm0tazBVF6jiaCFAITxmU+WSFWYE03Xhp1joCircXOe1WVAQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/tslint-react/-/tslint-react-4.1.0.tgz", + "integrity": "sha512-Y7CbFn09X7Mpg6rc7t/WPbmjx9xPI8p1RsQyiGCLWgDR6sh3+IBSlT+bEkc0PSZcWwClOkqq2wPsID8Vep6szQ==", "dev": true, "requires": { "tsutils": "^3.9.1" @@ -16714,22 +16891,29 @@ "dev": true }, "typescript": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.3.tgz", - "integrity": "sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g==", + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.3.tgz", + "integrity": "sha512-Mcr/Qk7hXqFBXMN7p7Lusj1ktCBydylfQM/FZCk5glCNQJrCUKPkMHdo9R0MTFWsC/4kPFvDS0fDPvukfCkFsw==", "dev": true }, "uglify-js": { - "version": "3.5.15", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.5.15.tgz", - "integrity": "sha512-fe7aYFotptIddkwcm6YuA0HmknBZ52ZzOsUxZEdhhkSsz7RfjHDX2QDxwKTiv4JQ5t5NhfmpgAK+J7LiDhKSqg==", + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.7.2.tgz", + "integrity": "sha512-uhRwZcANNWVLrxLfNFEdltoPNhECUR3lc+UdJoG9CBpMcSnKyWA94tc3eAujB1GcMY5Uwq8ZMp4qWpxWYDQmaA==", "dev": true, "optional": true, "requires": { - "commander": "~2.20.0", + "commander": "~2.20.3", "source-map": "~0.6.1" }, "dependencies": { + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "optional": true + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -17000,9 +17184,9 @@ } }, "value-equal": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-0.4.0.tgz", - "integrity": "sha512-x+cYdNnaA3CxvMaTX0INdTCN8m8aF2uY9BvEqmxuYp8bL09cs/kWVQPVGcA35fMktdOsP69IgU7wFj/61dJHEw==" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", + "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==" }, "vary": { "version": "1.1.2", @@ -17065,7 +17249,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } @@ -17906,7 +18090,7 @@ }, "wrap-ansi": { "version": "2.1.0", - "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "dev": true, "requires": { @@ -17987,23 +18171,17 @@ "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", "dev": true }, - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true - }, "yargs": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz", - "integrity": "sha512-NwW69J42EsCSanF8kyn5upxvjp5ds+t3+udGBeTbFnERA+lF541DDpMawzo4z6W/QrzNM18D+BPMiOBibnFV5A==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-11.1.1.tgz", + "integrity": "sha512-PRU7gJrJaXv3q3yQZ/+/X6KBswZiaQ+zOmdprZcouPYtQgvNU35i+68M4b1ZHLZtYFT5QObFLV+ZkmJYcwKdiw==", "dev": true, "requires": { "cliui": "^4.0.0", "decamelize": "^1.1.1", "find-up": "^2.1.0", "get-caller-file": "^1.0.1", - "os-locale": "^2.0.0", + "os-locale": "^3.1.0", "require-directory": "^2.1.1", "require-main-filename": "^1.0.1", "set-blocking": "^2.0.0", diff --git a/app/package.json b/app/package.json index e1048cfd..9c9915f5 100644 --- a/app/package.json +++ b/app/package.json @@ -14,55 +14,51 @@ "dependencies": { "@fortawesome/fontawesome-svg-core": "^1.2.21", "@fortawesome/free-solid-svg-icons": "^5.10.1", - "@fortawesome/react-fontawesome": "^0.1.4", + "@fortawesome/react-fontawesome": "^0.1.8", "@mapbox/sphericalmercator": "^1.1.0", "body-parser": "^1.19.0", - "bootstrap": "^4.3.1", + "bootstrap": "^4.4.1", "connect-pg-simple": "^6.0.1", "express": "^4.17.1", - "express-session": "^1.16.2", - "leaflet": "^1.5.1", + "express-session": "^1.17.0", + "leaflet": "^1.6.0", "mapnik": "^4.2.1", "node-fs": "^0.1.7", "nodemailer": "^6.3.0", "pg-promise": "^8.7.5", - "prop-types": "^15.7.2", "query-string": "^6.8.2", "react": "^16.9.0", "react-dom": "^16.9.0", "react-leaflet": "^1.0.1", "react-leaflet-universal": "^1.2.0", "react-router-dom": "^5.0.1", - "serialize-javascript": "^1.7.0", + "serialize-javascript": "^2.1.1", "sharp": "^0.22.1" }, "devDependencies": { - "@babel/plugin-syntax-jsx": "^7.2.0", - "@babel/plugin-syntax-typescript": "^7.3.3", - "@types/express": "^4.17.0", - "@types/express-session": "^1.15.13", - "@types/jest": "^24.0.17", + "@types/express": "^4.17.2", + "@types/express-session": "^1.15.16", + "@types/jest": "^24.0.23", + "@types/lodash": "^4.14.149", "@types/mapbox__sphericalmercator": "^1.1.3", - "@types/node": "^8.10.52", - "@types/nodemailer": "^6.2.1", - "@types/prop-types": "^15.7.1", - "@types/react": "^16.9.1", - "@types/react-dom": "^16.8.5", - "@types/react-leaflet": "^2.4.0", - "@types/react-router-dom": "^4.3.4", - "@types/sharp": "^0.22.2", - "@types/webpack-env": "^1.14.0", - "babel-eslint": "^10.0.2", - "babel-plugin-typescript-to-proptypes": "^0.17.1", + "@types/node": "^12.12.25", + "@types/nodemailer": "^6.2.2", + "@types/react": "^16.9.16", + "@types/react-dom": "^16.9.4", + "@types/react-leaflet": "^2.5.0", + "@types/react-router-dom": "^4.3.5", + "@types/sharp": "^0.22.3", + "@types/webpack-env": "^1.14.1", + "babel-eslint": "^10.0.3", "eslint": "^5.16.0", - "eslint-plugin-jest": "^22.15.0", - "eslint-plugin-react": "^7.14.3", + "eslint-plugin-jest": "^22.21.0", + "eslint-plugin-react": "^7.17.0", "razzle": "^3.0.0", "razzle-plugin-typescript": "^3.0.0", - "ts-jest": "^24.0.2", - "tslint": "^5.18.0", - "tslint-react": "^4.0.0", - "typescript": "^3.5.3" + "ts-jest": "^24.2.0", + "tslint": "^5.20.1", + "tslint-react": "^4.1.0", + "typescript": "^3.7.3" }, "jest": { "transform": { diff --git a/app/public/openapi.yml b/app/public/openapi.yml new file mode 100644 index 00000000..079ac355 --- /dev/null +++ b/app/public/openapi.yml @@ -0,0 +1,155 @@ +openapi: 3.0.0 +info: + title: Colouring London API + version: 1.0.0 + +servers: + - url: https://colouring.london/api + description: Production server (uses live data) + +paths: + + /extracts: + get: + summary: Returns a list of bulk data extracts + responses: + '200': + description: A list of bulk extracts, from newest to oldest + content: + application/json: + schema: + properties: + extracts: + type: array + items: + $ref: '#/components/schemas/BulkExtract' + example: + extracts: + - extract_id: 1 + extracted_on: 2019-10-03T05:33:00.000Z + download_path: /downloads/data-extract-2019-10-03-06_33_00.zip + '400': + $ref: '#/components/responses/BadRequest' + '500': + $ref: '#/components/responses/ServerError' + + /history: + get: + summary: Returns a paginated list of edits (latest edits if no relevant parameters specified) + parameters: + - name: before_id + description: Returned edits will be ones made directly before the specified revision ID + in: query + schema: + $ref: '#/components/schemas/RevisionId' + required: false + - name: after_id + description: Returned edits will be ones made directly after the specified revision ID + in: query + schema: + $ref: '#/components/schemas/RevisionId' + required: false + - name: count + description: The desired number of records to return + in: query + schema: + type: number + minimum: 1 + maximum: 100 + default: 100 + required: false + responses: + '200': + description: A list of edit history records + content: + application/json: + schema: + properties: + history: + type: array + items: + $ref: '#/components/schemas/BuildingEditHistoryEntry' + paging: + type: object + properties: + id_for_older_query: + allOf: + - $ref: '#/components/schemas/RevisionId' + - description: If older records exist - ID to use for querying them (use as before_id param), otherwise null + nullable: true + id_for_newer_query: + allOf: + - $ref: '#/components/schemas/RevisionId' + - description: If newer records exist - ID to use for querying them (use as after_id param), otherwise null + nullable: true + '400': + $ref: '#/components/responses/BadRequest' + '500': + $ref: '#/components/responses/ServerError' + +components: + responses: + BadRequest: + description: Invalid request submitted by user + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + ServerError: + description: Unexpected server error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + example: + error: Database error + + schemas: + Error: + type: object + properties: + error: + type: string + description: Error message + + BulkExtract: + type: object + properties: + extract_id: + type: integer + description: Unique sequential ID for the extract + extracted_on: + type: string + format: date-time + description: UTC timestamp at which the extract was generated + download_path: + type: string + description: Download path for the extract. Contains only URL path (should be used with the same hostname as the API). + + RevisionId: + description: Unique sequential ID for an edit history entry (positive big integer) + type: string + pattern: ^[1-9]\d*& + + BuildingEditHistoryEntry: + type: object + properties: + revision_id: + $ref: '#/components/schemas/RevisionId' + forward_patch: + type: object + description: Forward diff of the building attribute data + reverse_patch: + type: object + description: Reverse diff of the building attribute data + revision_timestamp: + type: string + format: date-time + description: UTC timestamp at which the building data was edited + username: + type: string + description: Username of the editor + building_id: + type: number + description: Unique ID of the edited building \ No newline at end of file diff --git a/app/razzle.config.js b/app/razzle.config.js index 42bd60aa..aa6348cd 100644 --- a/app/razzle.config.js +++ b/app/razzle.config.js @@ -9,17 +9,6 @@ module.exports = { }) config.module.rules = rules; - // find module rule that runs ts-loader for TS(X) files - const tsRule = config.module.rules.find(r => - new RegExp(r.test).test('test.tsx') && Array.isArray(r.use) && r.use.some(u => u.loader.includes('ts-loader'))); - - // run babel-loader before ts-loader to generate propTypes - tsRule.use.push({ - loader: 'babel-loader', - options: { - babelrc: true - } - }) return config; }, }; diff --git a/app/src/api/api.ts b/app/src/api/api.ts index 59471b6b..efa2bc14 100644 --- a/app/src/api/api.ts +++ b/app/src/api/api.ts @@ -2,6 +2,8 @@ import bodyParser from 'body-parser'; import express from 'express'; import * as editHistoryController from './controllers/editHistoryController'; +import { ApiParamError, ApiUserError } from './errors/api'; +import { DatabaseError } from './errors/general'; import buildingsRouter from './routes/buildingsRouter'; import extractsRouter from './routes/extractsRouter'; import usersRouter from './routes/usersRouter'; @@ -93,14 +95,30 @@ server.get('/search', function (req, res) { }); }); -server.use((err, req, res, next) => { +server.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => { if (res.headersSent) { return next(err); } if (err != undefined) { console.log('Global error handler: ', err); - res.status(500).send({ error: 'Server error' }); + + if (err instanceof ApiUserError) { + let errorMessage: string; + + if(err instanceof ApiParamError) { + errorMessage = `Problem with parameter ${err.paramName}: ${err.message}`; + } else { + errorMessage = err.message; + } + + res.status(400).send({ error: errorMessage }); + } else if(err instanceof DatabaseError){ + res.status(500).send({ error: 'Database error' }); + } else { + res.status(500).send({ error: 'Server error' }); + } + } }); diff --git a/app/src/api/controllers/buildingController.ts b/app/src/api/controllers/buildingController.ts index 269ea7b1..0bb6ad23 100644 --- a/app/src/api/controllers/buildingController.ts +++ b/app/src/api/controllers/buildingController.ts @@ -1,5 +1,6 @@ import express from 'express'; +import { parsePositiveIntParam, processParam } from '../parameters'; import asyncController from '../routes/asyncController'; import * as buildingService from '../services/building'; import * as userService from '../services/user'; @@ -34,9 +35,10 @@ const getBuildingsByReference = asyncController(async (req: express.Request, res // GET individual building, POST building updates const getBuildingById = asyncController(async (req: express.Request, res: express.Response) => { - const { building_id } = req.params; + const buildingId = processParam(req.params, 'building_id', parsePositiveIntParam, true); + try { - const result = await buildingService.getBuildingById(building_id); + const result = await buildingService.getBuildingById(buildingId); res.send(result); } catch(error) { console.error(error); @@ -61,11 +63,12 @@ const updateBuildingById = asyncController(async (req: express.Request, res: exp }); async function updateBuilding(req: express.Request, res: express.Response, userId: string) { - const { building_id } = req.params; + const buildingId = processParam(req.params, 'building_id', parsePositiveIntParam, true); + const buildingUpdate = req.body; try { - const building = await buildingService.saveBuilding(building_id, buildingUpdate, userId); + const building = await buildingService.saveBuilding(buildingId, buildingUpdate, userId); if (typeof (building) === 'undefined') { return res.send({ error: 'Database error' }); @@ -81,9 +84,10 @@ async function updateBuilding(req: express.Request, res: express.Response, userI // GET building UPRNs const getBuildingUPRNsById = asyncController(async (req: express.Request, res: express.Response) => { - const { building_id } = req.params; + const buildingId = processParam(req.params, 'building_id', parsePositiveIntParam, true); + try { - const result = await buildingService.getBuildingUPRNsById(building_id); + const result = await buildingService.getBuildingUPRNsById(buildingId); if (typeof (result) === 'undefined') { return res.send({ error: 'Database error' }); @@ -100,9 +104,11 @@ const getBuildingLikeById = asyncController(async (req: express.Request, res: ex if (!req.session.user_id) { return res.send({ like: false }); // not logged in, so cannot have liked } - const { building_id } = req.params; + + const buildingId = processParam(req.params, 'building_id', parsePositiveIntParam, true); + try { - const like = await buildingService.getBuildingLikeById(building_id, req.session.user_id); + const like = await buildingService.getBuildingLikeById(buildingId, req.session.user_id); // any value returned means like res.send({ like: like }); @@ -112,9 +118,10 @@ const getBuildingLikeById = asyncController(async (req: express.Request, res: ex }); const getBuildingEditHistoryById = asyncController(async (req: express.Request, res: express.Response) => { - const { building_id } = req.params; + const buildingId = processParam(req.params, 'building_id', parsePositiveIntParam, true); + try { - const editHistory = await buildingService.getBuildingEditHistory(building_id); + const editHistory = await buildingService.getBuildingEditHistory(buildingId); res.send({ history: editHistory }); } catch(error) { @@ -127,13 +134,13 @@ const updateBuildingLikeById = asyncController(async (req: express.Request, res: return res.send({ error: 'Must be logged in' }); } - const { building_id } = req.params; + const buildingId = processParam(req.params, 'building_id', parsePositiveIntParam, true); const { like } = req.body; try { const building = like ? - await buildingService.likeBuilding(building_id, req.session.user_id) : - await buildingService.unlikeBuilding(building_id, req.session.user_id); + await buildingService.likeBuilding(buildingId, req.session.user_id) : + await buildingService.unlikeBuilding(buildingId, req.session.user_id); if (building.error) { return res.send(building); diff --git a/app/src/api/controllers/editHistoryController.ts b/app/src/api/controllers/editHistoryController.ts index c266e7f5..2c8dcc8d 100644 --- a/app/src/api/controllers/editHistoryController.ts +++ b/app/src/api/controllers/editHistoryController.ts @@ -1,17 +1,32 @@ import express from 'express'; +import { ApiParamError, ApiUserError } from '../errors/api'; +import { ArgumentError } from '../errors/general'; +import { checkRegexParam, parsePositiveIntParam, processParam } from '../parameters'; import asyncController from "../routes/asyncController"; import * as editHistoryService from '../services/editHistory'; const getGlobalEditHistory = asyncController(async (req: express.Request, res: express.Response) => { + + const revisionIdRegex = /^[1-9]\d*$/; + const afterId: string = processParam(req.query, 'after_id', x => checkRegexParam(x, revisionIdRegex)); + const beforeId: string = processParam(req.query, 'before_id', x => checkRegexParam(x, revisionIdRegex)); + const count: number = processParam(req.query, 'count', parsePositiveIntParam); + + if(afterId != undefined && beforeId != undefined) { + throw new ApiUserError('Cannot specify both after_id and before_id parameters'); + } + try { - const result = await editHistoryService.getGlobalEditHistory(); - res.send({ - history: result - }); + const result = await editHistoryService.getGlobalEditHistory(beforeId, afterId, count); + res.send(result); } catch(error) { - console.error(error); - res.send({ error: 'Database error' }); + if(error instanceof ArgumentError && error.argumentName === 'count') { + const apiErr = new ApiParamError(error.message, 'count'); + throw apiErr; + } + + throw error; } }); diff --git a/app/src/api/controllers/extractController.ts b/app/src/api/controllers/extractController.ts index 183c23d8..077d69c5 100644 --- a/app/src/api/controllers/extractController.ts +++ b/app/src/api/controllers/extractController.ts @@ -1,5 +1,6 @@ import express from 'express'; +import { parsePositiveIntParam, processParam } from '../parameters'; import asyncController from '../routes/asyncController'; import * as dataExtractService from '../services/dataExtract'; @@ -14,8 +15,9 @@ const getAllDataExtracts = asyncController(async function(req: express.Request, }); const getDataExtract = asyncController(async function(req: express.Request, res: express.Response) { + const extractId = processParam(req.params, 'extract_id', parsePositiveIntParam, true); + try { - const extractId = req.params.extract_id; const extract = await dataExtractService.getDataExtractById(extractId); res.send({ extract: extract }); } catch (err) { diff --git a/app/src/api/dataAccess/__mocks__/editHistory.ts b/app/src/api/dataAccess/__mocks__/editHistory.ts new file mode 100644 index 00000000..b4636864 --- /dev/null +++ b/app/src/api/dataAccess/__mocks__/editHistory.ts @@ -0,0 +1,62 @@ +import { EditHistoryEntry } from '../../../frontend/models/edit-history-entry'; +import { numAsc, numDesc } from '../../../helpers'; + +/** + * Create an object mocking all method of editHistory dataAccess + * The type is set to reflect the type of that module, with added methods + * used when testing + */ +const mockEditHistory = + jest.genMockFromModule('../editHistory') as typeof import('../editHistory') & { + __setHistory: (mockHistoryData: EditHistoryEntry[]) => void + }; + +let mockData: EditHistoryEntry[] = []; + +mockEditHistory.__setHistory = function(mockHistoryData: EditHistoryEntry[]) { + mockData = mockHistoryData.sort(numDesc(x => BigInt(x.revision_id))); +}; + +mockEditHistory.getHistoryAfterId = function(id: string, count: number): Promise { + return Promise.resolve( + mockData + .filter(x => BigInt(x.revision_id) > BigInt(id)) + .sort(numAsc(x => BigInt(x.revision_id))) + .slice(0, count) + .sort(numDesc(x => BigInt(x.revision_id))) + ); +}; + +mockEditHistory.getHistoryBeforeId = function(id: string, count: number): Promise { + let filteredData = id == undefined ? mockData : mockData.filter(x => BigInt(x.revision_id) < BigInt(id)); + return Promise.resolve( + filteredData + .slice(0, count) + ); +}; + +mockEditHistory.getIdNewerThan = async function(id: string): Promise { + const historyAfterId = await mockEditHistory.getHistoryAfterId(id, 1); + return historyAfterId[historyAfterId.length - 1]?.revision_id; +}; + +mockEditHistory.getIdOlderThan = async function(id: string): Promise { + const historyBeforeId = await mockEditHistory.getHistoryBeforeId(id, 1); + return historyBeforeId[0]?.revision_id; +}; + +const { + __setHistory, + getHistoryAfterId, + getHistoryBeforeId, + getIdNewerThan, + getIdOlderThan +} = mockEditHistory; + +export { + __setHistory, + getHistoryAfterId, + getHistoryBeforeId, + getIdNewerThan, + getIdOlderThan +}; diff --git a/app/src/api/dataAccess/editHistory.ts b/app/src/api/dataAccess/editHistory.ts new file mode 100644 index 00000000..c500f293 --- /dev/null +++ b/app/src/api/dataAccess/editHistory.ts @@ -0,0 +1,88 @@ +import db from '../../db'; +import { EditHistoryEntry } from '../../frontend/models/edit-history-entry'; +import { DatabaseError } from '../errors/general'; + +const baseQuery = ` + SELECT + log_id as revision_id, + forward_patch, + reverse_patch, + date_trunc('minute', log_timestamp) as revision_timestamp, + username, + building_id + FROM logs + JOIN users ON logs.user_id = users.user_id`; + +export function getHistoryAfterId(id: string, count: number): Promise { + /** + * SQL with lower time bound specified (records after ID). + * The outer SELECT is so that final results are sorted by descending ID + * (like the other queries). The inner select is sorted in ascending order + * so that the right rows are returned when limiting the result set. + */ + try { + return db.any(` + SELECT * FROM ( + ${baseQuery} + WHERE log_id > $1 + ORDER BY revision_id ASC + LIMIT $2 + ) AS result_asc ORDER BY revision_id DESC`, + [id, count] + ); + } catch(err) { + throw new DatabaseError(err); + } +} + +export function getHistoryBeforeId(id: string, count: number): Promise { + try { + if(id == undefined) { + + return db.any(` + ${baseQuery} + ORDER BY revision_id DESC + LIMIT $1 + `, [count]); + + } else { + + return db.any(` + ${baseQuery} + WHERE log_id < $1 + ORDER BY revision_id DESC + LIMIT $2 + `, [id, count]); + } + } catch(err) { + throw new DatabaseError(err); + } +} + +export async function getIdOlderThan(id: string): Promise { + try { + const result = await db.oneOrNone<{revision_id:string}>(` + SELECT MAX(log_id) as revision_id + FROM logs + WHERE log_id < $1 + `, [id]); + + return result?.revision_id; + } catch(err) { + throw new DatabaseError(err); + } +} + +export async function getIdNewerThan(id: string): Promise { + try { + const result = await db.oneOrNone<{revision_id:string}>(` + SELECT MIN(log_id) as revision_id + FROM logs + WHERE log_id > $1 + `, [id]); + + return result?.revision_id; + } catch(err) { + throw new DatabaseError(err); + } +} diff --git a/app/src/api/errors/api.ts b/app/src/api/errors/api.ts new file mode 100644 index 00000000..cbc4ba2f --- /dev/null +++ b/app/src/api/errors/api.ts @@ -0,0 +1,44 @@ +/** + * Note that custom errors and the instanceof operator in TS work together + * only when transpiling to ES2015 and up. + * For earier target versions (ES5), a workaround is required: + * https://stackoverflow.com/questions/41102060/typescript-extending-error-class + */ + +export class ApiUserError extends Error { + constructor(message?: string) { + super(message); + this.name = 'ApiUserError'; + } +} + +export class ApiParamError extends ApiUserError { + public paramName: string; + + constructor(message?: string, paramName?: string) { + super(message); + this.name = 'ApiParamError'; + this.paramName = paramName; + } +} + +export class ApiParamRequiredError extends ApiParamError { + constructor(message?: string) { + super(message); + this.name = 'ApiParamRequiredError'; + } +} + +export class ApiParamOutOfBoundsError extends ApiParamError { + constructor(message?: string) { + super(message); + this.name = 'ApiParamOutOfBoundsError'; + } +} + +export class ApiParamInvalidFormatError extends ApiParamError { + constructor(message?: string) { + super(message); + this.name = 'ApiParamInvalidFormatError'; + } +} diff --git a/app/src/api/errors/general.ts b/app/src/api/errors/general.ts new file mode 100644 index 00000000..faacdb41 --- /dev/null +++ b/app/src/api/errors/general.ts @@ -0,0 +1,17 @@ +export class ArgumentError extends Error { + public argumentName: string; + constructor(message?: string, argumentName?: string) { + super(message); + this.name = 'ArgumentError'; + this.argumentName = argumentName; + } +} + +export class DatabaseError extends Error { + public detail: any; + constructor(detail?: string) { + super(); + this.name = 'DatabaseError'; + this.detail = detail; + } +} diff --git a/app/src/api/parameters.ts b/app/src/api/parameters.ts new file mode 100644 index 00000000..865c0947 --- /dev/null +++ b/app/src/api/parameters.ts @@ -0,0 +1,44 @@ +import { strictParseInt } from '../parse'; + +import { ApiParamError, ApiParamInvalidFormatError, ApiParamRequiredError } from './errors/api'; + + +export function processParam(params: object, paramName: string, processingFn: (x: string) => T, required: boolean = false) { + const stringValue = params[paramName]; + + if(stringValue == undefined && required) { + const err = new ApiParamRequiredError('Parameter required but not supplied'); + err.paramName = paramName; + throw err; + } + + try { + return processingFn(stringValue); + } catch(error) { + if(error instanceof ApiParamError) { + error.paramName = paramName; + } + + throw error; + } +} + +export function parsePositiveIntParam(param: string) { + if(param == undefined) return undefined; + + const result = strictParseInt(param); + if (isNaN(result)) { + throw new ApiParamInvalidFormatError('Invalid format: not a positive integer'); + } + return result; +} + +export function checkRegexParam(param: string, regex: RegExp): string { + if(param == undefined) return undefined; + + if(param.match(regex) == undefined) { + throw new ApiParamInvalidFormatError(`Invalid format: does not match regular expression ${regex}`); + } + + return param; +} diff --git a/app/src/api/services/__tests__/editHistory.test.ts b/app/src/api/services/__tests__/editHistory.test.ts new file mode 100644 index 00000000..e8fd2c6e --- /dev/null +++ b/app/src/api/services/__tests__/editHistory.test.ts @@ -0,0 +1,176 @@ +import { EditHistoryEntry } from '../../../frontend/models/edit-history-entry'; +import * as editHistoryData from '../../dataAccess/editHistory'; // manually mocked +import { ArgumentError } from '../../errors/general'; +import { getGlobalEditHistory } from '../editHistory'; + +jest.mock('../../dataAccess/editHistory'); + +const mockedEditHistoryData = editHistoryData as typeof import('../../dataAccess/__mocks__/editHistory'); + +function generateHistory(n: number, firstId: number = 100) { + return [...Array(n).keys()].map(i => ({ + revision_id: (firstId + i) + '', + revision_timestamp: new Date(2019, 10, 1, 17, 20 + i).toISOString(), + username: 'testuser', + building_id: 1234567, + forward_patch: {}, + reverse_patch: {} + })); +} + +describe('getGlobalEditHistory()', () => { + + beforeEach(() => mockedEditHistoryData.__setHistory(generateHistory(20))); + + afterEach(() => jest.clearAllMocks()); + + it.each([ + [null, null], + ['100', null], + [null, '100'] + ])('Should error when requesting non-positive number of records', async (beforeId: string, afterId: string) => { + let resultPromise = getGlobalEditHistory(beforeId, afterId, 0); + await expect(resultPromise).rejects.toBeInstanceOf(ArgumentError); + await expect(resultPromise).rejects.toHaveProperty('argumentName', 'count'); + }); + + describe('getting history before a point', () => { + + it('should return latest history if no ID specified', async () => { + const result = await getGlobalEditHistory(null, null, 5); + + expect(result.history.map(x => x.revision_id)).toEqual(['119', '118', '117', '116', '115']); + }); + + it.each( + [ + [null, 3, ['119', '118', '117']], + [null, 6, ['119', '118', '117', '116', '115', '114']], + ['118', 1, ['117']], + ['104', 10, ['103', '102','101', '100']], + ['100', 2, []] + ] + )('should return the N records before the specified ID in descending order [beforeId: %p, count: %p]', async ( + beforeId: string, count: number, ids: string[] + ) => { + const result = await getGlobalEditHistory(beforeId, null, count); + + expect(result.history.map(h => h.revision_id)).toEqual(ids); + }); + + it.each([ + [null, 4, null], + [null, 10, null], + [null, 20, null], + [null, 30, null], + ['50', 10, '99'], + ['100', 10, '99'], + ['130', 10, null], + ['105', 2, '104'], + ['120', 20, null], + ])('should detect if there are any newer records left [beforeId: %p, count: %p]', async ( + beforeId: string, count: number, idForNewerQuery: string + ) => { + const result = await getGlobalEditHistory(beforeId, null, count); + + expect(result.paging.id_for_newer_query).toBe(idForNewerQuery); + }); + + it.each([ + [null, 4, '116'], + [null, 10, '110'], + [null, 20, null], + [null, 30, null], + ['50', 10, null], + ['100', 10, null], + ['130', 10, '110'], + ['105', 2, '103'], + ['120', 20, null], + ])('should detect if there are any older records left [beforeId: %p, count: %p]', async ( + beforeId: string, count: number, idForOlderQuery: string + ) => { + const result = await getGlobalEditHistory(beforeId, null, count); + + expect(result.paging.id_for_older_query).toBe(idForOlderQuery); + }); + }); + + + describe('getting history after a point', () => { + + it.each([ + ['100', 7, ['107', '106', '105', '104', '103', '102', '101']], + ['115', 3, ['118', '117', '116']], + ['120', 10, []] + ])('should return N records after requested ID in descending order [afterId: %p, count: %p]', async ( + afterId: string, count: number, expected: string[] + ) => { + const result = await getGlobalEditHistory(null, afterId, count); + + expect(result.history.map(x => x.revision_id)).toEqual(expected); + }); + + it.each([ + ['99', 10, '109'], + ['110', 5, '115'], + ['119', 20, null], + ['99', 20, null], + ])('should detect if there are any newer records left [afterId: %p, count: %p]', async ( + afterId: string, count: number, idForNewerQuery: string + ) => { + const result = await getGlobalEditHistory(null, afterId, count); + + expect(result.paging.id_for_newer_query).toBe(idForNewerQuery); + }); + + it.each([ + ['99', 10, null], + ['110', 5, '111'], + ['119', 20, '120'], + ['99', 20, null], + ])('should detect if there are any older records left [afterId: %p, count: %p]', async ( + afterId: string, count: number, idForOlderQuery: string + ) => { + const result = await getGlobalEditHistory(null, afterId, count); + + expect(result.paging.id_for_older_query).toBe(idForOlderQuery); + }); + + }); + + describe('result count limit', () => { + + it.each([ + [null, null], + [null, '100'], + ['300', null] + ])('should not return more than 100 entries (beforeId: %p, afterId: %p)', async ( + beforeId: string, afterId: string + ) => { + mockedEditHistoryData.__setHistory( + generateHistory(200) + ); + + const result = await getGlobalEditHistory(beforeId, afterId, 200); + + expect(result.history.length).toBe(100); + }); + + it.each([ + [null, null], + [null, '100'], + ['300', null] + ])('should default to 100 entries', async ( + beforeId: string, afterId: string + ) => { + mockedEditHistoryData.__setHistory( + generateHistory(200) + ); + + const result = await getGlobalEditHistory(beforeId, afterId); + + expect(result.history.length).toBe(100); + }); + + }); +}); diff --git a/app/src/api/services/building.ts b/app/src/api/services/building.ts index 9cc611d8..79511324 100644 --- a/app/src/api/services/building.ts +++ b/app/src/api/services/building.ts @@ -2,12 +2,15 @@ * Building data access * */ +import * as _ from 'lodash'; import { ITask } from 'pg-promise'; import db from '../../db'; import { tileCache } from '../../tiles/rendererDefinition'; import { BoundingBox } from '../../tiles/types'; +import { processBuildingUpdate } from './domainLogic/processBuildingUpdate'; + // data type note: PostgreSQL bigint (64-bit) is handled as string in JavaScript, because of // JavaScript numerics are 64-bit double, giving only partial coverage. @@ -92,12 +95,16 @@ async function queryBuildingsByReference(key: string, ref: string) { } } +async function getCurrentBuildingDataById(id: number) { + return db.one( + 'SELECT * FROM buildings WHERE building_id = $1', + [id] + ); +} + async function getBuildingById(id: number) { try { - const building = await db.one( - 'SELECT * FROM buildings WHERE building_id = $1', - [id] - ); + const building = await getCurrentBuildingDataById(id); building.edit_history = await getBuildingEditHistory(id); @@ -111,7 +118,7 @@ async function getBuildingById(id: number) { async function getBuildingEditHistory(id: number) { try { return await db.manyOrNone( - `SELECT log_id as revision_id, forward_patch, reverse_patch, date_trunc('minute', log_timestamp), username + `SELECT log_id as revision_id, forward_patch, reverse_patch, date_trunc('minute', log_timestamp) as revision_timestamp, username FROM logs, users WHERE building_id = $1 AND logs.user_id = users.user_id ORDER BY log_timestamp DESC`, @@ -151,13 +158,15 @@ async function getBuildingUPRNsById(id: number) { async function saveBuilding(buildingId: number, building: any, userId: string) { // TODO add proper building type try { return await updateBuildingData(buildingId, userId, async () => { + const processedBuilding = await processBuildingUpdate(buildingId, building); + // remove read-only fields from consideration - delete building.building_id; - delete building.revision_id; - delete building.geometry_id; + delete processedBuilding.building_id; + delete processedBuilding.revision_id; + delete processedBuilding.geometry_id; // return whitelisted fields to update - return pickAttributesToUpdate(building, BUILDING_FIELD_WHITELIST); + return pickAttributesToUpdate(processedBuilding, BUILDING_FIELD_WHITELIST); }); } catch(error) { console.error(error); @@ -395,6 +404,10 @@ const BUILDING_FIELD_WHITELIST = new Set([ // 'sust_life_expectancy', 'building_attachment_form', 'date_change_building_use', + + 'current_landuse_class', + 'current_landuse_group', + 'current_landuse_order' ]); /** @@ -411,7 +424,7 @@ function compare(oldObj: object, newObj: object): [object, object] { const reverse = {}; const forward = {}; for (const [key, value] of Object.entries(newObj)) { - if (oldObj[key] != value) { + if (!_.isEqual(oldObj[key], value)) { reverse[key] = oldObj[key]; forward[key] = value; } @@ -422,6 +435,7 @@ function compare(oldObj: object, newObj: object): [object, object] { export { queryBuildingsAtPoint, queryBuildingsByReference, + getCurrentBuildingDataById, getBuildingById, getBuildingLikeById, getBuildingEditHistory, diff --git a/app/src/api/services/dataExtract.ts b/app/src/api/services/dataExtract.ts index 063e9a7b..c7dc33d3 100644 --- a/app/src/api/services/dataExtract.ts +++ b/app/src/api/services/dataExtract.ts @@ -19,7 +19,8 @@ async function listDataExtracts(): Promise { const extractRecords = await db.manyOrNone( `SELECT extract_id, extracted_on, extract_path - FROM bulk_extracts` + FROM bulk_extracts + ORDER BY extracted_on DESC` ); return extractRecords.map(getDataExtractFromRow); diff --git a/app/src/api/services/domainLogic/currentLandUseClassifications.ts b/app/src/api/services/domainLogic/currentLandUseClassifications.ts new file mode 100644 index 00000000..e15a9244 --- /dev/null +++ b/app/src/api/services/domainLogic/currentLandUseClassifications.ts @@ -0,0 +1,102 @@ +import * as _ from 'lodash'; + +import db from '../../../db'; +import { isNullishOrEmpty } from '../../../helpers'; +import { getCurrentBuildingDataById } from '../building'; + +export async function processCurrentLandUseClassifications(buildingId: number, building: any): Promise { + let updateData = _.pick(await getCurrentBuildingDataById(buildingId), [ + 'current_landuse_class', + 'current_landuse_group', + 'current_landuse_order' + ]); + + updateData = Object.assign({}, updateData, getClearValues(building)); + + const updateFrom = getUpdateStartingStage(building); + if(updateFrom === 'class') { + updateData.current_landuse_class = building.current_landuse_class; + updateData.current_landuse_group = await deriveGroupFromClass(updateData.current_landuse_class); + updateData.current_landuse_order = await deriveOrderFromGroup(updateData.current_landuse_group); + } else if (updateFrom === 'group') { + if (isNullishOrEmpty(updateData.current_landuse_class)) { + updateData.current_landuse_group = building.current_landuse_group; + updateData.current_landuse_order = await deriveOrderFromGroup(building.current_landuse_group); + } else { + throw new Error('Trying to update current_landuse_group field but a more detailed field (current_landuse_class) is already filled'); + } + } else if (updateFrom === 'order') { + if (isNullishOrEmpty(updateData.current_landuse_class) && isNullishOrEmpty(updateData.current_landuse_group)) { + updateData.current_landuse_order = building.current_landuse_order; + } else { + throw new Error('Trying to update current_landuse_order field but a more detailed field (current_landuse_class or current_landuse_group) is already filled'); + } + } + + return Object.assign({}, building, updateData); +} + +function getClearValues(building) { + const clearValues: any = {}; + if(building.hasOwnProperty('current_landuse_class') && isNullishOrEmpty(building.current_landuse_class)) { + clearValues.current_landuse_class = []; + } + if(building.hasOwnProperty('current_landuse_group') && isNullishOrEmpty(building.current_landuse_group)) { + clearValues.current_landuse_group = []; + } + if(building.hasOwnProperty('current_landuse_order') && isNullishOrEmpty(building.current_landuse_order)) { + clearValues.current_landuse_order = null; + } + + return clearValues; +} +/** + * Choose which level of the land use classification hierarchy the update should start from. + * @param building + */ +function getUpdateStartingStage(building) { + if(building.hasOwnProperty('current_landuse_class') && !isNullishOrEmpty(building.current_landuse_class)) { + return 'class'; + } else if(building.hasOwnProperty('current_landuse_group') && !isNullishOrEmpty(building.current_landuse_group)) { + return 'group'; + } else if(building.hasOwnProperty('current_landuse_order') && !isNullishOrEmpty(building.current_landuse_order)) { + return 'order'; + } else return 'none'; +} + +async function deriveGroupFromClass(classes: string[]): Promise { + if (classes.length === 0) return []; + + return (await db.many( + ` + SELECT DISTINCT parent.description + FROM reference_tables.buildings_landuse_group AS parent + JOIN reference_tables.buildings_landuse_class AS child + ON child.parent_group_id = parent.landuse_id + WHERE child.description IN ($1:csv) + ORDER BY parent.description`, + [classes] + )).map(x => x.description); +} + +async function deriveOrderFromGroup(groups: string[]): Promise { + if(groups.length === 0) return null; + + const orders = (await db.many( + ` + SELECT DISTINCT parent.description + FROM reference_tables.buildings_landuse_order AS parent + JOIN reference_tables.buildings_landuse_group AS child + ON child.parent_order_id = parent.landuse_id + WHERE child.description IN ($1:csv) + ORDER BY parent.description + `, + [groups] + )).map(x => x.description); + + if(orders.length === 1) { + return orders[0]; + } else if (orders.length > 1) { + return 'Mixed Use'; + } else return null; +} diff --git a/app/src/api/services/domainLogic/processBuildingUpdate.ts b/app/src/api/services/domainLogic/processBuildingUpdate.ts new file mode 100644 index 00000000..7b7d166f --- /dev/null +++ b/app/src/api/services/domainLogic/processBuildingUpdate.ts @@ -0,0 +1,11 @@ +import { hasAnyOwnProperty } from '../../../helpers'; + +import { processCurrentLandUseClassifications } from './currentLandUseClassifications'; + +export async function processBuildingUpdate(buildingId: number, building: any): Promise { + if(hasAnyOwnProperty(building, ['current_landuse_class', 'current_landuse_group', 'current_landuse_order'])) { + building = await processCurrentLandUseClassifications(buildingId, building); + } + + return building; +} diff --git a/app/src/api/services/editHistory.ts b/app/src/api/services/editHistory.ts index 92bf9ca7..81b4c899 100644 --- a/app/src/api/services/editHistory.ts +++ b/app/src/api/services/editHistory.ts @@ -1,20 +1,38 @@ -import db from '../../db'; +import { EditHistoryEntry } from '../../frontend/models/edit-history-entry'; +import { decBigInt, incBigInt } from '../../helpers'; +import { getHistoryAfterId, getHistoryBeforeId, getIdNewerThan, getIdOlderThan } from '../dataAccess/editHistory'; +import { ArgumentError } from '../errors/general'; -async function getGlobalEditHistory() { - try { - return await db.manyOrNone( - `SELECT log_id as revision_id, forward_patch, reverse_patch, date_trunc('minute', log_timestamp), username, building_id - FROM logs, users - WHERE logs.user_id = users.user_id - AND log_timestamp >= now() - interval '7 days' - ORDER BY log_timestamp DESC` - ); - } catch (error) { - console.error(error); - return []; +async function getGlobalEditHistory(beforeId?: string, afterId?: string, count: number = 100) { + if(count <= 0) throw new ArgumentError('cannot request less than 1 history record', 'count'); + if(count > 100) count = 100; + + // limited set of records. Expected to be already ordered from newest to oldest + let editHistoryRecords: EditHistoryEntry[]; + + if(afterId != undefined) { + editHistoryRecords = await getHistoryAfterId(afterId, count); + } else { + editHistoryRecords = await getHistoryBeforeId(beforeId, count); } -} + const currentBatchMaxId = editHistoryRecords[0]?.revision_id ?? decBigInt(beforeId); + const newer = currentBatchMaxId && await getIdNewerThan(currentBatchMaxId); + + const currentBatchMinId = editHistoryRecords[editHistoryRecords.length-1]?.revision_id ?? incBigInt(afterId); + const older = currentBatchMinId && await getIdOlderThan(currentBatchMinId); + + const idForOlderQuery = older != undefined ? incBigInt(older) : null; + const idForNewerQuery = newer != undefined ? decBigInt(newer) : null; + + return { + history: editHistoryRecords, + paging: { + id_for_newer_query: idForNewerQuery, + id_for_older_query: idForOlderQuery + } + }; +} export { getGlobalEditHistory diff --git a/app/src/api/services/user.ts b/app/src/api/services/user.ts index 0eba9e3f..06fdec8b 100644 --- a/app/src/api/services/user.ts +++ b/app/src/api/services/user.ts @@ -60,7 +60,10 @@ async function authUser(username: string, password: string) { user_id, ( pass = crypt($2, pass) - ) AS auth_ok + ) AS auth_ok, + is_blocked, + blocked_on, + blocked_reason FROM users WHERE username = $1 @@ -71,6 +74,9 @@ async function authUser(username: string, password: string) { ); if (user && user.auth_ok) { + if (user.is_blocked) { + return { error: `Account temporarily blocked.${user.blocked_reason == undefined ? '' : ' Reason: '+user.blocked_reason}` }; + } return { user_id: user.user_id }; } else { return { error: 'Username or password not recognised' }; diff --git a/app/src/frontend/apiHelpers.ts b/app/src/frontend/apiHelpers.ts new file mode 100644 index 00000000..b35ecd7f --- /dev/null +++ b/app/src/frontend/apiHelpers.ts @@ -0,0 +1,44 @@ +type JsonReviver = (name: string, value: any) => any; + +export function apiGet(path: string, options?: { + jsonReviver?: JsonReviver +}): Promise { + return apiRequest(path, 'GET', null, options); +} + +export function apiPost(path: string, data?: object, options?: { + jsonReviver?: JsonReviver +}): Promise { + return apiRequest(path, 'POST', data, options); +} + +export function apiDelete(path: string, options?: { + jsonReviver?: JsonReviver +}): Promise { + return apiRequest(path, 'DELETE', null, options); +} + +async function apiRequest( + path: string, + method: 'GET' | 'POST' | 'DELETE', + data?: object, + options?: { + jsonReviver?: JsonReviver + } +): Promise { + const res = await fetch(path, { + method: method, + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json' + }, + body: data == undefined ? null : JSON.stringify(data), + }); + + const reviver = options == undefined ? undefined : options.jsonReviver; + if (reviver != undefined) { + return JSON.parse(await res.text(), reviver); + } else { + return await res.json(); + } +} diff --git a/app/src/frontend/building/building-view.tsx b/app/src/frontend/building/building-view.tsx index d21f7115..797e8b69 100644 --- a/app/src/frontend/building/building-view.tsx +++ b/app/src/frontend/building/building-view.tsx @@ -43,7 +43,7 @@ const BuildingView: React.FunctionComponent = (props) => { case 'use': return = (prop slug={props.slug} title={props.title} tooltip={props.tooltip} - disabled={props.disabled} + disabled={props.disabled || props.value == undefined} copy={props.copy} />
diff --git a/app/src/frontend/building/data-components/data-entry.tsx b/app/src/frontend/building/data-components/data-entry.tsx index 963173b4..1e1bfca0 100644 --- a/app/src/frontend/building/data-components/data-entry.tsx +++ b/app/src/frontend/building/data-components/data-entry.tsx @@ -28,7 +28,7 @@ const DataEntry: React.FunctionComponent = (props) => { slug={props.slug} title={props.title} tooltip={props.tooltip} - disabled={props.disabled} + disabled={props.disabled || props.value == undefined || props.value == ''} copy={props.copy} /> { slug={props.slug} title={props.title} tooltip={props.tooltip} - disabled={props.disabled} + disabled={props.disabled || props.value == undefined || props.value.length === 0} /> { (props.mode === 'view')? diff --git a/app/src/frontend/building/data-components/numeric-data-entry.tsx b/app/src/frontend/building/data-components/numeric-data-entry.tsx index 3c1a21fe..15c0c740 100644 --- a/app/src/frontend/building/data-components/numeric-data-entry.tsx +++ b/app/src/frontend/building/data-components/numeric-data-entry.tsx @@ -19,7 +19,7 @@ const NumericDataEntry: React.FunctionComponent = (props) slug={props.slug} title={props.title} tooltip={props.tooltip} - disabled={props.disabled} + disabled={props.disabled || props.value == undefined} copy={props.copy} /> = (props) type="number" id={props.slug} name={props.slug} - value={props.value || ''} - step={props.step || 1} + value={props.value == undefined ? '' : props.value} + step={props.step == undefined ? 1 : props.step} max={props.max} - min={props.min || 0} + min={props.min} disabled={props.mode === 'view' || props.disabled} placeholder={props.placeholder} onChange={e => diff --git a/app/src/frontend/building/data-components/select-data-entry.tsx b/app/src/frontend/building/data-components/select-data-entry.tsx index 7ed9a70c..01adf556 100644 --- a/app/src/frontend/building/data-components/select-data-entry.tsx +++ b/app/src/frontend/building/data-components/select-data-entry.tsx @@ -17,7 +17,7 @@ const SelectDataEntry: React.FunctionComponent = (props) = slug={props.slug} title={props.title} tooltip={props.tooltip} - disabled={props.disabled} + disabled={props.disabled || props.value == undefined} copy={props.copy} />