From 66ff0eaf51573309f58510ec77ea553c107c2b4f Mon Sep 17 00:00:00 2001 From: Monomax Bot Date: Mon, 4 May 2020 08:05:04 +0000 Subject: [PATCH 01/99] Upgrade dependencies. --- browser/package-lock.json | 228 ++-- browser/package.json | 8 +- go.mod | 12 +- go.sum | 14 + jslib/package-lock.json | 12 +- jslib/package.json | 2 +- package-lock.json | 32 +- package.json | 4 +- web/package-lock.json | 2142 ++++++++++++++++++++++++++++++------- web/package.json | 18 +- 10 files changed, 1935 insertions(+), 537 deletions(-) diff --git a/browser/package-lock.json b/browser/package-lock.json index d8098bba..2c2648e5 100644 --- a/browser/package-lock.json +++ b/browser/package-lock.json @@ -13,12 +13,12 @@ } }, "@babel/compat-data": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.9.0.tgz", - "integrity": "sha512-zeFQrr+284Ekvd9e7KAX954LkapWiOmQtsfHirhxqfdlX6MEC32iRE+pqUGlYIBchdevaCwvzxWGSy/YBNI85g==", + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.9.6.tgz", + "integrity": "sha512-5QPTrNen2bm7RBc7dsOmcA5hbrS4O2Vhmk5XOL4zWW/zD/hV0iinpefDlkm+tBBy8kDtFaaeEvmAqt+nURAV2g==", "dev": true, "requires": { - "browserslist": "^4.9.1", + "browserslist": "^4.11.1", "invariant": "^2.2.4", "semver": "^5.5.0" }, @@ -32,19 +32,19 @@ } }, "@babel/core": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.9.0.tgz", - "integrity": "sha512-kWc7L0fw1xwvI0zi8OKVBuxRVefwGOrKSQMvrQ3dW+bIIavBY3/NpXmpjMy7bQnLgwgzWQZ8TlM57YHpHNHz4w==", + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.9.6.tgz", + "integrity": "sha512-nD3deLvbsApbHAHttzIssYqgb883yU/d9roe4RZymBCDaZryMJDbptVpEpeQuRh4BJ+SYI8le9YGxKvFEvl1Wg==", "dev": true, "requires": { "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.9.0", + "@babel/generator": "^7.9.6", "@babel/helper-module-transforms": "^7.9.0", - "@babel/helpers": "^7.9.0", - "@babel/parser": "^7.9.0", + "@babel/helpers": "^7.9.6", + "@babel/parser": "^7.9.6", "@babel/template": "^7.8.6", - "@babel/traverse": "^7.9.0", - "@babel/types": "^7.9.0", + "@babel/traverse": "^7.9.6", + "@babel/types": "^7.9.6", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.1", @@ -55,6 +55,17 @@ "source-map": "^0.5.0" }, "dependencies": { + "@babel/types": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.6.tgz", + "integrity": "sha512-qxXzvBO//jO9ZnoasKF1uJzHd2+M6Q2ZPIVfnFps8JJvXy0ZBbwbNOmE6SGIY5XOY6d1Bo5lb9d9RJ8nv3WSeA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.9.5", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, "convert-source-map": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", @@ -88,15 +99,28 @@ } }, "@babel/generator": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.5.tgz", - "integrity": "sha512-GbNIxVB3ZJe3tLeDm1HSn2AhuD/mVcyLDpgtLXa5tplmWrJdF/elxB56XNqCuD6szyNkDi6wuoKXln3QeBmCHQ==", + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.6.tgz", + "integrity": "sha512-+htwWKJbH2bL72HRluF8zumBxzuX0ZZUFl3JLNyoUjM/Ho8wnVpPXM6aUz8cfKDqQ/h7zHqKt4xzJteUosckqQ==", "dev": true, "requires": { - "@babel/types": "^7.9.5", + "@babel/types": "^7.9.6", "jsesc": "^2.5.1", "lodash": "^4.17.13", "source-map": "^0.5.0" + }, + "dependencies": { + "@babel/types": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.6.tgz", + "integrity": "sha512-qxXzvBO//jO9ZnoasKF1uJzHd2+M6Q2ZPIVfnFps8JJvXy0ZBbwbNOmE6SGIY5XOY6d1Bo5lb9d9RJ8nv3WSeA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.9.5", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/helper-annotate-as-pure": { @@ -119,13 +143,13 @@ } }, "@babel/helper-compilation-targets": { - "version": "7.8.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.8.7.tgz", - "integrity": "sha512-4mWm8DCK2LugIS+p1yArqvG1Pf162upsIsjE7cNBjez+NjliQpVhj20obE520nao0o14DaTnFJv+Fw5a0JpoUw==", + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.9.6.tgz", + "integrity": "sha512-x2Nvu0igO0ejXzx09B/1fGBxY9NXQlBW2kZsSxCJft+KHN8t9XWzIvFxtPHnBOAXpVsdxZKZFbRUC8TsNKajMw==", "dev": true, "requires": { - "@babel/compat-data": "^7.8.6", - "browserslist": "^4.9.1", + "@babel/compat-data": "^7.9.6", + "browserslist": "^4.11.1", "invariant": "^2.2.4", "levenary": "^1.1.1", "semver": "^5.5.0" @@ -270,15 +294,28 @@ } }, "@babel/helper-replace-supers": { - "version": "7.8.6", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.8.6.tgz", - "integrity": "sha512-PeMArdA4Sv/Wf4zXwBKPqVj7n9UF/xg6slNRtZW84FM7JpE1CbG8B612FyM4cxrf4fMAMGO0kR7voy1ForHHFA==", + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.9.6.tgz", + "integrity": "sha512-qX+chbxkbArLyCImk3bWV+jB5gTNU/rsze+JlcF6Nf8tVTigPJSI1o1oBow/9Resa1yehUO9lIipsmu9oG4RzA==", "dev": true, "requires": { "@babel/helper-member-expression-to-functions": "^7.8.3", "@babel/helper-optimise-call-expression": "^7.8.3", - "@babel/traverse": "^7.8.6", - "@babel/types": "^7.8.6" + "@babel/traverse": "^7.9.6", + "@babel/types": "^7.9.6" + }, + "dependencies": { + "@babel/types": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.6.tgz", + "integrity": "sha512-qxXzvBO//jO9ZnoasKF1uJzHd2+M6Q2ZPIVfnFps8JJvXy0ZBbwbNOmE6SGIY5XOY6d1Bo5lb9d9RJ8nv3WSeA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.9.5", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/helper-simple-access": { @@ -318,14 +355,27 @@ } }, "@babel/helpers": { - "version": "7.9.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.9.2.tgz", - "integrity": "sha512-JwLvzlXVPjO8eU9c/wF9/zOIN7X6h8DYf7mG4CiFRZRvZNKEF5dQ3H3V+ASkHoIB3mWhatgl5ONhyqHRI6MppA==", + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.9.6.tgz", + "integrity": "sha512-tI4bUbldloLcHWoRUMAj4g1bF313M/o6fBKhIsb3QnGVPwRm9JsNf/gqMkQ7zjqReABiffPV6RWj7hEglID5Iw==", "dev": true, "requires": { "@babel/template": "^7.8.3", - "@babel/traverse": "^7.9.0", - "@babel/types": "^7.9.0" + "@babel/traverse": "^7.9.6", + "@babel/types": "^7.9.6" + }, + "dependencies": { + "@babel/types": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.6.tgz", + "integrity": "sha512-qxXzvBO//jO9ZnoasKF1uJzHd2+M6Q2ZPIVfnFps8JJvXy0ZBbwbNOmE6SGIY5XOY6d1Bo5lb9d9RJ8nv3WSeA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.9.5", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/highlight": { @@ -339,9 +389,9 @@ } }, "@babel/parser": { - "version": "7.9.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.4.tgz", - "integrity": "sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA==", + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.6.tgz", + "integrity": "sha512-AoeIEJn8vt+d/6+PXDRPaksYhnlbMIiejioBZvvMQsOjW/JYK6k/0dKnvvP3EhK5GfMBWDPtrxRtegWdAcdq9Q==", "dev": true }, "@babel/plugin-proposal-async-generator-functions": { @@ -396,9 +446,9 @@ } }, "@babel/plugin-proposal-object-rest-spread": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.9.5.tgz", - "integrity": "sha512-VP2oXvAf7KCYTthbUHwBlewbl1Iq059f6seJGsxMizaCdgHIeczOr7FBqELhSqfkIl04Fi8okzWzl63UKbQmmg==", + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.9.6.tgz", + "integrity": "sha512-Ga6/fhGqA9Hj+y6whNpPv8psyaK5xzrQwSPsGPloVkvmH+PqW1ixdnfJ9uIO06OjQNYol3PMnfmJ8vfZtkzF+A==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.8.3", @@ -657,38 +707,38 @@ } }, "@babel/plugin-transform-modules-amd": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.9.0.tgz", - "integrity": "sha512-vZgDDF003B14O8zJy0XXLnPH4sg+9X5hFBBGN1V+B2rgrB+J2xIypSN6Rk9imB2hSTHQi5OHLrFWsZab1GMk+Q==", + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.9.6.tgz", + "integrity": "sha512-zoT0kgC3EixAyIAU+9vfaUVKTv9IxBDSabgHoUCBP6FqEJ+iNiN7ip7NBKcYqbfUDfuC2mFCbM7vbu4qJgOnDw==", "dev": true, "requires": { "@babel/helper-module-transforms": "^7.9.0", "@babel/helper-plugin-utils": "^7.8.3", - "babel-plugin-dynamic-import-node": "^2.3.0" + "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-modules-commonjs": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.9.0.tgz", - "integrity": "sha512-qzlCrLnKqio4SlgJ6FMMLBe4bySNis8DFn1VkGmOcxG9gqEyPIOzeQrA//u0HAKrWpJlpZbZMPB1n/OPa4+n8g==", + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.9.6.tgz", + "integrity": "sha512-7H25fSlLcn+iYimmsNe3uK1at79IE6SKW9q0/QeEHTMC9MdOZ+4bA+T1VFB5fgOqBWoqlifXRzYD0JPdmIrgSQ==", "dev": true, "requires": { "@babel/helper-module-transforms": "^7.9.0", "@babel/helper-plugin-utils": "^7.8.3", "@babel/helper-simple-access": "^7.8.3", - "babel-plugin-dynamic-import-node": "^2.3.0" + "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-modules-systemjs": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.9.0.tgz", - "integrity": "sha512-FsiAv/nao/ud2ZWy4wFacoLOm5uxl0ExSQ7ErvP7jpoihLR6Cq90ilOFyX9UXct3rbtKsAiZ9kFt5XGfPe/5SQ==", + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.9.6.tgz", + "integrity": "sha512-NW5XQuW3N2tTHim8e1b7qGy7s0kZ2OH3m5octc49K1SdAKGxYxeIx7hiIz05kS1R2R+hOWcsr1eYwcGhrdHsrg==", "dev": true, "requires": { "@babel/helper-hoist-variables": "^7.8.3", "@babel/helper-module-transforms": "^7.9.0", "@babel/helper-plugin-utils": "^7.8.3", - "babel-plugin-dynamic-import-node": "^2.3.0" + "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-modules-umd": { @@ -824,13 +874,13 @@ } }, "@babel/preset-env": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.9.5.tgz", - "integrity": "sha512-eWGYeADTlPJH+wq1F0wNfPbVS1w1wtmMJiYk55Td5Yu28AsdR9AsC97sZ0Qq8fHqQuslVSIYSGJMcblr345GfQ==", + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.9.6.tgz", + "integrity": "sha512-0gQJ9RTzO0heXOhzftog+a/WyOuqMrAIugVYxMYf83gh1CQaQDjMtsOpqOwXyDL/5JcWsrCm8l4ju8QC97O7EQ==", "dev": true, "requires": { - "@babel/compat-data": "^7.9.0", - "@babel/helper-compilation-targets": "^7.8.7", + "@babel/compat-data": "^7.9.6", + "@babel/helper-compilation-targets": "^7.9.6", "@babel/helper-module-imports": "^7.8.3", "@babel/helper-plugin-utils": "^7.8.3", "@babel/plugin-proposal-async-generator-functions": "^7.8.3", @@ -838,7 +888,7 @@ "@babel/plugin-proposal-json-strings": "^7.8.3", "@babel/plugin-proposal-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-proposal-numeric-separator": "^7.8.3", - "@babel/plugin-proposal-object-rest-spread": "^7.9.5", + "@babel/plugin-proposal-object-rest-spread": "^7.9.6", "@babel/plugin-proposal-optional-catch-binding": "^7.8.3", "@babel/plugin-proposal-optional-chaining": "^7.9.0", "@babel/plugin-proposal-unicode-property-regex": "^7.8.3", @@ -865,9 +915,9 @@ "@babel/plugin-transform-function-name": "^7.8.3", "@babel/plugin-transform-literals": "^7.8.3", "@babel/plugin-transform-member-expression-literals": "^7.8.3", - "@babel/plugin-transform-modules-amd": "^7.9.0", - "@babel/plugin-transform-modules-commonjs": "^7.9.0", - "@babel/plugin-transform-modules-systemjs": "^7.9.0", + "@babel/plugin-transform-modules-amd": "^7.9.6", + "@babel/plugin-transform-modules-commonjs": "^7.9.6", + "@babel/plugin-transform-modules-systemjs": "^7.9.6", "@babel/plugin-transform-modules-umd": "^7.9.0", "@babel/plugin-transform-named-capturing-groups-regex": "^7.8.3", "@babel/plugin-transform-new-target": "^7.8.3", @@ -883,14 +933,25 @@ "@babel/plugin-transform-typeof-symbol": "^7.8.4", "@babel/plugin-transform-unicode-regex": "^7.8.3", "@babel/preset-modules": "^0.1.3", - "@babel/types": "^7.9.5", - "browserslist": "^4.9.1", + "@babel/types": "^7.9.6", + "browserslist": "^4.11.1", "core-js-compat": "^3.6.2", "invariant": "^2.2.2", "levenary": "^1.1.1", "semver": "^5.5.0" }, "dependencies": { + "@babel/types": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.6.tgz", + "integrity": "sha512-qxXzvBO//jO9ZnoasKF1uJzHd2+M6Q2ZPIVfnFps8JJvXy0ZBbwbNOmE6SGIY5XOY6d1Bo5lb9d9RJ8nv3WSeA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.9.5", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", @@ -932,22 +993,33 @@ } }, "@babel/traverse": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.5.tgz", - "integrity": "sha512-c4gH3jsvSuGUezlP6rzSJ6jf8fYjLj3hsMZRx/nX0h+fmHN0w+ekubRrHPqnMec0meycA2nwCsJ7dC8IPem2FQ==", + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.6.tgz", + "integrity": "sha512-b3rAHSjbxy6VEAvlxM8OV/0X4XrG72zoxme6q1MOoe2vd0bEc+TwayhuC1+Dfgqh1QEG+pj7atQqvUprHIccsg==", "dev": true, "requires": { "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.9.5", + "@babel/generator": "^7.9.6", "@babel/helper-function-name": "^7.9.5", "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.9.0", - "@babel/types": "^7.9.5", + "@babel/parser": "^7.9.6", + "@babel/types": "^7.9.6", "debug": "^4.1.0", "globals": "^11.1.0", "lodash": "^4.17.13" }, "dependencies": { + "@babel/types": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.6.tgz", + "integrity": "sha512-qxXzvBO//jO9ZnoasKF1uJzHd2+M6Q2ZPIVfnFps8JJvXy0ZBbwbNOmE6SGIY5XOY6d1Bo5lb9d9RJ8nv3WSeA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.9.5", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, "debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", @@ -1149,9 +1221,9 @@ } }, "@types/react-dom": { - "version": "16.9.6", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.6.tgz", - "integrity": "sha512-S6ihtlPMDotrlCJE9ST1fRmYrQNNwfgL61UB4I1W7M6kPulUKx9fXAleW5zpdIjUQ4fTaaog8uERezjsGUj9HQ==", + "version": "16.9.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.7.tgz", + "integrity": "sha512-GHTYhM8/OwUCf254WO5xqR/aqD3gC9kSTLpopWGpQLpnw23jk44RvMHsyUSEplvRJZdHxhJGMMLF0kCPYHPhQA==", "dev": true, "requires": { "@types/react": "*" @@ -2499,9 +2571,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001046", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001046.tgz", - "integrity": "sha512-CsGjBRYWG6FvgbyGy+hBbaezpwiqIOLkxQPY4A4Ea49g1eNsnQuESB+n4QM0BKii1j80MyJ26Ir5ywTQkbRE4g==", + "version": "1.0.30001050", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001050.tgz", + "integrity": "sha512-OvGZqalCwmapci76ISq5q4kuAskb1ebqF3FEQBv1LE1kWht0pojlDDqzFlmk5jgYkuZN7MNZ1n+ULwe/7MaDNQ==", "dev": true }, "caw": { @@ -3737,9 +3809,9 @@ "dev": true }, "electron-to-chromium": { - "version": "1.3.416", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.416.tgz", - "integrity": "sha512-fmSrpOQC1dEXzsznzAMXbhQLkpAr21WtaUfRXnIbh8kblZIaMwSL6A8u2RZHAzZliSoSOM3FzS2z/j8tVqrAAw==", + "version": "1.3.427", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.427.tgz", + "integrity": "sha512-/rG5G7Opcw68/Yrb4qYkz07h3bESVRJjUl4X/FrKLXzoUJleKm6D7K7rTTz8V5LUWnd+BbTOyxJX2XprRqHD8A==", "dev": true }, "elliptic": { @@ -7961,9 +8033,9 @@ "optional": true }, "qs": { - "version": "6.9.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.3.tgz", - "integrity": "sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw==" + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz", + "integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==" }, "query-string": { "version": "5.1.1", diff --git a/browser/package.json b/browser/package.json index 4c6a41bf..3703fd9f 100644 --- a/browser/package.json +++ b/browser/package.json @@ -18,7 +18,7 @@ "dependencies": { "classnames": "^2.2.5", "lodash": "^4.17.15", - "qs": "^6.9.3", + "qs": "^6.9.4", "react": "^16.12.0", "react-dom": "^16.12.0", "react-redux": "^7.1.3", @@ -28,10 +28,10 @@ "redux-thunk": "^2.2.0" }, "devDependencies": { - "@babel/core": "^7.9.0", - "@babel/preset-env": "^7.9.5", + "@babel/core": "^7.9.6", + "@babel/preset-env": "^7.9.6", "@types/react": "^16.9.34", - "@types/react-dom": "^16.9.6", + "@types/react-dom": "^16.9.7", "concurrently": "^5.2.0", "del": "^5.0.0", "gulp": "^4.0.0", diff --git a/go.mod b/go.mod index 086444d2..f79d24f7 100644 --- a/go.mod +++ b/go.mod @@ -23,22 +23,22 @@ require ( github.com/justincampbell/bigduration v0.0.0-20160531141349-e45bf03c0666 // indirect github.com/justincampbell/timeago v0.0.0-20160528003754-027f40306f1d github.com/karrick/godirwalk v1.15.6 // indirect - github.com/lib/pq v1.4.0 + github.com/lib/pq v1.5.0 github.com/mattn/go-colorable v0.1.6 // indirect github.com/mattn/go-sqlite3 v2.0.3+incompatible github.com/pkg/errors v0.9.1 github.com/radovskyb/watcher v1.0.7 github.com/robfig/cron v1.2.0 - github.com/rubenv/sql-migrate v0.0.0-20200423171638-eef9d3b68125 + github.com/rubenv/sql-migrate v0.0.0-20200429072036-ae26b214fa43 github.com/sergi/go-diff v1.1.0 - github.com/sirupsen/logrus v1.5.0 // indirect + github.com/sirupsen/logrus v1.6.0 // indirect github.com/spf13/cobra v1.0.0 github.com/spf13/pflag v1.0.5 // indirect github.com/stripe/stripe-go v70.15.0+incompatible - golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5 - golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0 // indirect + golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79 + golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5 // indirect golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a // indirect - golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f // indirect + golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3 // indirect golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 golang.org/x/tools v0.0.0-20200425043458-8463f397d07c // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect diff --git a/go.sum b/go.sum index bad19a68..5befb8c7 100644 --- a/go.sum +++ b/go.sum @@ -229,6 +229,8 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -240,6 +242,8 @@ github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.4.0 h1:TmtCFbH+Aw0AixwyttznSMQDgbR5Yed/Gg6S8Funrhc= github.com/lib/pq v1.4.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.5.0 h1:Hq6pEflc2Q3hP5iEH3Q6XopXrJXxjhwbvMpj9eZnpp0= +github.com/lib/pq v1.5.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= @@ -363,6 +367,8 @@ github.com/rubenv/sql-migrate v0.0.0-20190618074426-f4d34eae5a5c h1:LCELEbde3/GT github.com/rubenv/sql-migrate v0.0.0-20190618074426-f4d34eae5a5c/go.mod h1:WS0rl9eEliYI8DPnr3TOwz4439pay+qNgzJoVya/DmY= github.com/rubenv/sql-migrate v0.0.0-20200423171638-eef9d3b68125 h1:TNreUp2iVj8BSG7NrBFUeq5UoAGK7fWas/Eb4jlwKlY= github.com/rubenv/sql-migrate v0.0.0-20200423171638-eef9d3b68125/go.mod h1:DCgfY80j8GYL7MLEfvcpSFvjD0L5yZq/aZUJmhZklyg= +github.com/rubenv/sql-migrate v0.0.0-20200429072036-ae26b214fa43 h1:0i6uTtxUGc/jpK/CngM4T2S2NFnqYUUxH+lKDgBLw8U= +github.com/rubenv/sql-migrate v0.0.0-20200429072036-ae26b214fa43/go.mod h1:DCgfY80j8GYL7MLEfvcpSFvjD0L5yZq/aZUJmhZklyg= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= @@ -378,6 +384,8 @@ github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.5.0 h1:1N5EYkVAPEywqZRJd7cwnRtCb6xJx7NH3T3WUTF980Q= github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo= +github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= @@ -452,6 +460,8 @@ golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5 h1:Q7tZBpemrlsc2I7IyODzhtallWRSm4Q0d09pL6XbQtU= golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79 h1:IaQbIIB2X/Mp/DKctl6ROxz1KyMlKp4uyvL6+kQ7C88= +golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -486,6 +496,8 @@ golang.org/x/net v0.0.0-20200421231249-e086a090c8fd h1:QPwSajcTUrFriMF1nJ3Xzgoqa golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0 h1:Jcxah/M+oLZ/R4/z5RzfPzGbPXnVDPkEDtf2JnuxN+U= golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5 h1:WQ8q63x+f/zpC8Ac1s9wLElVoHhm32p6tudrU72n1QA= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -522,6 +534,8 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f h1:gWF768j/LaZugp8dyS4UwsslYCYz9XgFxvlgsn0n9H8= golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3 h1:5B6i6EAiSYyejWfvc5Rc9BbI3rzIsrrXfAQBWnYfn+w= +golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/jslib/package-lock.json b/jslib/package-lock.json index 243e804b..e070a59d 100644 --- a/jslib/package-lock.json +++ b/jslib/package-lock.json @@ -3802,9 +3802,9 @@ "dev": true }, "qs": { - "version": "6.9.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.3.tgz", - "integrity": "sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw==" + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz", + "integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==" }, "react-is": { "version": "16.11.0", @@ -4886,9 +4886,9 @@ } }, "yargs-parser": { - "version": "13.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz", - "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", "dev": true, "requires": { "camelcase": "^5.0.0", diff --git a/jslib/package.json b/jslib/package.json index 5bad7983..22b372d1 100644 --- a/jslib/package.json +++ b/jslib/package.json @@ -18,7 +18,7 @@ "@types/history": "^4.7.5", "history": "^4.10.1", "lodash": "^4.17.15", - "qs": "^6.9.3" + "qs": "^6.9.4" }, "devDependencies": { "@types/jest": "^24.9.1", diff --git a/package-lock.json b/package-lock.json index b1fde446..fc98b3dd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -200,25 +200,25 @@ "dev": true }, "@typescript-eslint/eslint-plugin": { - "version": "2.29.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.29.0.tgz", - "integrity": "sha512-X/YAY7azKirENm4QRpT7OVmzok02cSkqeIcLmdz6gXUQG4Hk0Fi9oBAynSAyNXeGdMRuZvjBa0c1Lu0dn/u6VA==", + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.30.0.tgz", + "integrity": "sha512-PGejii0qIZ9Q40RB2jIHyUpRWs1GJuHP1pkoCiaeicfwO9z7Fx03NQzupuyzAmv+q9/gFNHu7lo1ByMXe8PNyg==", "dev": true, "requires": { - "@typescript-eslint/experimental-utils": "2.29.0", + "@typescript-eslint/experimental-utils": "2.30.0", "functional-red-black-tree": "^1.0.1", "regexpp": "^3.0.0", "tsutils": "^3.17.1" } }, "@typescript-eslint/experimental-utils": { - "version": "2.29.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.29.0.tgz", - "integrity": "sha512-H/6VJr6eWYstyqjWXBP2Nn1hQJyvJoFdDtsHxGiD+lEP7piGnGpb/ZQd+z1ZSB1F7dN+WsxUDh8+S4LwI+f3jw==", + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.30.0.tgz", + "integrity": "sha512-L3/tS9t+hAHksy8xuorhOzhdefN0ERPDWmR9CclsIGOUqGKy6tqc/P+SoXeJRye5gazkuPO0cK9MQRnolykzkA==", "dev": true, "requires": { "@types/json-schema": "^7.0.3", - "@typescript-eslint/typescript-estree": "2.29.0", + "@typescript-eslint/typescript-estree": "2.30.0", "eslint-scope": "^5.0.0", "eslint-utils": "^2.0.0" }, @@ -235,21 +235,21 @@ } }, "@typescript-eslint/parser": { - "version": "2.29.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-2.29.0.tgz", - "integrity": "sha512-H78M+jcu5Tf6m/5N8iiFblUUv+HJDguMSdFfzwa6vSg9lKR8Mk9BsgeSjO8l2EshKnJKcbv0e8IDDOvSNjl0EA==", + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-2.30.0.tgz", + "integrity": "sha512-9kDOxzp0K85UnpmPJqUzdWaCNorYYgk1yZmf4IKzpeTlSAclnFsrLjfwD9mQExctLoLoGAUXq1co+fbr+3HeFw==", "dev": true, "requires": { "@types/eslint-visitor-keys": "^1.0.0", - "@typescript-eslint/experimental-utils": "2.29.0", - "@typescript-eslint/typescript-estree": "2.29.0", + "@typescript-eslint/experimental-utils": "2.30.0", + "@typescript-eslint/typescript-estree": "2.30.0", "eslint-visitor-keys": "^1.1.0" } }, "@typescript-eslint/typescript-estree": { - "version": "2.29.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.29.0.tgz", - "integrity": "sha512-3YGbtnWy4az16Egy5Fj5CckkVlpIh0MADtAQza+jiMADRSKkjdpzZp/5WuvwK/Qib3Z0HtzrDFeWanS99dNhnA==", + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.30.0.tgz", + "integrity": "sha512-nI5WOechrA0qAhnr+DzqwmqHsx7Ulr/+0H7bWCcClDhhWkSyZR5BmTvnBEyONwJCTWHfc5PAQExX24VD26IAVw==", "dev": true, "requires": { "debug": "^4.1.1", diff --git a/package.json b/package.json index 5b16a514..e9edbf21 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,8 @@ "description": "Dnote monorepo", "license": "SEE LICENSE IN LICENSE", "devDependencies": { - "@typescript-eslint/eslint-plugin": "^2.29.0", - "@typescript-eslint/parser": "^2.29.0", + "@typescript-eslint/eslint-plugin": "^2.30.0", + "@typescript-eslint/parser": "^2.30.0", "babel-eslint": "^10.1.0", "eslint": "^6.8.0", "eslint-config-airbnb": "^18.1.0", diff --git a/web/package-lock.json b/web/package-lock.json index c853b243..12aabbc2 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -13,30 +13,30 @@ } }, "@babel/compat-data": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.9.0.tgz", - "integrity": "sha512-zeFQrr+284Ekvd9e7KAX954LkapWiOmQtsfHirhxqfdlX6MEC32iRE+pqUGlYIBchdevaCwvzxWGSy/YBNI85g==", + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.9.6.tgz", + "integrity": "sha512-5QPTrNen2bm7RBc7dsOmcA5hbrS4O2Vhmk5XOL4zWW/zD/hV0iinpefDlkm+tBBy8kDtFaaeEvmAqt+nURAV2g==", "dev": true, "requires": { - "browserslist": "^4.9.1", + "browserslist": "^4.11.1", "invariant": "^2.2.4", "semver": "^5.5.0" } }, "@babel/core": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.9.0.tgz", - "integrity": "sha512-kWc7L0fw1xwvI0zi8OKVBuxRVefwGOrKSQMvrQ3dW+bIIavBY3/NpXmpjMy7bQnLgwgzWQZ8TlM57YHpHNHz4w==", + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.9.6.tgz", + "integrity": "sha512-nD3deLvbsApbHAHttzIssYqgb883yU/d9roe4RZymBCDaZryMJDbptVpEpeQuRh4BJ+SYI8le9YGxKvFEvl1Wg==", "dev": true, "requires": { "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.9.0", + "@babel/generator": "^7.9.6", "@babel/helper-module-transforms": "^7.9.0", - "@babel/helpers": "^7.9.0", - "@babel/parser": "^7.9.0", + "@babel/helpers": "^7.9.6", + "@babel/parser": "^7.9.6", "@babel/template": "^7.8.6", - "@babel/traverse": "^7.9.0", - "@babel/types": "^7.9.0", + "@babel/traverse": "^7.9.6", + "@babel/types": "^7.9.6", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.1", @@ -47,6 +47,52 @@ "source-map": "^0.5.0" }, "dependencies": { + "@babel/generator": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.6.tgz", + "integrity": "sha512-+htwWKJbH2bL72HRluF8zumBxzuX0ZZUFl3JLNyoUjM/Ho8wnVpPXM6aUz8cfKDqQ/h7zHqKt4xzJteUosckqQ==", + "dev": true, + "requires": { + "@babel/types": "^7.9.6", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0" + } + }, + "@babel/parser": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.6.tgz", + "integrity": "sha512-AoeIEJn8vt+d/6+PXDRPaksYhnlbMIiejioBZvvMQsOjW/JYK6k/0dKnvvP3EhK5GfMBWDPtrxRtegWdAcdq9Q==", + "dev": true + }, + "@babel/traverse": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.6.tgz", + "integrity": "sha512-b3rAHSjbxy6VEAvlxM8OV/0X4XrG72zoxme6q1MOoe2vd0bEc+TwayhuC1+Dfgqh1QEG+pj7atQqvUprHIccsg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/generator": "^7.9.6", + "@babel/helper-function-name": "^7.9.5", + "@babel/helper-split-export-declaration": "^7.8.3", + "@babel/parser": "^7.9.6", + "@babel/types": "^7.9.6", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" + } + }, + "@babel/types": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.6.tgz", + "integrity": "sha512-qxXzvBO//jO9ZnoasKF1uJzHd2+M6Q2ZPIVfnFps8JJvXy0ZBbwbNOmE6SGIY5XOY6d1Bo5lb9d9RJ8nv3WSeA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.9.5", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, "json5": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", @@ -110,13 +156,13 @@ } }, "@babel/helper-compilation-targets": { - "version": "7.8.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.8.7.tgz", - "integrity": "sha512-4mWm8DCK2LugIS+p1yArqvG1Pf162upsIsjE7cNBjez+NjliQpVhj20obE520nao0o14DaTnFJv+Fw5a0JpoUw==", + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.9.6.tgz", + "integrity": "sha512-x2Nvu0igO0ejXzx09B/1fGBxY9NXQlBW2kZsSxCJft+KHN8t9XWzIvFxtPHnBOAXpVsdxZKZFbRUC8TsNKajMw==", "dev": true, "requires": { - "@babel/compat-data": "^7.8.6", - "browserslist": "^4.9.1", + "@babel/compat-data": "^7.9.6", + "browserslist": "^4.11.1", "invariant": "^2.2.4", "levenary": "^1.1.1", "semver": "^5.5.0" @@ -308,14 +354,62 @@ } }, "@babel/helpers": { - "version": "7.9.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.9.2.tgz", - "integrity": "sha512-JwLvzlXVPjO8eU9c/wF9/zOIN7X6h8DYf7mG4CiFRZRvZNKEF5dQ3H3V+ASkHoIB3mWhatgl5ONhyqHRI6MppA==", + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.9.6.tgz", + "integrity": "sha512-tI4bUbldloLcHWoRUMAj4g1bF313M/o6fBKhIsb3QnGVPwRm9JsNf/gqMkQ7zjqReABiffPV6RWj7hEglID5Iw==", "dev": true, "requires": { "@babel/template": "^7.8.3", - "@babel/traverse": "^7.9.0", - "@babel/types": "^7.9.0" + "@babel/traverse": "^7.9.6", + "@babel/types": "^7.9.6" + }, + "dependencies": { + "@babel/generator": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.6.tgz", + "integrity": "sha512-+htwWKJbH2bL72HRluF8zumBxzuX0ZZUFl3JLNyoUjM/Ho8wnVpPXM6aUz8cfKDqQ/h7zHqKt4xzJteUosckqQ==", + "dev": true, + "requires": { + "@babel/types": "^7.9.6", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0" + } + }, + "@babel/parser": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.6.tgz", + "integrity": "sha512-AoeIEJn8vt+d/6+PXDRPaksYhnlbMIiejioBZvvMQsOjW/JYK6k/0dKnvvP3EhK5GfMBWDPtrxRtegWdAcdq9Q==", + "dev": true + }, + "@babel/traverse": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.6.tgz", + "integrity": "sha512-b3rAHSjbxy6VEAvlxM8OV/0X4XrG72zoxme6q1MOoe2vd0bEc+TwayhuC1+Dfgqh1QEG+pj7atQqvUprHIccsg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/generator": "^7.9.6", + "@babel/helper-function-name": "^7.9.5", + "@babel/helper-split-export-declaration": "^7.8.3", + "@babel/parser": "^7.9.6", + "@babel/types": "^7.9.6", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" + } + }, + "@babel/types": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.6.tgz", + "integrity": "sha512-qxXzvBO//jO9ZnoasKF1uJzHd2+M6Q2ZPIVfnFps8JJvXy0ZBbwbNOmE6SGIY5XOY6d1Bo5lb9d9RJ8nv3WSeA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.9.5", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/highlight": { @@ -394,9 +488,9 @@ } }, "@babel/plugin-proposal-object-rest-spread": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.9.5.tgz", - "integrity": "sha512-VP2oXvAf7KCYTthbUHwBlewbl1Iq059f6seJGsxMizaCdgHIeczOr7FBqELhSqfkIl04Fi8okzWzl63UKbQmmg==", + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.9.6.tgz", + "integrity": "sha512-Ga6/fhGqA9Hj+y6whNpPv8psyaK5xzrQwSPsGPloVkvmH+PqW1ixdnfJ9uIO06OjQNYol3PMnfmJ8vfZtkzF+A==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.8.3", @@ -700,38 +794,38 @@ } }, "@babel/plugin-transform-modules-amd": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.9.0.tgz", - "integrity": "sha512-vZgDDF003B14O8zJy0XXLnPH4sg+9X5hFBBGN1V+B2rgrB+J2xIypSN6Rk9imB2hSTHQi5OHLrFWsZab1GMk+Q==", + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.9.6.tgz", + "integrity": "sha512-zoT0kgC3EixAyIAU+9vfaUVKTv9IxBDSabgHoUCBP6FqEJ+iNiN7ip7NBKcYqbfUDfuC2mFCbM7vbu4qJgOnDw==", "dev": true, "requires": { "@babel/helper-module-transforms": "^7.9.0", "@babel/helper-plugin-utils": "^7.8.3", - "babel-plugin-dynamic-import-node": "^2.3.0" + "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-modules-commonjs": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.9.0.tgz", - "integrity": "sha512-qzlCrLnKqio4SlgJ6FMMLBe4bySNis8DFn1VkGmOcxG9gqEyPIOzeQrA//u0HAKrWpJlpZbZMPB1n/OPa4+n8g==", + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.9.6.tgz", + "integrity": "sha512-7H25fSlLcn+iYimmsNe3uK1at79IE6SKW9q0/QeEHTMC9MdOZ+4bA+T1VFB5fgOqBWoqlifXRzYD0JPdmIrgSQ==", "dev": true, "requires": { "@babel/helper-module-transforms": "^7.9.0", "@babel/helper-plugin-utils": "^7.8.3", "@babel/helper-simple-access": "^7.8.3", - "babel-plugin-dynamic-import-node": "^2.3.0" + "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-modules-systemjs": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.9.0.tgz", - "integrity": "sha512-FsiAv/nao/ud2ZWy4wFacoLOm5uxl0ExSQ7ErvP7jpoihLR6Cq90ilOFyX9UXct3rbtKsAiZ9kFt5XGfPe/5SQ==", + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.9.6.tgz", + "integrity": "sha512-NW5XQuW3N2tTHim8e1b7qGy7s0kZ2OH3m5octc49K1SdAKGxYxeIx7hiIz05kS1R2R+hOWcsr1eYwcGhrdHsrg==", "dev": true, "requires": { "@babel/helper-hoist-variables": "^7.8.3", "@babel/helper-module-transforms": "^7.9.0", "@babel/helper-plugin-utils": "^7.8.3", - "babel-plugin-dynamic-import-node": "^2.3.0" + "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-modules-umd": { @@ -939,13 +1033,13 @@ } }, "@babel/preset-env": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.9.5.tgz", - "integrity": "sha512-eWGYeADTlPJH+wq1F0wNfPbVS1w1wtmMJiYk55Td5Yu28AsdR9AsC97sZ0Qq8fHqQuslVSIYSGJMcblr345GfQ==", + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.9.6.tgz", + "integrity": "sha512-0gQJ9RTzO0heXOhzftog+a/WyOuqMrAIugVYxMYf83gh1CQaQDjMtsOpqOwXyDL/5JcWsrCm8l4ju8QC97O7EQ==", "dev": true, "requires": { - "@babel/compat-data": "^7.9.0", - "@babel/helper-compilation-targets": "^7.8.7", + "@babel/compat-data": "^7.9.6", + "@babel/helper-compilation-targets": "^7.9.6", "@babel/helper-module-imports": "^7.8.3", "@babel/helper-plugin-utils": "^7.8.3", "@babel/plugin-proposal-async-generator-functions": "^7.8.3", @@ -953,7 +1047,7 @@ "@babel/plugin-proposal-json-strings": "^7.8.3", "@babel/plugin-proposal-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-proposal-numeric-separator": "^7.8.3", - "@babel/plugin-proposal-object-rest-spread": "^7.9.5", + "@babel/plugin-proposal-object-rest-spread": "^7.9.6", "@babel/plugin-proposal-optional-catch-binding": "^7.8.3", "@babel/plugin-proposal-optional-chaining": "^7.9.0", "@babel/plugin-proposal-unicode-property-regex": "^7.8.3", @@ -980,9 +1074,9 @@ "@babel/plugin-transform-function-name": "^7.8.3", "@babel/plugin-transform-literals": "^7.8.3", "@babel/plugin-transform-member-expression-literals": "^7.8.3", - "@babel/plugin-transform-modules-amd": "^7.9.0", - "@babel/plugin-transform-modules-commonjs": "^7.9.0", - "@babel/plugin-transform-modules-systemjs": "^7.9.0", + "@babel/plugin-transform-modules-amd": "^7.9.6", + "@babel/plugin-transform-modules-commonjs": "^7.9.6", + "@babel/plugin-transform-modules-systemjs": "^7.9.6", "@babel/plugin-transform-modules-umd": "^7.9.0", "@babel/plugin-transform-named-capturing-groups-regex": "^7.8.3", "@babel/plugin-transform-new-target": "^7.8.3", @@ -998,12 +1092,25 @@ "@babel/plugin-transform-typeof-symbol": "^7.8.4", "@babel/plugin-transform-unicode-regex": "^7.8.3", "@babel/preset-modules": "^0.1.3", - "@babel/types": "^7.9.5", - "browserslist": "^4.9.1", + "@babel/types": "^7.9.6", + "browserslist": "^4.11.1", "core-js-compat": "^3.6.2", "invariant": "^2.2.2", "levenary": "^1.1.1", "semver": "^5.5.0" + }, + "dependencies": { + "@babel/types": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.6.tgz", + "integrity": "sha512-qxXzvBO//jO9ZnoasKF1uJzHd2+M6Q2ZPIVfnFps8JJvXy0ZBbwbNOmE6SGIY5XOY6d1Bo5lb9d9RJ8nv3WSeA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.9.5", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/preset-modules": { @@ -1189,18 +1296,30 @@ "dev": true }, "@jest/console": { - "version": "25.4.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-25.4.0.tgz", - "integrity": "sha512-CfE0erx4hdJ6t7RzAcE1wLG6ZzsHSmybvIBQDoCkDM1QaSeWL9wJMzID/2BbHHa7ll9SsbbK43HjbERbBaFX2A==", + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-25.5.0.tgz", + "integrity": "sha512-T48kZa6MK1Y6k4b89sexwmSF4YLeZS/Udqg3Jj3jG/cHH+N/sLFCEoXEDMOKugJQ9FxPN1osxIknvKkxt6MKyw==", "dev": true, "requires": { - "@jest/types": "^25.4.0", + "@jest/types": "^25.5.0", "chalk": "^3.0.0", - "jest-message-util": "^25.4.0", - "jest-util": "^25.4.0", + "jest-message-util": "^25.5.0", + "jest-util": "^25.5.0", "slash": "^3.0.0" }, "dependencies": { + "@jest/types": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0" + } + }, "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -1254,33 +1373,33 @@ } }, "@jest/core": { - "version": "25.4.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-25.4.0.tgz", - "integrity": "sha512-h1x9WSVV0+TKVtATGjyQIMJENs8aF6eUjnCoi4jyRemYZmekLr8EJOGQqTWEX8W6SbZ6Skesy9pGXrKeAolUJw==", + "version": "25.5.4", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-25.5.4.tgz", + "integrity": "sha512-3uSo7laYxF00Dg/DMgbn4xMJKmDdWvZnf89n8Xj/5/AeQ2dOQmn6b6Hkj/MleyzZWXpwv+WSdYWl4cLsy2JsoA==", "dev": true, "requires": { - "@jest/console": "^25.4.0", - "@jest/reporters": "^25.4.0", - "@jest/test-result": "^25.4.0", - "@jest/transform": "^25.4.0", - "@jest/types": "^25.4.0", + "@jest/console": "^25.5.0", + "@jest/reporters": "^25.5.1", + "@jest/test-result": "^25.5.0", + "@jest/transform": "^25.5.1", + "@jest/types": "^25.5.0", "ansi-escapes": "^4.2.1", "chalk": "^3.0.0", "exit": "^0.1.2", - "graceful-fs": "^4.2.3", - "jest-changed-files": "^25.4.0", - "jest-config": "^25.4.0", - "jest-haste-map": "^25.4.0", - "jest-message-util": "^25.4.0", + "graceful-fs": "^4.2.4", + "jest-changed-files": "^25.5.0", + "jest-config": "^25.5.4", + "jest-haste-map": "^25.5.1", + "jest-message-util": "^25.5.0", "jest-regex-util": "^25.2.6", - "jest-resolve": "^25.4.0", - "jest-resolve-dependencies": "^25.4.0", - "jest-runner": "^25.4.0", - "jest-runtime": "^25.4.0", - "jest-snapshot": "^25.4.0", - "jest-util": "^25.4.0", - "jest-validate": "^25.4.0", - "jest-watcher": "^25.4.0", + "jest-resolve": "^25.5.1", + "jest-resolve-dependencies": "^25.5.4", + "jest-runner": "^25.5.4", + "jest-runtime": "^25.5.4", + "jest-snapshot": "^25.5.1", + "jest-util": "^25.5.0", + "jest-validate": "^25.5.0", + "jest-watcher": "^25.5.0", "micromatch": "^4.0.2", "p-each-series": "^2.1.0", "realpath-native": "^2.0.0", @@ -1289,6 +1408,18 @@ "strip-ansi": "^6.0.0" }, "dependencies": { + "@jest/types": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0" + } + }, "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -1342,6 +1473,12 @@ "to-regex-range": "^5.0.1" } }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -1394,61 +1531,28 @@ } }, "@jest/environment": { - "version": "25.4.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-25.4.0.tgz", - "integrity": "sha512-KDctiak4mu7b4J6BIoN/+LUL3pscBzoUCP+EtSPd2tK9fqyDY5OF+CmkBywkFWezS9tyH5ACOQNtpjtueEDH6Q==", + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-25.5.0.tgz", + "integrity": "sha512-U2VXPEqL07E/V7pSZMSQCvV5Ea4lqOlT+0ZFijl/i316cRMHvZ4qC+jBdryd+lmRetjQo0YIQr6cVPNxxK87mA==", "dev": true, "requires": { - "@jest/fake-timers": "^25.4.0", - "@jest/types": "^25.4.0", - "jest-mock": "^25.4.0" - } - }, - "@jest/fake-timers": { - "version": "25.4.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-25.4.0.tgz", - "integrity": "sha512-lI9z+VOmVX4dPPFzyj0vm+UtaB8dCJJ852lcDnY0uCPRvZAaVGnMwBBc1wxtf+h7Vz6KszoOvKAt4QijDnHDkg==", - "dev": true, - "requires": { - "@jest/types": "^25.4.0", - "jest-message-util": "^25.4.0", - "jest-mock": "^25.4.0", - "jest-util": "^25.4.0", - "lolex": "^5.0.0" - } - }, - "@jest/reporters": { - "version": "25.4.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-25.4.0.tgz", - "integrity": "sha512-bhx/buYbZgLZm4JWLcRJ/q9Gvmd3oUh7k2V7gA4ZYBx6J28pIuykIouclRdiAC6eGVX1uRZT+GK4CQJLd/PwPg==", - "dev": true, - "requires": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^25.4.0", - "@jest/test-result": "^25.4.0", - "@jest/transform": "^25.4.0", - "@jest/types": "^25.4.0", - "chalk": "^3.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.2", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^4.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.2", - "jest-haste-map": "^25.4.0", - "jest-resolve": "^25.4.0", - "jest-util": "^25.4.0", - "jest-worker": "^25.4.0", - "node-notifier": "^6.0.0", - "slash": "^3.0.0", - "source-map": "^0.6.0", - "string-length": "^3.1.0", - "terminal-link": "^2.0.0", - "v8-to-istanbul": "^4.1.3" + "@jest/fake-timers": "^25.5.0", + "@jest/types": "^25.5.0", + "jest-mock": "^25.5.0" }, "dependencies": { + "@jest/types": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0" + } + }, "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -1490,6 +1594,261 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@jest/fake-timers": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-25.5.0.tgz", + "integrity": "sha512-9y2+uGnESw/oyOI3eww9yaxdZyHq7XvprfP/eeoCsjqKYts2yRlsHS/SgjPDV8FyMfn2nbMy8YzUk6nyvdLOpQ==", + "dev": true, + "requires": { + "@jest/types": "^25.5.0", + "jest-message-util": "^25.5.0", + "jest-mock": "^25.5.0", + "jest-util": "^25.5.0", + "lolex": "^5.0.0" + }, + "dependencies": { + "@jest/types": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0" + } + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@jest/globals": { + "version": "25.5.2", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-25.5.2.tgz", + "integrity": "sha512-AgAS/Ny7Q2RCIj5kZ+0MuKM1wbF0WMLxbCVl/GOMoCNbODRdJ541IxJ98xnZdVSZXivKpJlNPIWa3QmY0l4CXA==", + "dev": true, + "requires": { + "@jest/environment": "^25.5.0", + "@jest/types": "^25.5.0", + "expect": "^25.5.0" + }, + "dependencies": { + "@jest/types": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0" + } + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@jest/reporters": { + "version": "25.5.1", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-25.5.1.tgz", + "integrity": "sha512-3jbd8pPDTuhYJ7vqiHXbSwTJQNavczPs+f1kRprRDxETeE3u6srJ+f0NPuwvOmk+lmunZzPkYWIFZDLHQPkviw==", + "dev": true, + "requires": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^25.5.0", + "@jest/test-result": "^25.5.0", + "@jest/transform": "^25.5.1", + "@jest/types": "^25.5.0", + "chalk": "^3.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.2", + "graceful-fs": "^4.2.4", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "jest-haste-map": "^25.5.1", + "jest-resolve": "^25.5.1", + "jest-util": "^25.5.0", + "jest-worker": "^25.5.0", + "node-notifier": "^6.0.0", + "slash": "^3.0.0", + "source-map": "^0.6.0", + "string-length": "^3.1.0", + "terminal-link": "^2.0.0", + "v8-to-istanbul": "^4.1.3" + }, + "dependencies": { + "@jest/types": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0" + } + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -1508,13 +1867,13 @@ } }, "@jest/source-map": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-25.2.6.tgz", - "integrity": "sha512-VuIRZF8M2zxYFGTEhkNSvQkUKafQro4y+mwUxy5ewRqs5N/ynSFUODYp3fy1zCnbCMy1pz3k+u57uCqx8QRSQQ==", + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-25.5.0.tgz", + "integrity": "sha512-eIGx0xN12yVpMcPaVpjXPnn3N30QGJCJQSkEDUt9x1fI1Gdvb07Ml6K5iN2hG7NmMP6FDmtPEssE3z6doOYUwQ==", "dev": true, "requires": { "callsites": "^3.0.0", - "graceful-fs": "^4.2.3", + "graceful-fs": "^4.2.4", "source-map": "^0.6.0" }, "dependencies": { @@ -1524,6 +1883,12 @@ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -1533,45 +1898,118 @@ } }, "@jest/test-result": { - "version": "25.4.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-25.4.0.tgz", - "integrity": "sha512-8BAKPaMCHlL941eyfqhWbmp3MebtzywlxzV+qtngQ3FH+RBqnoSAhNEPj4MG7d2NVUrMOVfrwuzGpVIK+QnMAA==", + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-25.5.0.tgz", + "integrity": "sha512-oV+hPJgXN7IQf/fHWkcS99y0smKLU2czLBJ9WA0jHITLst58HpQMtzSYxzaBvYc6U5U6jfoMthqsUlUlbRXs0A==", "dev": true, "requires": { - "@jest/console": "^25.4.0", - "@jest/types": "^25.4.0", + "@jest/console": "^25.5.0", + "@jest/types": "^25.5.0", "@types/istanbul-lib-coverage": "^2.0.0", "collect-v8-coverage": "^1.0.0" + }, + "dependencies": { + "@jest/types": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0" + } + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, "@jest/test-sequencer": { - "version": "25.4.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-25.4.0.tgz", - "integrity": "sha512-240cI+nsM3attx2bMp9uGjjHrwrpvxxrZi8Tyqp/cfOzl98oZXVakXBgxODGyBYAy/UGXPKXLvNc2GaqItrsJg==", + "version": "25.5.4", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-25.5.4.tgz", + "integrity": "sha512-pTJGEkSeg1EkCO2YWq6hbFvKNXk8ejqlxiOg1jBNLnWrgXOkdY6UmqZpwGFXNnRt9B8nO1uWMzLLZ4eCmhkPNA==", "dev": true, "requires": { - "@jest/test-result": "^25.4.0", - "jest-haste-map": "^25.4.0", - "jest-runner": "^25.4.0", - "jest-runtime": "^25.4.0" + "@jest/test-result": "^25.5.0", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^25.5.1", + "jest-runner": "^25.5.4", + "jest-runtime": "^25.5.4" + }, + "dependencies": { + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + } } }, "@jest/transform": { - "version": "25.4.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-25.4.0.tgz", - "integrity": "sha512-t1w2S6V1sk++1HHsxboWxPEuSpN8pxEvNrZN+Ud/knkROWtf8LeUmz73A4ezE8476a5AM00IZr9a8FO9x1+j3g==", + "version": "25.5.1", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-25.5.1.tgz", + "integrity": "sha512-Y8CEoVwXb4QwA6Y/9uDkn0Xfz0finGkieuV0xkdF9UtZGJeLukD5nLkaVrVsODB1ojRWlaoD0AJZpVHCSnJEvg==", "dev": true, "requires": { "@babel/core": "^7.1.0", - "@jest/types": "^25.4.0", + "@jest/types": "^25.5.0", "babel-plugin-istanbul": "^6.0.0", "chalk": "^3.0.0", "convert-source-map": "^1.4.0", "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.3", - "jest-haste-map": "^25.4.0", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^25.5.1", "jest-regex-util": "^25.2.6", - "jest-util": "^25.4.0", + "jest-util": "^25.5.0", "micromatch": "^4.0.2", "pirates": "^4.0.1", "realpath-native": "^2.0.0", @@ -1580,6 +2018,18 @@ "write-file-atomic": "^3.0.0" }, "dependencies": { + "@jest/types": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0" + } + }, "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -1633,6 +2083,12 @@ "to-regex-range": "^5.0.1" } }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -1787,9 +2243,9 @@ } }, "@types/babel__traverse": { - "version": "7.0.10", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.10.tgz", - "integrity": "sha512-74fNdUGrWsgIB/V9kTO5FGHPWYY6Eqn+3Z7L6Hc4e/BxjYV7puvBqp5HwsVYYfLm6iURYBNCx4Ut37OF9yitCw==", + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.11.tgz", + "integrity": "sha512-ddHK5icION5U6q11+tV2f9Mo6CZVuT8GJKld2q9LqHSZbvLbH34Kcu2yFGckZut453+eQU6btIA3RihmnRgI+Q==", "dev": true, "requires": { "@babel/types": "^7.3.0" @@ -1823,6 +2279,15 @@ "@types/node": "*" } }, + "@types/graceful-fs": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.3.tgz", + "integrity": "sha512-AiHRaEB50LQg0pZmm659vNBb9f4SJ0qrAnteuzhSeAUcJKxoYgEnprg/83kppCnc2zvtCKbdZry1a5pVY3lOTQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/history": { "version": "4.7.5", "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.5.tgz", @@ -1917,9 +2382,9 @@ } }, "@types/react-dom": { - "version": "16.9.6", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.6.tgz", - "integrity": "sha512-S6ihtlPMDotrlCJE9ST1fRmYrQNNwfgL61UB4I1W7M6kPulUKx9fXAleW5zpdIjUQ4fTaaog8uERezjsGUj9HQ==", + "version": "16.9.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.7.tgz", + "integrity": "sha512-GHTYhM8/OwUCf254WO5xqR/aqD3gC9kSTLpopWGpQLpnw23jk44RvMHsyUSEplvRJZdHxhJGMMLF0kCPYHPhQA==", "dev": true, "requires": { "@types/react": "*" @@ -2482,20 +2947,33 @@ "dev": true }, "babel-jest": { - "version": "25.4.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-25.4.0.tgz", - "integrity": "sha512-p+epx4K0ypmHuCnd8BapfyOwWwosNCYhedetQey1awddtfmEX0MmdxctGl956uwUmjwXR5VSS5xJcGX9DvdIog==", + "version": "25.5.1", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-25.5.1.tgz", + "integrity": "sha512-9dA9+GmMjIzgPnYtkhBg73gOo/RHqPmLruP3BaGL4KEX3Dwz6pI8auSN8G8+iuEG90+GSswyKvslN+JYSaacaQ==", "dev": true, "requires": { - "@jest/transform": "^25.4.0", - "@jest/types": "^25.4.0", + "@jest/transform": "^25.5.1", + "@jest/types": "^25.5.0", "@types/babel__core": "^7.1.7", "babel-plugin-istanbul": "^6.0.0", - "babel-preset-jest": "^25.4.0", + "babel-preset-jest": "^25.5.0", "chalk": "^3.0.0", + "graceful-fs": "^4.2.4", "slash": "^3.0.0" }, "dependencies": { + "@jest/types": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0" + } + }, "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -2531,6 +3009,12 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -2596,11 +3080,13 @@ } }, "babel-plugin-jest-hoist": { - "version": "25.4.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-25.4.0.tgz", - "integrity": "sha512-M3a10JCtTyKevb0MjuH6tU+cP/NVQZ82QPADqI1RQYY1OphztsCeIeQmTsHmF/NS6m0E51Zl4QNsI3odXSQF5w==", + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-25.5.0.tgz", + "integrity": "sha512-u+/W+WAjMlvoocYGTwthAiQSxDcJAyHpQ6oWlHdFZaaN+Rlk8Q7iiwDPg2lN/FyJtAYnKjFxbn7xus4HCFkg5g==", "dev": true, "requires": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", "@types/babel__traverse": "^7.0.6" } }, @@ -2623,12 +3109,12 @@ } }, "babel-preset-jest": { - "version": "25.4.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-25.4.0.tgz", - "integrity": "sha512-PwFiEWflHdu3JCeTr0Pb9NcHHE34qWFnPQRVPvqQITx4CsDCzs6o05923I10XvLvn9nNsRHuiVgB72wG/90ZHQ==", + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-25.5.0.tgz", + "integrity": "sha512-8ZczygctQkBU+63DtSOKGh7tFL0CeCuz+1ieud9lJ1WPQ9O6A1a/r+LGn6Y705PA6whHQ3T1XuB/PmpfNYf8Fw==", "dev": true, "requires": { - "babel-plugin-jest-hoist": "^25.4.0", + "babel-plugin-jest-hoist": "^25.5.0", "babel-preset-current-node-syntax": "^0.1.2" } }, @@ -3803,9 +4289,9 @@ "dev": true }, "cssstyle": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.2.0.tgz", - "integrity": "sha512-sEb3XFPx3jNnCAMtqrXPDeSgQr+jojtCeNf8cvMNMh1cG970+lljssvQDzPq6lmmJu2Vhqood/gtEomBiHOGnA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", "dev": true, "requires": { "cssom": "~0.3.6" @@ -4463,19 +4949,31 @@ } }, "expect": { - "version": "25.4.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-25.4.0.tgz", - "integrity": "sha512-7BDIX99BTi12/sNGJXA9KMRcby4iAmu1xccBOhyKCyEhjcVKS3hPmHdA/4nSI9QGIOkUropKqr3vv7WMDM5lvQ==", + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-25.5.0.tgz", + "integrity": "sha512-w7KAXo0+6qqZZhovCaBVPSIqQp7/UTcx4M9uKt2m6pd2VB1voyC8JizLRqeEqud3AAVP02g+hbErDu5gu64tlA==", "dev": true, "requires": { - "@jest/types": "^25.4.0", + "@jest/types": "^25.5.0", "ansi-styles": "^4.0.0", "jest-get-type": "^25.2.6", - "jest-matcher-utils": "^25.4.0", - "jest-message-util": "^25.4.0", + "jest-matcher-utils": "^25.5.0", + "jest-message-util": "^25.5.0", "jest-regex-util": "^25.2.6" }, "dependencies": { + "@jest/types": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0" + } + }, "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -4486,6 +4984,16 @@ "color-convert": "^2.0.1" } }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -4500,6 +5008,21 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } } } }, @@ -5816,9 +6339,9 @@ "dev": true }, "highlight.js": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.0.1.tgz", - "integrity": "sha512-l1HB5S9nmBuvurFIOPbpeJv4psKh2MyKCTOYRK/E6dwRXkbG96PLH7amP/xpGNyZOK8OWqv45DxLS/ZAIb3n9w==" + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.0.2.tgz", + "integrity": "sha512-2gMT2MHU6/2OjAlnaOE2LFdr9dwviDN3Q2lSw7Ois3/5uTtahbgYTkr4EPoY828ps+2eQWiixPTF8+phU6Ofkg==" }, "history": { "version": "4.10.1", @@ -6331,6 +6854,13 @@ "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", "dev": true }, + "is-docker": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.0.0.tgz", + "integrity": "sha512-pJEdRugimx4fBMra5z2/5iRdZ63OhYV0vr0Dwm5+xtW4D1FvRkB8hamMIhnWfyJeDdyr/aa7BDyNbtG38VxgoQ==", + "dev": true, + "optional": true + }, "is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", @@ -6614,16 +7144,28 @@ } }, "jest": { - "version": "25.4.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-25.4.0.tgz", - "integrity": "sha512-XWipOheGB4wai5JfCYXd6vwsWNwM/dirjRoZgAa7H2wd8ODWbli2AiKjqG8AYhyx+8+5FBEdpO92VhGlBydzbw==", + "version": "25.5.4", + "resolved": "https://registry.npmjs.org/jest/-/jest-25.5.4.tgz", + "integrity": "sha512-hHFJROBTqZahnO+X+PMtT6G2/ztqAZJveGqz//FnWWHurizkD05PQGzRZOhF3XP6z7SJmL+5tCfW8qV06JypwQ==", "dev": true, "requires": { - "@jest/core": "^25.4.0", + "@jest/core": "^25.5.4", "import-local": "^3.0.2", - "jest-cli": "^25.4.0" + "jest-cli": "^25.5.4" }, "dependencies": { + "@jest/types": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0" + } + }, "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -6659,6 +7201,12 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -6666,21 +7214,22 @@ "dev": true }, "jest-cli": { - "version": "25.4.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-25.4.0.tgz", - "integrity": "sha512-usyrj1lzCJZMRN1r3QEdnn8e6E6yCx/QN7+B1sLoA68V7f3WlsxSSQfy0+BAwRiF4Hz2eHauf11GZG3PIfWTXQ==", + "version": "25.5.4", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-25.5.4.tgz", + "integrity": "sha512-rG8uJkIiOUpnREh1768/N3n27Cm+xPFkSNFO91tgg+8o2rXeVLStz+vkXkGr4UtzH6t1SNbjwoiswd7p4AhHTw==", "dev": true, "requires": { - "@jest/core": "^25.4.0", - "@jest/test-result": "^25.4.0", - "@jest/types": "^25.4.0", + "@jest/core": "^25.5.4", + "@jest/test-result": "^25.5.0", + "@jest/types": "^25.5.0", "chalk": "^3.0.0", "exit": "^0.1.2", + "graceful-fs": "^4.2.4", "import-local": "^3.0.2", "is-ci": "^2.0.0", - "jest-config": "^25.4.0", - "jest-util": "^25.4.0", - "jest-validate": "^25.4.0", + "jest-config": "^25.5.4", + "jest-util": "^25.5.0", + "jest-validate": "^25.5.0", "prompts": "^2.0.1", "realpath-native": "^2.0.0", "yargs": "^15.3.1" @@ -6698,16 +7247,63 @@ } }, "jest-changed-files": { - "version": "25.4.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-25.4.0.tgz", - "integrity": "sha512-VR/rfJsEs4BVMkwOTuStRyS630fidFVekdw/lBaBQjx9KK3VZFOZ2c0fsom2fRp8pMCrCTP6LGna00o/DXGlqA==", + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-25.5.0.tgz", + "integrity": "sha512-EOw9QEqapsDT7mKF162m8HFzRPbmP8qJQny6ldVOdOVBz3ACgPm/1nAn5fPQ/NDaYhX/AHkrGwwkCncpAVSXcw==", "dev": true, "requires": { - "@jest/types": "^25.4.0", + "@jest/types": "^25.5.0", "execa": "^3.2.0", "throat": "^5.0.0" }, "dependencies": { + "@jest/types": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0" + } + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "cross-spawn": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.2.tgz", @@ -6746,6 +7342,12 @@ "pump": "^3.0.0" } }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, "is-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", @@ -6787,35 +7389,57 @@ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } } } }, "jest-config": { - "version": "25.4.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-25.4.0.tgz", - "integrity": "sha512-egT9aKYxMyMSQV1aqTgam0SkI5/I2P9qrKexN5r2uuM2+68ypnc+zPGmfUxK7p1UhE7dYH9SLBS7yb+TtmT1AA==", + "version": "25.5.4", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-25.5.4.tgz", + "integrity": "sha512-SZwR91SwcdK6bz7Gco8qL7YY2sx8tFJYzvg216DLihTWf+LKY/DoJXpM9nTzYakSyfblbqeU48p/p7Jzy05Atg==", "dev": true, "requires": { "@babel/core": "^7.1.0", - "@jest/test-sequencer": "^25.4.0", - "@jest/types": "^25.4.0", - "babel-jest": "^25.4.0", + "@jest/test-sequencer": "^25.5.4", + "@jest/types": "^25.5.0", + "babel-jest": "^25.5.1", "chalk": "^3.0.0", "deepmerge": "^4.2.2", "glob": "^7.1.1", - "jest-environment-jsdom": "^25.4.0", - "jest-environment-node": "^25.4.0", + "graceful-fs": "^4.2.4", + "jest-environment-jsdom": "^25.5.0", + "jest-environment-node": "^25.5.0", "jest-get-type": "^25.2.6", - "jest-jasmine2": "^25.4.0", + "jest-jasmine2": "^25.5.4", "jest-regex-util": "^25.2.6", - "jest-resolve": "^25.4.0", - "jest-util": "^25.4.0", - "jest-validate": "^25.4.0", + "jest-resolve": "^25.5.1", + "jest-util": "^25.5.0", + "jest-validate": "^25.5.0", "micromatch": "^4.0.2", - "pretty-format": "^25.4.0", + "pretty-format": "^25.5.0", "realpath-native": "^2.0.0" }, "dependencies": { + "@jest/types": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0" + } + }, "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -6869,6 +7493,12 @@ "to-regex-range": "^5.0.1" } }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -6891,6 +7521,18 @@ "picomatch": "^2.0.5" } }, + "pretty-format": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.5.0.tgz", + "integrity": "sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ==", + "dev": true, + "requires": { + "@jest/types": "^25.5.0", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^16.12.0" + } + }, "supports-color": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", @@ -6985,18 +7627,120 @@ } }, "jest-each": { - "version": "25.4.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-25.4.0.tgz", - "integrity": "sha512-lwRIJ8/vQU/6vq3nnSSUw1Y3nz5tkYSFIywGCZpUBd6WcRgpn8NmJoQICojbpZmsJOJNHm0BKdyuJ6Xdx+eDQQ==", + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-25.5.0.tgz", + "integrity": "sha512-QBogUxna3D8vtiItvn54xXde7+vuzqRrEeaw8r1s+1TG9eZLVJE5ZkKoSUlqFwRjnlaA4hyKGiu9OlkFIuKnjA==", "dev": true, "requires": { - "@jest/types": "^25.4.0", + "@jest/types": "^25.5.0", "chalk": "^3.0.0", "jest-get-type": "^25.2.6", - "jest-util": "^25.4.0", - "pretty-format": "^25.4.0" + "jest-util": "^25.5.0", + "pretty-format": "^25.5.0" }, "dependencies": { + "@jest/types": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0" + } + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "pretty-format": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.5.0.tgz", + "integrity": "sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ==", + "dev": true, + "requires": { + "@jest/types": "^25.5.0", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^16.12.0" + } + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-environment-jsdom": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-25.5.0.tgz", + "integrity": "sha512-7Jr02ydaq4jaWMZLY+Skn8wL5nVIYpWvmeatOHL3tOcV3Zw8sjnPpx+ZdeBfc457p8jCR9J6YCc+Lga0oIy62A==", + "dev": true, + "requires": { + "@jest/environment": "^25.5.0", + "@jest/fake-timers": "^25.5.0", + "@jest/types": "^25.5.0", + "jest-mock": "^25.5.0", + "jest-util": "^25.5.0", + "jsdom": "^15.2.1" + }, + "dependencies": { + "@jest/types": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0" + } + }, "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -7049,39 +7793,87 @@ } } }, - "jest-environment-jsdom": { - "version": "25.4.0", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-25.4.0.tgz", - "integrity": "sha512-KTitVGMDrn2+pt7aZ8/yUTuS333w3pWt1Mf88vMntw7ZSBNDkRS6/4XLbFpWXYfWfp1FjcjQTOKzbK20oIehWQ==", - "dev": true, - "requires": { - "@jest/environment": "^25.4.0", - "@jest/fake-timers": "^25.4.0", - "@jest/types": "^25.4.0", - "jest-mock": "^25.4.0", - "jest-util": "^25.4.0", - "jsdom": "^15.2.1" - } - }, "jest-environment-node": { - "version": "25.4.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-25.4.0.tgz", - "integrity": "sha512-wryZ18vsxEAKFH7Z74zi/y/SyI1j6UkVZ6QsllBuT/bWlahNfQjLNwFsgh/5u7O957dYFoXj4yfma4n4X6kU9A==", + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-25.5.0.tgz", + "integrity": "sha512-iuxK6rQR2En9EID+2k+IBs5fCFd919gVVK5BeND82fYeLWPqvRcFNPKu9+gxTwfB5XwBGBvZ0HFQa+cHtIoslA==", "dev": true, "requires": { - "@jest/environment": "^25.4.0", - "@jest/fake-timers": "^25.4.0", - "@jest/types": "^25.4.0", - "jest-mock": "^25.4.0", - "jest-util": "^25.4.0", + "@jest/environment": "^25.5.0", + "@jest/fake-timers": "^25.5.0", + "@jest/types": "^25.5.0", + "jest-mock": "^25.5.0", + "jest-util": "^25.5.0", "semver": "^6.3.0" }, "dependencies": { + "@jest/types": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0" + } + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } } } }, @@ -7092,25 +7884,48 @@ "dev": true }, "jest-haste-map": { - "version": "25.4.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-25.4.0.tgz", - "integrity": "sha512-5EoCe1gXfGC7jmXbKzqxESrgRcaO3SzWXGCnvp9BcT0CFMyrB1Q6LIsjl9RmvmJGQgW297TCfrdgiy574Rl9HQ==", + "version": "25.5.1", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-25.5.1.tgz", + "integrity": "sha512-dddgh9UZjV7SCDQUrQ+5t9yy8iEgKc1AKqZR9YDww8xsVOtzPQSMVLDChc21+g29oTRexb9/B0bIlZL+sWmvAQ==", "dev": true, "requires": { - "@jest/types": "^25.4.0", + "@jest/types": "^25.5.0", + "@types/graceful-fs": "^4.1.2", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", "fsevents": "^2.1.2", - "graceful-fs": "^4.2.3", - "jest-serializer": "^25.2.6", - "jest-util": "^25.4.0", - "jest-worker": "^25.4.0", + "graceful-fs": "^4.2.4", + "jest-serializer": "^25.5.0", + "jest-util": "^25.5.0", + "jest-worker": "^25.5.0", "micromatch": "^4.0.2", "sane": "^4.0.3", "walker": "^1.0.7", "which": "^2.0.2" }, "dependencies": { + "@jest/types": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0" + } + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, "anymatch": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", @@ -7130,6 +7945,31 @@ "fill-range": "^7.0.1" } }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -7146,6 +7986,18 @@ "dev": true, "optional": true }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -7162,6 +8014,15 @@ "picomatch": "^2.0.5" } }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -7174,30 +8035,42 @@ } }, "jest-jasmine2": { - "version": "25.4.0", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-25.4.0.tgz", - "integrity": "sha512-QccxnozujVKYNEhMQ1vREiz859fPN/XklOzfQjm2j9IGytAkUbSwjFRBtQbHaNZ88cItMpw02JnHGsIdfdpwxQ==", + "version": "25.5.4", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-25.5.4.tgz", + "integrity": "sha512-9acbWEfbmS8UpdcfqnDO+uBUgKa/9hcRh983IHdM+pKmJPL77G0sWAAK0V0kr5LK3a8cSBfkFSoncXwQlRZfkQ==", "dev": true, "requires": { "@babel/traverse": "^7.1.0", - "@jest/environment": "^25.4.0", - "@jest/source-map": "^25.2.6", - "@jest/test-result": "^25.4.0", - "@jest/types": "^25.4.0", + "@jest/environment": "^25.5.0", + "@jest/source-map": "^25.5.0", + "@jest/test-result": "^25.5.0", + "@jest/types": "^25.5.0", "chalk": "^3.0.0", "co": "^4.6.0", - "expect": "^25.4.0", + "expect": "^25.5.0", "is-generator-fn": "^2.0.0", - "jest-each": "^25.4.0", - "jest-matcher-utils": "^25.4.0", - "jest-message-util": "^25.4.0", - "jest-runtime": "^25.4.0", - "jest-snapshot": "^25.4.0", - "jest-util": "^25.4.0", - "pretty-format": "^25.4.0", + "jest-each": "^25.5.0", + "jest-matcher-utils": "^25.5.0", + "jest-message-util": "^25.5.0", + "jest-runtime": "^25.5.4", + "jest-snapshot": "^25.5.1", + "jest-util": "^25.5.0", + "pretty-format": "^25.5.0", "throat": "^5.0.0" }, "dependencies": { + "@jest/types": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0" + } + }, "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -7239,6 +8112,18 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "pretty-format": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.5.0.tgz", + "integrity": "sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ==", + "dev": true, + "requires": { + "@jest/types": "^25.5.0", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^16.12.0" + } + }, "supports-color": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", @@ -7251,27 +8136,27 @@ } }, "jest-leak-detector": { - "version": "25.4.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-25.4.0.tgz", - "integrity": "sha512-7Y6Bqfv2xWsB+7w44dvZuLs5SQ//fzhETgOGG7Gq3TTGFdYvAgXGwV8z159RFZ6fXiCPm/szQ90CyfVos9JIFQ==", + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-25.5.0.tgz", + "integrity": "sha512-rV7JdLsanS8OkdDpZtgBf61L5xZ4NnYLBq72r6ldxahJWWczZjXawRsoHyXzibM5ed7C2QRjpp6ypgwGdKyoVA==", "dev": true, "requires": { "jest-get-type": "^25.2.6", - "pretty-format": "^25.4.0" - } - }, - "jest-matcher-utils": { - "version": "25.4.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-25.4.0.tgz", - "integrity": "sha512-yPMdtj7YDgXhnGbc66bowk8AkQ0YwClbbwk3Kzhn5GVDrciiCr27U4NJRbrqXbTdtxjImONITg2LiRIw650k5A==", - "dev": true, - "requires": { - "chalk": "^3.0.0", - "jest-diff": "^25.4.0", - "jest-get-type": "^25.2.6", - "pretty-format": "^25.4.0" + "pretty-format": "^25.5.0" }, "dependencies": { + "@jest/types": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0" + } + }, "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -7313,6 +8198,118 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "pretty-format": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.5.0.tgz", + "integrity": "sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ==", + "dev": true, + "requires": { + "@jest/types": "^25.5.0", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^16.12.0" + } + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-matcher-utils": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-25.5.0.tgz", + "integrity": "sha512-VWI269+9JS5cpndnpCwm7dy7JtGQT30UHfrnM3mXl22gHGt/b7NkjBqXfbhZ8V4B7ANUsjK18PlSBmG0YH7gjw==", + "dev": true, + "requires": { + "chalk": "^3.0.0", + "jest-diff": "^25.5.0", + "jest-get-type": "^25.2.6", + "pretty-format": "^25.5.0" + }, + "dependencies": { + "@jest/types": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0" + } + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "jest-diff": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-25.5.0.tgz", + "integrity": "sha512-z1kygetuPiREYdNIumRpAHY6RXiGmp70YHptjdaxTWGmA085W3iCnXNx0DhflK3vwrKmrRWyY1wUpkPMVxMK7A==", + "dev": true, + "requires": { + "chalk": "^3.0.0", + "diff-sequences": "^25.2.6", + "jest-get-type": "^25.2.6", + "pretty-format": "^25.5.0" + } + }, + "pretty-format": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.5.0.tgz", + "integrity": "sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ==", + "dev": true, + "requires": { + "@jest/types": "^25.5.0", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^16.12.0" + } + }, "supports-color": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", @@ -7325,20 +8322,33 @@ } }, "jest-message-util": { - "version": "25.4.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-25.4.0.tgz", - "integrity": "sha512-LYY9hRcVGgMeMwmdfh9tTjeux1OjZHMusq/E5f3tJN+dAoVVkJtq5ZUEPIcB7bpxDUt2zjUsrwg0EGgPQ+OhXQ==", + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-25.5.0.tgz", + "integrity": "sha512-ezddz3YCT/LT0SKAmylVyWWIGYoKHOFOFXx3/nA4m794lfVUskMcwhip6vTgdVrOtYdjeQeis2ypzes9mZb4EA==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", - "@jest/types": "^25.4.0", + "@jest/types": "^25.5.0", "@types/stack-utils": "^1.0.1", "chalk": "^3.0.0", + "graceful-fs": "^4.2.4", "micromatch": "^4.0.2", "slash": "^3.0.0", "stack-utils": "^1.0.1" }, "dependencies": { + "@jest/types": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0" + } + }, "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -7392,6 +8402,12 @@ "to-regex-range": "^5.0.1" } }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -7435,42 +8451,26 @@ } }, "jest-mock": { - "version": "25.4.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-25.4.0.tgz", - "integrity": "sha512-MdazSfcYAUjJjuVTTnusLPzE0pE4VXpOUzWdj8sbM+q6abUjm3bATVPXFqTXrxSieR8ocpvQ9v/QaQCftioQFg==", + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-25.5.0.tgz", + "integrity": "sha512-eXWuTV8mKzp/ovHc5+3USJMYsTBhyQ+5A1Mak35dey/RG8GlM4YWVylZuGgVXinaW6tpvk/RSecmF37FKUlpXA==", "dev": true, "requires": { - "@jest/types": "^25.4.0" - } - }, - "jest-pnp-resolver": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.1.tgz", - "integrity": "sha512-pgFw2tm54fzgYvc/OHrnysABEObZCUNFnhjoRjaVOCN8NYc032/gVjPaHD4Aq6ApkSieWtfKAFQtmDKAmhupnQ==", - "dev": true - }, - "jest-regex-util": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-25.2.6.tgz", - "integrity": "sha512-KQqf7a0NrtCkYmZZzodPftn7fL1cq3GQAFVMn5Hg8uKx/fIenLEobNanUxb7abQ1sjADHBseG/2FGpsv/wr+Qw==", - "dev": true - }, - "jest-resolve": { - "version": "25.4.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-25.4.0.tgz", - "integrity": "sha512-wOsKqVDFWUiv8BtLMCC6uAJ/pHZkfFgoBTgPtmYlsprAjkxrr2U++ZnB3l5ykBMd2O24lXvf30SMAjJIW6k2aA==", - "dev": true, - "requires": { - "@jest/types": "^25.4.0", - "browser-resolve": "^1.11.3", - "chalk": "^3.0.0", - "jest-pnp-resolver": "^1.2.1", - "read-pkg-up": "^7.0.1", - "realpath-native": "^2.0.0", - "resolve": "^1.15.1", - "slash": "^3.0.0" + "@jest/types": "^25.5.0" }, "dependencies": { + "@jest/types": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0" + } + }, "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -7523,44 +8523,128 @@ } } }, + "jest-pnp-resolver": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.1.tgz", + "integrity": "sha512-pgFw2tm54fzgYvc/OHrnysABEObZCUNFnhjoRjaVOCN8NYc032/gVjPaHD4Aq6ApkSieWtfKAFQtmDKAmhupnQ==", + "dev": true + }, + "jest-regex-util": { + "version": "25.2.6", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-25.2.6.tgz", + "integrity": "sha512-KQqf7a0NrtCkYmZZzodPftn7fL1cq3GQAFVMn5Hg8uKx/fIenLEobNanUxb7abQ1sjADHBseG/2FGpsv/wr+Qw==", + "dev": true + }, + "jest-resolve": { + "version": "25.5.1", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-25.5.1.tgz", + "integrity": "sha512-Hc09hYch5aWdtejsUZhA+vSzcotf7fajSlPA6EZPE1RmPBAD39XtJhvHWFStid58iit4IPDLI/Da4cwdDmAHiQ==", + "dev": true, + "requires": { + "@jest/types": "^25.5.0", + "browser-resolve": "^1.11.3", + "chalk": "^3.0.0", + "graceful-fs": "^4.2.4", + "jest-pnp-resolver": "^1.2.1", + "read-pkg-up": "^7.0.1", + "realpath-native": "^2.0.0", + "resolve": "^1.17.0", + "slash": "^3.0.0" + }, + "dependencies": { + "@jest/types": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0" + } + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "jest-resolve-dependencies": { - "version": "25.4.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-25.4.0.tgz", - "integrity": "sha512-A0eoZXx6kLiuG1Ui7wITQPl04HwjLErKIJTt8GR3c7UoDAtzW84JtCrgrJ6Tkw6c6MwHEyAaLk7dEPml5pf48A==", + "version": "25.5.4", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-25.5.4.tgz", + "integrity": "sha512-yFmbPd+DAQjJQg88HveObcGBA32nqNZ02fjYmtL16t1xw9bAttSn5UGRRhzMHIQbsep7znWvAvnD4kDqOFM0Uw==", "dev": true, "requires": { - "@jest/types": "^25.4.0", + "@jest/types": "^25.5.0", "jest-regex-util": "^25.2.6", - "jest-snapshot": "^25.4.0" - } - }, - "jest-runner": { - "version": "25.4.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-25.4.0.tgz", - "integrity": "sha512-wWQSbVgj2e/1chFdMRKZdvlmA6p1IPujhpLT7TKNtCSl1B0PGBGvJjCaiBal/twaU2yfk8VKezHWexM8IliBfA==", - "dev": true, - "requires": { - "@jest/console": "^25.4.0", - "@jest/environment": "^25.4.0", - "@jest/test-result": "^25.4.0", - "@jest/types": "^25.4.0", - "chalk": "^3.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.3", - "jest-config": "^25.4.0", - "jest-docblock": "^25.3.0", - "jest-haste-map": "^25.4.0", - "jest-jasmine2": "^25.4.0", - "jest-leak-detector": "^25.4.0", - "jest-message-util": "^25.4.0", - "jest-resolve": "^25.4.0", - "jest-runtime": "^25.4.0", - "jest-util": "^25.4.0", - "jest-worker": "^25.4.0", - "source-map-support": "^0.5.6", - "throat": "^5.0.0" + "jest-snapshot": "^25.5.1" }, "dependencies": { + "@jest/types": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0" + } + }, "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -7613,39 +8697,149 @@ } } }, + "jest-runner": { + "version": "25.5.4", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-25.5.4.tgz", + "integrity": "sha512-V/2R7fKZo6blP8E9BL9vJ8aTU4TH2beuqGNxHbxi6t14XzTb+x90B3FRgdvuHm41GY8ch4xxvf0ATH4hdpjTqg==", + "dev": true, + "requires": { + "@jest/console": "^25.5.0", + "@jest/environment": "^25.5.0", + "@jest/test-result": "^25.5.0", + "@jest/types": "^25.5.0", + "chalk": "^3.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.4", + "jest-config": "^25.5.4", + "jest-docblock": "^25.3.0", + "jest-haste-map": "^25.5.1", + "jest-jasmine2": "^25.5.4", + "jest-leak-detector": "^25.5.0", + "jest-message-util": "^25.5.0", + "jest-resolve": "^25.5.1", + "jest-runtime": "^25.5.4", + "jest-util": "^25.5.0", + "jest-worker": "^25.5.0", + "source-map-support": "^0.5.6", + "throat": "^5.0.0" + }, + "dependencies": { + "@jest/types": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0" + } + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "jest-runtime": { - "version": "25.4.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-25.4.0.tgz", - "integrity": "sha512-lgNJlCDULtXu9FumnwCyWlOub8iytijwsPNa30BKrSNtgoT6NUMXOPrZvsH06U6v0wgD/Igwz13nKA2wEKU2VA==", + "version": "25.5.4", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-25.5.4.tgz", + "integrity": "sha512-RWTt8LeWh3GvjYtASH2eezkc8AehVoWKK20udV6n3/gC87wlTbE1kIA+opCvNWyyPeBs6ptYsc6nyHUb1GlUVQ==", "dev": true, "requires": { - "@jest/console": "^25.4.0", - "@jest/environment": "^25.4.0", - "@jest/source-map": "^25.2.6", - "@jest/test-result": "^25.4.0", - "@jest/transform": "^25.4.0", - "@jest/types": "^25.4.0", + "@jest/console": "^25.5.0", + "@jest/environment": "^25.5.0", + "@jest/globals": "^25.5.2", + "@jest/source-map": "^25.5.0", + "@jest/test-result": "^25.5.0", + "@jest/transform": "^25.5.1", + "@jest/types": "^25.5.0", "@types/yargs": "^15.0.0", "chalk": "^3.0.0", "collect-v8-coverage": "^1.0.0", "exit": "^0.1.2", "glob": "^7.1.3", - "graceful-fs": "^4.2.3", - "jest-config": "^25.4.0", - "jest-haste-map": "^25.4.0", - "jest-message-util": "^25.4.0", - "jest-mock": "^25.4.0", + "graceful-fs": "^4.2.4", + "jest-config": "^25.5.4", + "jest-haste-map": "^25.5.1", + "jest-message-util": "^25.5.0", + "jest-mock": "^25.5.0", "jest-regex-util": "^25.2.6", - "jest-resolve": "^25.4.0", - "jest-snapshot": "^25.4.0", - "jest-util": "^25.4.0", - "jest-validate": "^25.4.0", + "jest-resolve": "^25.5.1", + "jest-snapshot": "^25.5.1", + "jest-util": "^25.5.0", + "jest-validate": "^25.5.0", "realpath-native": "^2.0.0", "slash": "^3.0.0", "strip-bom": "^4.0.0", "yargs": "^15.3.1" }, "dependencies": { + "@jest/types": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0" + } + }, "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -7681,6 +8875,12 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -7699,33 +8899,57 @@ } }, "jest-serializer": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-25.2.6.tgz", - "integrity": "sha512-RMVCfZsezQS2Ww4kB5HJTMaMJ0asmC0BHlnobQC6yEtxiFKIxohFA4QSXSabKwSggaNkqxn6Z2VwdFCjhUWuiQ==", - "dev": true + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-25.5.0.tgz", + "integrity": "sha512-LxD8fY1lByomEPflwur9o4e2a5twSQ7TaVNLlFUuToIdoJuBt8tzHfCsZ42Ok6LkKXWzFWf3AGmheuLAA7LcCA==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.4" + }, + "dependencies": { + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + } + } }, "jest-snapshot": { - "version": "25.4.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-25.4.0.tgz", - "integrity": "sha512-J4CJ0X2SaGheYRZdLz9CRHn9jUknVmlks4UBeu270hPAvdsauFXOhx9SQP2JtRzhnR3cvro/9N9KP83/uvFfRg==", + "version": "25.5.1", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-25.5.1.tgz", + "integrity": "sha512-C02JE1TUe64p2v1auUJ2ze5vcuv32tkv9PyhEb318e8XOKF7MOyXdJ7kdjbvrp3ChPLU2usI7Rjxs97Dj5P0uQ==", "dev": true, "requires": { "@babel/types": "^7.0.0", - "@jest/types": "^25.4.0", + "@jest/types": "^25.5.0", "@types/prettier": "^1.19.0", "chalk": "^3.0.0", - "expect": "^25.4.0", - "jest-diff": "^25.4.0", + "expect": "^25.5.0", + "graceful-fs": "^4.2.4", + "jest-diff": "^25.5.0", "jest-get-type": "^25.2.6", - "jest-matcher-utils": "^25.4.0", - "jest-message-util": "^25.4.0", - "jest-resolve": "^25.4.0", + "jest-matcher-utils": "^25.5.0", + "jest-message-util": "^25.5.0", + "jest-resolve": "^25.5.1", "make-dir": "^3.0.0", "natural-compare": "^1.4.0", - "pretty-format": "^25.4.0", + "pretty-format": "^25.5.0", "semver": "^6.3.0" }, "dependencies": { + "@jest/types": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0" + } + }, "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -7761,12 +8985,30 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "jest-diff": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-25.5.0.tgz", + "integrity": "sha512-z1kygetuPiREYdNIumRpAHY6RXiGmp70YHptjdaxTWGmA085W3iCnXNx0DhflK3vwrKmrRWyY1wUpkPMVxMK7A==", + "dev": true, + "requires": { + "chalk": "^3.0.0", + "diff-sequences": "^25.2.6", + "jest-get-type": "^25.2.6", + "pretty-format": "^25.5.0" + } + }, "make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -7776,6 +9018,18 @@ "semver": "^6.0.0" } }, + "pretty-format": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.5.0.tgz", + "integrity": "sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ==", + "dev": true, + "requires": { + "@jest/types": "^25.5.0", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^16.12.0" + } + }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -7794,17 +9048,30 @@ } }, "jest-util": { - "version": "25.4.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-25.4.0.tgz", - "integrity": "sha512-WSZD59sBtAUjLv1hMeKbNZXmMcrLRWcYqpO8Dz8b4CeCTZpfNQw2q9uwrYAD+BbJoLJlu4ezVPwtAmM/9/SlZA==", + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-25.5.0.tgz", + "integrity": "sha512-KVlX+WWg1zUTB9ktvhsg2PXZVdkI1NBevOJSkTKYAyXyH4QSvh+Lay/e/v+bmaFfrkfx43xD8QTfgobzlEXdIA==", "dev": true, "requires": { - "@jest/types": "^25.4.0", + "@jest/types": "^25.5.0", "chalk": "^3.0.0", + "graceful-fs": "^4.2.4", "is-ci": "^2.0.0", "make-dir": "^3.0.0" }, "dependencies": { + "@jest/types": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0" + } + }, "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -7840,6 +9107,12 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -7873,19 +9146,31 @@ } }, "jest-validate": { - "version": "25.4.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-25.4.0.tgz", - "integrity": "sha512-hvjmes/EFVJSoeP1yOl8qR8mAtMR3ToBkZeXrD/ZS9VxRyWDqQ/E1C5ucMTeSmEOGLipvdlyipiGbHJ+R1MQ0g==", + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-25.5.0.tgz", + "integrity": "sha512-okUFKqhZIpo3jDdtUXUZ2LxGUZJIlfdYBvZb1aczzxrlyMlqdnnws9MOxezoLGhSaFc2XYaHNReNQfj5zPIWyQ==", "dev": true, "requires": { - "@jest/types": "^25.4.0", + "@jest/types": "^25.5.0", "camelcase": "^5.3.1", "chalk": "^3.0.0", "jest-get-type": "^25.2.6", "leven": "^3.1.0", - "pretty-format": "^25.4.0" + "pretty-format": "^25.5.0" }, "dependencies": { + "@jest/types": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0" + } + }, "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -7927,6 +9212,18 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "pretty-format": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.5.0.tgz", + "integrity": "sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ==", + "dev": true, + "requires": { + "@jest/types": "^25.5.0", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^16.12.0" + } + }, "supports-color": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", @@ -7939,19 +9236,31 @@ } }, "jest-watcher": { - "version": "25.4.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-25.4.0.tgz", - "integrity": "sha512-36IUfOSRELsKLB7k25j/wutx0aVuHFN6wO94gPNjQtQqFPa2rkOymmx9rM5EzbF3XBZZ2oqD9xbRVoYa2w86gw==", + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-25.5.0.tgz", + "integrity": "sha512-XrSfJnVASEl+5+bb51V0Q7WQx65dTSk7NL4yDdVjPnRNpM0hG+ncFmDYJo9O8jaSRcAitVbuVawyXCRoxGrT5Q==", "dev": true, "requires": { - "@jest/test-result": "^25.4.0", - "@jest/types": "^25.4.0", + "@jest/test-result": "^25.5.0", + "@jest/types": "^25.5.0", "ansi-escapes": "^4.2.1", "chalk": "^3.0.0", - "jest-util": "^25.4.0", + "jest-util": "^25.5.0", "string-length": "^3.1.0" }, "dependencies": { + "@jest/types": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0" + } + }, "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -8005,9 +9314,9 @@ } }, "jest-worker": { - "version": "25.4.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-25.4.0.tgz", - "integrity": "sha512-ghAs/1FtfYpMmYQ0AHqxV62XPvKdUDIBBApMZfly+E9JEmYh2K45G0R5dWxx986RN12pRCxsViwQVtGl+N4whw==", + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-25.5.0.tgz", + "integrity": "sha512-/dsSmUkIy5EBGfv/IjjqmFxrNAUpBERfGs1oHROyD7yxjG/w+t0GOJDX8O1k32ySmd7+a5IhnJU2qQFcJ4n1vw==", "dev": true, "requires": { "merge-stream": "^2.0.0", @@ -8897,11 +10206,14 @@ }, "dependencies": { "is-wsl": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.1.1.tgz", - "integrity": "sha512-umZHcSrwlDHo2TGMXv0DZ8dIUGunZ2Iv68YZnrmCiBPkZ4aaOhtv7pXJKeki9k3qJ3RJr0cDyitcl5wEH3AYog==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", "dev": true, - "optional": true + "optional": true, + "requires": { + "is-docker": "^2.0.0" + } }, "semver": { "version": "6.3.0", @@ -10430,9 +11742,9 @@ "dev": true }, "qs": { - "version": "6.9.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.3.tgz", - "integrity": "sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw==" + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz", + "integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==" }, "query-string": { "version": "4.3.4", @@ -10541,9 +11853,9 @@ } }, "react-hot-loader": { - "version": "4.12.20", - "resolved": "https://registry.npmjs.org/react-hot-loader/-/react-hot-loader-4.12.20.tgz", - "integrity": "sha512-lPlv1HVizi0lsi+UFACBJaydtRYILWkfHAC/lyCs6ZlAxlOZRQIfYHDqiGaRvL/GF7zyti+Qn9XpnDAUvdFA4A==", + "version": "4.12.21", + "resolved": "https://registry.npmjs.org/react-hot-loader/-/react-hot-loader-4.12.21.tgz", + "integrity": "sha512-Ynxa6ROfWUeKWsTHxsrL2KMzujxJVPjs385lmB2t5cHUxdoRPGind9F00tOkdc1l5WBleOF4XEAMILY1KPIIDA==", "requires": { "fast-levenshtein": "^2.0.6", "global": "^4.3.0", @@ -12203,9 +13515,9 @@ } }, "style-loader": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-1.2.0.tgz", - "integrity": "sha512-HC8WcGnjwNrKji7HSBqFOhGNUSt7UDU/jHxT6bA83Gk+JWJBmgitWlGihc0V1w6ZvwlzcX5LJOsofZzSP7b1tQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-1.2.1.tgz", + "integrity": "sha512-ByHSTQvHLkWE9Ir5+lGbVOXhxX10fbprhLvdg96wedFZb4NDekDPxVKv5Fwmio+QcMlkkNfuK+5W1peQ5CUhZg==", "dev": true, "requires": { "loader-utils": "^2.0.0", @@ -12638,9 +13950,9 @@ } }, "ts-loader": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-7.0.1.tgz", - "integrity": "sha512-wdGs9xO8UnwASwbluehzXciBtc9HfGlYA8Aiv856etLmdv8mJfAxCkt3YpS4g7G1IsGxaCVKQ102Qh0zycpeZQ==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-7.0.2.tgz", + "integrity": "sha512-DwpZFB67RoILQHx42dMjSgv2STpacsQu5X+GD/H9ocd8IhU0m8p3b/ZrIln2KmcucC6xep2PdEMEblpWT71euA==", "dev": true, "requires": { "chalk": "^2.3.0", @@ -13906,9 +15218,9 @@ } }, "ws": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.2.3.tgz", - "integrity": "sha512-HTDl9G9hbkNDk98naoR/cHDws7+EyYMOdL1BmjsZXRUjf7d+MficC4B7HLUPlSiho0vg+CWKrGIt/VJBd1xunQ==", + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.2.5.tgz", + "integrity": "sha512-C34cIU4+DB2vMyAbmEKossWq2ZQDr6QEyuuCzWrM9zfw1sGc0mYiJ0UnG9zzNykt49C2Fi34hvr2vssFQRS6EA==", "dev": true }, "xml-name-validator": { diff --git a/web/package.json b/web/package.json index 3eb1b68d..ba31e8d6 100644 --- a/web/package.json +++ b/web/package.json @@ -12,22 +12,22 @@ "author": "Monomax Software Pty Ltd", "license": "AGPL-3.0-or-later", "devDependencies": { - "@babel/core": "^7.9.0", + "@babel/core": "^7.9.6", "@babel/plugin-transform-react-constant-elements": "^7.9.0", - "@babel/preset-env": "^7.9.5", + "@babel/preset-env": "^7.9.6", "@babel/preset-react": "^7.9.4", "@babel/preset-typescript": "^7.9.0", "@babel/register": "^7.9.0", "@hot-loader/react-dom": "^16.13.0", "@types/jest": "^25.2.1", "@types/react": "^16.9.34", - "@types/react-dom": "^16.9.6", + "@types/react-dom": "^16.9.7", "autoprefixer": "^9.7.6", "babel-loader": "^8.1.0", "css-loader": "^3.5.3", "cssnano": "^4.1.10", "file-loader": "^6.0.0", - "jest": "^25.4.0", + "jest": "^25.5.4", "mini-css-extract-plugin": "^0.9.0", "node-sass": "^4.14.0", "postcss-loader": "^3.0.0", @@ -35,9 +35,9 @@ "sass-loader": "^8.0.2", "source-map-loader": "^0.2.4", "source-map-support": "^0.5.19", - "style-loader": "^1.2.0", + "style-loader": "^1.2.1", "ts-jest": "^25.4.0", - "ts-loader": "^7.0.0", + "ts-loader": "^7.0.2", "typescript": "^3.8.3", "url-loader": "^4.1.0", "webpack-cli": "^3.3.11", @@ -53,14 +53,14 @@ "@types/react-router-dom": "^5.1.5", "classnames": "^2.2.5", "core-js": "^3.6.5", - "highlight.js": "^10.0.1", + "highlight.js": "^10.0.2", "history": "^4.10.1", "markdown-it": "^9.0.0", - "qs": "^6.9.3", + "qs": "^6.9.4", "react": "^16.13.1", "react-dom": "^16.13.1", "react-helmet": "^5.0.0", - "react-hot-loader": "^4.12.20", + "react-hot-loader": "^4.12.21", "react-redux": "^7.2.0", "react-router": "^5.1.2", "react-router-config": "^5.1.1", From 6acc2936e3ac194ba3d173c2764d7fc3949fe0a2 Mon Sep 17 00:00:00 2001 From: Sung Won Cho Date: Fri, 22 May 2020 16:30:05 +1000 Subject: [PATCH 02/99] Reduce bundle size (#469) * Rename handlers to api * Fix imports * Fix test * Abstract * Fix warning * wip * Split session * Pass db * Fix test * Fix test * Remove payment * Fix state * Fix flow * Check password when changing email * Add test methods * Fix timestamp * Document * Remove clutter * Redirect to login * Fix * Fix --- .eslintrc | 1 + CHANGELOG.md | 8 +- browser/src/global.d.ts | 1 + go.mod | 23 +- go.sum | 96 +-- jslib/src/services/index.ts | 5 +- jslib/src/services/payment.ts | 70 --- jslib/src/services/users.ts | 6 +- pkg/assert/assert.go | 8 + pkg/server/.gitignore | 8 - pkg/server/api/auth.go | 181 ++++++ pkg/server/{handlers => api}/auth_test.go | 82 ++- pkg/server/{handlers => api}/health.go | 2 +- pkg/server/{handlers => api}/health_test.go | 2 +- pkg/server/api/helpers.go | 85 +++ .../Form.scss => pkg/server/api/main_test.go | 22 +- pkg/server/{handlers => api}/notes.go | 27 +- pkg/server/{handlers => api}/notes_test.go | 6 +- pkg/server/api/routes.go | 116 ++++ pkg/server/api/routes_test.go | 161 +++++ pkg/server/{handlers => api}/testutils.go | 5 +- pkg/server/{handlers => api}/user.go | 89 +-- pkg/server/{handlers => api}/user_test.go | 74 ++- pkg/server/{handlers => api}/v3_auth.go | 41 +- pkg/server/{handlers => api}/v3_auth_test.go | 25 +- pkg/server/{handlers => api}/v3_books.go | 39 +- pkg/server/{handlers => api}/v3_books_test.go | 14 +- pkg/server/{handlers => api}/v3_notes.go | 37 +- pkg/server/{handlers => api}/v3_notes_test.go | 8 +- pkg/server/{handlers => api}/v3_sync.go | 15 +- pkg/server/{handlers => api}/v3_sync_test.go | 2 +- pkg/server/app/app.go | 12 +- pkg/server/app/books_test.go | 6 +- pkg/server/app/helpers_test.go | 2 +- pkg/server/app/main_test.go | 2 +- pkg/server/app/notes_test.go | 6 +- pkg/server/app/subscriptions.go | 80 --- pkg/server/app/testutils.go | 14 +- pkg/server/app/users.go | 4 +- pkg/server/app/users_test.go | 2 +- pkg/server/handlers/auth.go | 282 ++++----- pkg/server/handlers/helpers.go | 190 +++--- .../{routes_test.go => helpers_test.go} | 216 +++---- pkg/server/handlers/limit.go | 4 +- pkg/server/handlers/logging.go | 42 ++ pkg/server/handlers/main_test.go | 2 +- pkg/server/handlers/routes.go | 390 ------------ pkg/server/handlers/semver.go | 62 -- pkg/server/handlers/subscription.go | 561 ------------------ pkg/server/job/remind/inactive_test.go | 4 +- pkg/server/job/remind/main_test.go | 2 +- pkg/server/main.go | 16 +- pkg/server/operations/main_test.go | 2 +- pkg/server/operations/notes_test.go | 4 +- pkg/server/permissions/permissions_test.go | 4 +- pkg/server/session/session.go | 23 + pkg/server/session/session_test.go | 67 +++ pkg/server/testutils/main.go | 43 +- pkg/server/tmpl/app_test.go | 2 +- pkg/server/tmpl/main_test.go | 2 +- pkg/server/token/main_test.go | 2 +- pkg/server/token/token_test.go | 2 +- pkg/server/web/handlers.go | 2 +- pkg/server/web/main_test.go | 2 +- scripts/web/build-prod.sh | 1 + scripts/web/build.sh | 4 +- scripts/web/dev.sh | 2 +- scripts/web/webpack-dev.sh | 3 +- web/declrations.d.ts | 2 +- web/package-lock.json | 8 - web/package.json | 1 - web/src/components/App/index.tsx | 5 +- web/src/components/Common/Note/Footer.tsx | 12 +- web/src/components/Common/PayWall.tsx | 9 +- .../components/Common/PaymentInput/Card.tsx | 94 --- .../Common/PaymentInput/Country.tsx | 58 -- .../Common/PaymentInput/CountrySelect.scss | 50 -- .../Common/PaymentInput/CountrySelect.tsx | 63 -- .../Common/PaymentInput/NameOnCard.tsx | 60 -- .../Common/PaymentInput/PaymentInput.scss | 51 -- .../components/Header/SubscriptionHeader.scss | 59 -- .../components/Header/SubscriptionHeader.tsx | 54 -- web/src/components/Settings/About/index.tsx | 2 +- .../Settings/Account/EmailModal.tsx | 3 +- .../Settings/Billing/CancelPlanModal.tsx | 108 ---- .../Billing/PaymentMethodModal/Form.tsx | 151 ----- .../Billing/PaymentMethodModal/index.tsx | 91 --- .../PaymentSection/PaymentMethodRow.tsx | 66 --- .../Billing/PaymentSection/Placeholder.scss | 49 -- .../Billing/PaymentSection/Placeholder.tsx | 51 -- .../Settings/Billing/PaymentSection/index.tsx | 52 -- .../Billing/PlanSection/CancelRow.tsx | 49 -- .../Billing/PlanSection/Placeholder.scss | 41 -- .../Billing/PlanSection/Placeholder.tsx | 60 -- .../Settings/Billing/PlanSection/PlanRow.scss | 52 -- .../Settings/Billing/PlanSection/PlanRow.tsx | 91 --- .../Billing/PlanSection/ReactivateRow.tsx | 79 --- .../Settings/Billing/PlanSection/index.tsx | 64 -- web/src/components/Settings/Billing/index.tsx | 176 ------ web/src/components/Settings/Sidebar.tsx | 16 +- web/src/components/Settings/index.tsx | 4 - .../Subscription/Checkout/Form.scss | 72 --- .../components/Subscription/Checkout/Form.tsx | 194 ------ .../Subscription/Checkout/PaymentSummary.tsx | 36 -- .../Subscription/Checkout/Price.tsx | 28 - .../Subscription/Checkout/ScheduleSummary.tsx | 43 -- .../Subscription/Checkout/Sidebar.scss | 131 ---- .../Subscription/Checkout/Sidebar.tsx | 113 ---- .../Subscription/Checkout/index.tsx | 58 -- web/src/components/Subscription/Footer.tsx | 35 -- .../components/Subscription/Subscription.scss | 101 ---- web/src/hocs/guestOnly.tsx | 6 + web/src/libs/paths.ts | 7 - web/src/routes.tsx | 12 - web/src/store/auth/actions.ts | 109 +--- web/src/store/auth/reducers.ts | 114 +--- web/src/store/auth/type.ts | 62 +- web/webpack/dev.config.js | 4 +- web/webpack/plugins.js | 14 +- web/webpack/prod.config.js | 4 +- 120 files changed, 1418 insertions(+), 4779 deletions(-) delete mode 100644 jslib/src/services/payment.ts create mode 100644 pkg/server/api/auth.go rename pkg/server/{handlers => api}/auth_test.go (84%) rename pkg/server/{handlers => api}/health.go (98%) rename pkg/server/{handlers => api}/health_test.go (98%) create mode 100644 pkg/server/api/helpers.go rename web/src/components/Settings/Billing/PaymentMethodModal/Form.scss => pkg/server/api/main_test.go (77%) rename pkg/server/{handlers => api}/notes.go (89%) rename pkg/server/{handlers => api}/notes_test.go (99%) create mode 100644 pkg/server/api/routes.go create mode 100644 pkg/server/api/routes_test.go rename pkg/server/{handlers => api}/testutils.go (96%) rename pkg/server/{handlers => api}/user.go (75%) rename pkg/server/{handlers => api}/user_test.go (91%) rename pkg/server/{handlers => api}/v3_auth.go (84%) rename pkg/server/{handlers => api}/v3_auth_test.go (96%) rename pkg/server/{handlers => api}/v3_books.go (81%) rename pkg/server/{handlers => api}/v3_books_test.go (98%) rename pkg/server/{handlers => api}/v3_notes.go (78%) rename pkg/server/{handlers => api}/v3_notes_test.go (98%) rename pkg/server/{handlers => api}/v3_sync.go (93%) rename pkg/server/{handlers => api}/v3_sync_test.go (99%) delete mode 100644 pkg/server/app/subscriptions.go rename pkg/server/handlers/{routes_test.go => helpers_test.go} (79%) create mode 100644 pkg/server/handlers/logging.go delete mode 100644 pkg/server/handlers/routes.go delete mode 100644 pkg/server/handlers/semver.go delete mode 100644 pkg/server/handlers/subscription.go create mode 100644 pkg/server/session/session.go create mode 100644 pkg/server/session/session_test.go delete mode 100644 web/src/components/Common/PaymentInput/Card.tsx delete mode 100644 web/src/components/Common/PaymentInput/Country.tsx delete mode 100644 web/src/components/Common/PaymentInput/CountrySelect.scss delete mode 100644 web/src/components/Common/PaymentInput/CountrySelect.tsx delete mode 100644 web/src/components/Common/PaymentInput/NameOnCard.tsx delete mode 100644 web/src/components/Common/PaymentInput/PaymentInput.scss delete mode 100644 web/src/components/Header/SubscriptionHeader.scss delete mode 100644 web/src/components/Header/SubscriptionHeader.tsx delete mode 100644 web/src/components/Settings/Billing/CancelPlanModal.tsx delete mode 100644 web/src/components/Settings/Billing/PaymentMethodModal/Form.tsx delete mode 100644 web/src/components/Settings/Billing/PaymentMethodModal/index.tsx delete mode 100644 web/src/components/Settings/Billing/PaymentSection/PaymentMethodRow.tsx delete mode 100644 web/src/components/Settings/Billing/PaymentSection/Placeholder.scss delete mode 100644 web/src/components/Settings/Billing/PaymentSection/Placeholder.tsx delete mode 100644 web/src/components/Settings/Billing/PaymentSection/index.tsx delete mode 100644 web/src/components/Settings/Billing/PlanSection/CancelRow.tsx delete mode 100644 web/src/components/Settings/Billing/PlanSection/Placeholder.scss delete mode 100644 web/src/components/Settings/Billing/PlanSection/Placeholder.tsx delete mode 100644 web/src/components/Settings/Billing/PlanSection/PlanRow.scss delete mode 100644 web/src/components/Settings/Billing/PlanSection/PlanRow.tsx delete mode 100644 web/src/components/Settings/Billing/PlanSection/ReactivateRow.tsx delete mode 100644 web/src/components/Settings/Billing/PlanSection/index.tsx delete mode 100644 web/src/components/Settings/Billing/index.tsx delete mode 100644 web/src/components/Subscription/Checkout/Form.scss delete mode 100644 web/src/components/Subscription/Checkout/Form.tsx delete mode 100644 web/src/components/Subscription/Checkout/PaymentSummary.tsx delete mode 100644 web/src/components/Subscription/Checkout/Price.tsx delete mode 100644 web/src/components/Subscription/Checkout/ScheduleSummary.tsx delete mode 100644 web/src/components/Subscription/Checkout/Sidebar.scss delete mode 100644 web/src/components/Subscription/Checkout/Sidebar.tsx delete mode 100644 web/src/components/Subscription/Checkout/index.tsx delete mode 100644 web/src/components/Subscription/Footer.tsx delete mode 100644 web/src/components/Subscription/Subscription.scss diff --git a/.eslintrc b/.eslintrc index 4c0427d6..1e16f097 100644 --- a/.eslintrc +++ b/.eslintrc @@ -60,6 +60,7 @@ "__STRIPE_PUBLIC_KEY__": true, "__ROOT_URL__": true, "__CDN_URL__": true, + "__STANDALONE__": true, "socket": true, "webpackIsomorphicTools": true, "StripeCheckout": true, diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d302062..0ce29c2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,13 @@ The following log documents the history of the server project. ### Unreleased -None +#### Removed + +- Remove unnecessary payment logic + +#### Fixed + +- Fix timestamp in the note content view ### 1.0.3 2020-05-03 diff --git a/browser/src/global.d.ts b/browser/src/global.d.ts index d0fe4ad7..0bf0f41c 100644 --- a/browser/src/global.d.ts +++ b/browser/src/global.d.ts @@ -20,5 +20,6 @@ // defined by webpack-define-plugin declare let __API_ENDPOINT__: string; +declare let __STANDALONE__: string; declare let __WEB_URL__: string; declare let __VERSION__: string; diff --git a/go.mod b/go.mod index 086444d2..23714f4f 100644 --- a/go.mod +++ b/go.mod @@ -3,14 +3,11 @@ module github.com/dnote/dnote go 1.13 require ( - cloud.google.com/go v0.37.4 // indirect github.com/PuerkitoBio/goquery v1.5.1 // indirect + github.com/andybalholm/cascadia v1.2.0 // indirect github.com/aymerick/douceur v0.2.0 github.com/dnote/actions v0.2.0 github.com/dnote/color v1.7.0 - github.com/dnote/xgo v0.0.0-20200205013105-40be7d6d43ff // indirect - github.com/gobuffalo/envy v1.9.0 // indirect - github.com/gobuffalo/packr v1.30.1 // indirect github.com/gobuffalo/packr/v2 v2.8.0 github.com/google/go-cmp v0.4.0 github.com/google/go-github v17.0.0+incompatible @@ -20,28 +17,24 @@ require ( github.com/gorilla/mux v1.7.4 github.com/jinzhu/gorm v1.9.12 github.com/joho/godotenv v1.3.0 - github.com/justincampbell/bigduration v0.0.0-20160531141349-e45bf03c0666 // indirect - github.com/justincampbell/timeago v0.0.0-20160528003754-027f40306f1d github.com/karrick/godirwalk v1.15.6 // indirect - github.com/lib/pq v1.4.0 + github.com/lib/pq v1.5.2 github.com/mattn/go-colorable v0.1.6 // indirect github.com/mattn/go-sqlite3 v2.0.3+incompatible github.com/pkg/errors v0.9.1 github.com/radovskyb/watcher v1.0.7 github.com/robfig/cron v1.2.0 - github.com/rubenv/sql-migrate v0.0.0-20200423171638-eef9d3b68125 + github.com/rubenv/sql-migrate v0.0.0-20200429072036-ae26b214fa43 github.com/sergi/go-diff v1.1.0 - github.com/sirupsen/logrus v1.5.0 // indirect + github.com/sirupsen/logrus v1.6.0 // indirect github.com/spf13/cobra v1.0.0 github.com/spf13/pflag v1.0.5 // indirect - github.com/stripe/stripe-go v70.15.0+incompatible - golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5 - golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0 // indirect + golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 + golang.org/x/net v0.0.0-20200513185701-a91f0712d120 // indirect golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a // indirect - golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f // indirect + golang.org/x/sys v0.0.0-20200513112337-417ce2331b5c // indirect golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 - golang.org/x/tools v0.0.0-20200425043458-8463f397d07c // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df - gopkg.in/yaml.v2 v2.2.8 + gopkg.in/yaml.v2 v2.3.0 ) diff --git a/go.sum b/go.sum index bad19a68..9f6ee296 100644 --- a/go.sum +++ b/go.sum @@ -1,12 +1,8 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.37.4 h1:glPeL3BQJsbF6aIIYfZizMwc5LTYz250bDMjttbBGAU= -cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/PuerkitoBio/goquery v1.5.0 h1:uGvmFXOA73IKluu/F84Xd1tt/z07GYm8X49XKHP7EJk= -github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg= github.com/PuerkitoBio/goquery v1.5.1 h1:PSPBGne8NIUWw+/7vFBV+kG2J/5MOjbzc7154OaKCSE= github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= @@ -17,10 +13,10 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/andybalholm/cascadia v1.0.0 h1:hOCXnnZ5A+3eVDX8pvgl4kofXv2ELss0bKcqRySc45o= -github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5zzsLTo= github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= +github.com/andybalholm/cascadia v1.2.0 h1:vuRCkM5Ozh/BfmsaTm26kbjm0mIOM3yS5Ek/F5h18aE= +github.com/andybalholm/cascadia v1.2.0/go.mod h1:YCyR8vOZT9aZ1CHEd8ap0gMVm2aFgxBp0T0eFw1RUQY= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= @@ -61,9 +57,8 @@ github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7Do github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3 h1:tkum0XDgfR0jcVVXuTsYv/erY2NnEDqwRojbxR1rBYA= -github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM= github.com/denisenkom/go-mssqldb v0.0.0-20191001013358-cfbb681360f0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM= github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= @@ -71,8 +66,6 @@ github.com/dnote/actions v0.2.0 h1:P1ut2/QRKwfAzIIB374vN9A4IanU94C/payEocvngYo= github.com/dnote/actions v0.2.0/go.mod h1:bBIassLhppVQdbC3iaE92SHBpM1HOVe+xZoAlj9ROxw= github.com/dnote/color v1.7.0 h1:8/QGLQKSU8/zcWQaHbMyC1hJRkKO/Uu9M89sH76ecHE= github.com/dnote/color v1.7.0/go.mod h1:75UcP/TH7CNvjQ5pwDumkUS3vkPdGggy7/3fT8MlxHM= -github.com/dnote/xgo v0.0.0-20200205013105-40be7d6d43ff h1:DJKdzouhr6u1NzuLbmSWeei9BagH3Nm4mSOzP0RMdc0= -github.com/dnote/xgo v0.0.0-20200205013105-40be7d6d43ff/go.mod h1:ruGZjl8WThApI7BAIKV2Q/PnJoudvd6Epjc3z79jWVg= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= @@ -101,8 +94,6 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= github.com/gobuffalo/envy v1.7.1 h1:OQl5ys5MBea7OGCdvPbBJWRgnhC/fGona6QKfvFeau8= github.com/gobuffalo/envy v1.7.1/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w= -github.com/gobuffalo/envy v1.9.0/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w= -github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs= github.com/gobuffalo/logger v1.0.1 h1:ZEgyRGgAm4ZAhAO45YXMs5Fp+bzGLESFewzAVBMKuTg= github.com/gobuffalo/logger v1.0.1/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs= github.com/gobuffalo/logger v1.0.3 h1:YaXOTHNPCvkqqA7w05A4v0k2tCdpr+sgFlgINbQ6gqc= @@ -111,9 +102,6 @@ github.com/gobuffalo/packd v0.3.0 h1:eMwymTkA1uXsqxS0Tpoop3Lc0u3kTfiMBE6nKtQU4g4 github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q= github.com/gobuffalo/packd v1.0.0 h1:6ERZvJHfe24rfFmA9OaoKBdC7+c9sydrytMg8SdFGBM= github.com/gobuffalo/packd v1.0.0/go.mod h1:6VTc4htmJRFB7u1m/4LeMTWjFoYrUiBkU9Fdec9hrhI= -github.com/gobuffalo/packr v1.30.1 h1:hu1fuVR3fXEZR7rXNW3h8rqSML8EVAf6KNm0NKO/wKg= -github.com/gobuffalo/packr v1.30.1/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIaviy289eRuk= -github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw= github.com/gobuffalo/packr/v2 v2.7.1 h1:n3CIW5T17T8v4GGK5sWXLVWJhCz7b5aNLSxW6gYim4o= github.com/gobuffalo/packr/v2 v2.7.1/go.mod h1:qYEvAazPaVxy7Y7KR0W8qYEE+RymX74kETFqjFoFlOc= github.com/gobuffalo/packr/v2 v2.8.0 h1:IULGd15bQL59ijXLxEvA5wlMxsmx/ZkQv9T282zNVIY= @@ -123,13 +111,13 @@ github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFG github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -147,20 +135,15 @@ github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+u github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.2 h1:zoNxOV7WjqXptQOVngLmcSQgXmgk4NMz1HibBchjl/I= -github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc= github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= @@ -197,8 +180,6 @@ github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmK github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= -github.com/jinzhu/gorm v1.9.9 h1:Gc8bP20O+vroFUzZEXA1r7vNGQZGQ+RKgOnriuNF3ds= -github.com/jinzhu/gorm v1.9.9/go.mod h1:Kh6hTsSGffh4ui079FHrR5Gg+5D0hgihqDcsDN2BBJY= github.com/jinzhu/gorm v1.9.12 h1:Drgk1clyWT9t9ERbzHza6Mj/8FY/CqMyVzOiHviMo6Q= github.com/jinzhu/gorm v1.9.12/go.mod h1:vhTjlKSJUTWNtcbQtrMBFCxy7eXTzeCAzfL5fBZT/Qs= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= @@ -212,14 +193,8 @@ github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/justincampbell/bigduration v0.0.0-20160531141349-e45bf03c0666 h1:abLciEiilfMf19Q1TFWDrp9j5z5one60dnnpvc6eabg= -github.com/justincampbell/bigduration v0.0.0-20160531141349-e45bf03c0666/go.mod h1:xqGOmDZzLOG7+q/CgsbXv10g4tgPsbjhmAxyaTJMvis= -github.com/justincampbell/timeago v0.0.0-20160528003754-027f40306f1d h1:qtCcYJK2bebPXEC8Wy+enYxQqmWnT6jlVTHnDGpwvkc= -github.com/justincampbell/timeago v0.0.0-20160528003754-027f40306f1d/go.mod h1:U7FWcK1jzZJnYuSnxP6efX3ZoHbK1CEpD0ThYyGNPNI= -github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= github.com/karrick/godirwalk v1.15.3 h1:0a2pXOgtB16CqIqXTiT7+K9L73f74n/aNQUnH6Ortew= github.com/karrick/godirwalk v1.15.3/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= github.com/karrick/godirwalk v1.15.6 h1:Yf2mmR8TJy+8Fa0SuQVto5SYap6IF7lNVX4Jdl8G1qA= @@ -229,6 +204,8 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -238,8 +215,8 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4= github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.4.0 h1:TmtCFbH+Aw0AixwyttznSMQDgbR5Yed/Gg6S8Funrhc= -github.com/lib/pq v1.4.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.5.2 h1:yTSXVswvWUOQ3k1sd7vJfDrbSl8lKuscqFJRqjC0ifw= +github.com/lib/pq v1.5.2/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= @@ -256,15 +233,11 @@ github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+v github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-oci8 v0.0.7/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o= -github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.12.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v2.0.1+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= @@ -359,25 +332,21 @@ github.com/rogpeppe/go-internal v1.4.0 h1:LUa41nrWTQNGhzdsZ5lTnkwbNjj6rXTdazA1cS github.com/rogpeppe/go-internal v1.4.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.5.2 h1:qLvObTrvO/XRCqmkKxUlOBc48bI3efyDuAZe25QiF0w= github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rubenv/sql-migrate v0.0.0-20190618074426-f4d34eae5a5c h1:LCELEbde3/GT141OpHRs+jJZrI1tI3ayVd4VqW7Ui2U= -github.com/rubenv/sql-migrate v0.0.0-20190618074426-f4d34eae5a5c/go.mod h1:WS0rl9eEliYI8DPnr3TOwz4439pay+qNgzJoVya/DmY= -github.com/rubenv/sql-migrate v0.0.0-20200423171638-eef9d3b68125 h1:TNreUp2iVj8BSG7NrBFUeq5UoAGK7fWas/Eb4jlwKlY= -github.com/rubenv/sql-migrate v0.0.0-20200423171638-eef9d3b68125/go.mod h1:DCgfY80j8GYL7MLEfvcpSFvjD0L5yZq/aZUJmhZklyg= +github.com/rubenv/sql-migrate v0.0.0-20200429072036-ae26b214fa43 h1:0i6uTtxUGc/jpK/CngM4T2S2NFnqYUUxH+lKDgBLw8U= +github.com/rubenv/sql-migrate v0.0.0-20200429072036-ae26b214fa43/go.mod h1:DCgfY80j8GYL7MLEfvcpSFvjD0L5yZq/aZUJmhZklyg= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= -github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.5.0 h1:1N5EYkVAPEywqZRJd7cwnRtCb6xJx7NH3T3WUTF980Q= -github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo= +github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= @@ -408,11 +377,8 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stripe/stripe-go v61.7.1+incompatible h1:sflLf/SPZxu81RtdypT48tjw6/NYQX55JCSuEm0rkWs= -github.com/stripe/stripe-go v61.7.1+incompatible/go.mod h1:A1dQZmO/QypXmsL0T8axYZkSN/uA/T/A64pfKdBAMiY= -github.com/stripe/stripe-go v70.15.0+incompatible h1:hNML7M1zx8RgtepEMlxyu/FpVPrP7KZm1gPFQquJQvM= -github.com/stripe/stripe-go v70.15.0+incompatible/go.mod h1:A1dQZmO/QypXmsL0T8axYZkSN/uA/T/A64pfKdBAMiY= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= @@ -421,7 +387,6 @@ github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijb github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= @@ -450,8 +415,8 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5 h1:Q7tZBpemrlsc2I7IyODzhtallWRSm4Q0d09pL6XbQtU= -golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 h1:cg5LA/zNPRzIXIWSCxQW10Rvpy94aQh3LT/ShoCpkHw= +golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -482,10 +447,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200421231249-e086a090c8fd h1:QPwSajcTUrFriMF1nJ3XzgoqakqQEsnZf9LdXdi2nkI= -golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0 h1:Jcxah/M+oLZ/R4/z5RzfPzGbPXnVDPkEDtf2JnuxN+U= -golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120 h1:EZ3cVSzKOlJxAd8e8YAJ7no8nNypTxexh/YE/xW3ZEY= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -507,27 +470,22 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 h1:HyfiK1WMnHj5FXFXatD+Qs1A/xC2Run6RzeW1SyHxpc= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f h1:gWF768j/LaZugp8dyS4UwsslYCYz9XgFxvlgsn0n9H8= -golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200513112337-417ce2331b5c h1:kISX68E8gSkNYAFRFiDU8rl5RIn1sJYKYb/r2vMLDrU= +golang.org/x/sys v0.0.0-20200513112337-417ce2331b5c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -543,7 +501,6 @@ golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20191004055002-72853e10c5a3/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -551,20 +508,19 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200308013534-11ec41452d41 h1:9Di9iYgOt9ThCipBxChBVhgNipDoE5mxO84rQV7D0FE= golang.org/x/tools v0.0.0-20200308013534-11ec41452d41/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200425043458-8463f397d07c h1:iHhCR0b26amDCiiO+kBguKZom9aMF+NrFxh9zeKR/XU= -golang.org/x/tools v0.0.0-20200425043458-8463f397d07c/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= @@ -583,13 +539,12 @@ gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= -gopkg.in/gomail.v2 v2.0.0-20150902115704-41f357289737 h1:NvePS/smRcFQ4bMtTddFtknbGCtoBkJxGmpSpVRafCc= -gopkg.in/gomail.v2 v2.0.0-20150902115704-41f357289737/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw= gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE= gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw= gopkg.in/gorp.v1 v1.7.2 h1:j3DWlAyGVv8whO7AcIWznQ2Yj7yJkn34B8s63GViAAw= @@ -603,11 +558,10 @@ gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= diff --git a/jslib/src/services/index.ts b/jslib/src/services/index.ts index f486a177..ec403fcc 100644 --- a/jslib/src/services/index.ts +++ b/jslib/src/services/index.ts @@ -20,7 +20,6 @@ import { HttpClientConfig } from '../helpers/http'; import initUsersService from './users'; import initBooksService from './books'; import initNotesService from './notes'; -import initPaymentService from './payment'; // init initializes service helpers with the given http configuration // and returns an object of all services. @@ -28,12 +27,10 @@ export default function initServices(c: HttpClientConfig) { const usersService = initUsersService(c); const booksService = initBooksService(c); const notesService = initNotesService(c); - const paymentService = initPaymentService(c); return { users: usersService, books: booksService, - notes: notesService, - payment: paymentService + notes: notesService }; } diff --git a/jslib/src/services/payment.ts b/jslib/src/services/payment.ts deleted file mode 100644 index d2539c02..00000000 --- a/jslib/src/services/payment.ts +++ /dev/null @@ -1,70 +0,0 @@ -/* Copyright (C) 2019, 2020 Monomax Software Pty Ltd - * - * This file is part of Dnote. - * - * Dnote is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Dnote is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Dnote. If not, see . - */ - -import { getHttpClient, HttpClientConfig } from '../helpers/http'; - -export default function init(config: HttpClientConfig) { - const client = getHttpClient(config); - - return { - createSubscription: ({ yearly, source, country }) => { - const payload = { - yearly, - source, - country - }; - - return client.post('/subscriptions', payload); - }, - - getSubscription: () => { - return client.get('/subscriptions'); - }, - - cancelSubscription: ({ subscriptionId }) => { - const data = { - op: 'cancel', - stripe_subscription_id: subscriptionId - }; - - return client.patch('/subscriptions', data); - }, - - reactivateSubscription: ({ subscriptionId }) => { - const data = { - op: 'reactivate', - stripe_subscription_id: subscriptionId - }; - - return client.patch('/subscriptions', data); - }, - - getSource: () => { - return client.get('/stripe_source'); - }, - - updateSource: ({ source, country }) => { - const payload = { - source, - country - }; - - return client.patch('/stripe_source', payload); - } - }; -} diff --git a/jslib/src/services/users.ts b/jslib/src/services/users.ts index b872031d..ed2cd0c6 100644 --- a/jslib/src/services/users.ts +++ b/jslib/src/services/users.ts @@ -21,6 +21,7 @@ import { EmailPrefData, UserData } from '../operations/types'; export interface UpdateProfileParams { email: string; + password: string; } export interface UpdatePasswordParams { @@ -83,9 +84,10 @@ export default function init(config: HttpClientConfig) { return client.patch('/account/profile', payload); }, - updateProfile: ({ email }: UpdateProfileParams) => { + updateProfile: ({ email, password }: UpdateProfileParams) => { const payload = { - email + email, + password }; return client.patch('/account/profile', payload); diff --git a/pkg/assert/assert.go b/pkg/assert/assert.go index c1abdaf4..24c65bd7 100644 --- a/pkg/assert/assert.go +++ b/pkg/assert/assert.go @@ -87,6 +87,14 @@ func NotEqual(t *testing.T, a, b interface{}, message string) { } } +// NotEqualf fails a test if the actual matches the expected +func NotEqualf(t *testing.T, a, b interface{}, message string) { + ok, m := checkEqual(a, b, message) + if ok { + t.Fatal(m) + } +} + // DeepEqual fails a test if the actual does not deeply equal the expected func DeepEqual(t *testing.T, a, b interface{}, message string) { if cmp.Equal(a, b) { diff --git a/pkg/server/.gitignore b/pkg/server/.gitignore index 0494e979..fa49c96a 100644 --- a/pkg/server/.gitignore +++ b/pkg/server/.gitignore @@ -6,11 +6,3 @@ test-dnote /dist /build server - -# Elastic Beanstalk Files -/tmp -application.zip -test-api -/dump -api -/build diff --git a/pkg/server/api/auth.go b/pkg/server/api/auth.go new file mode 100644 index 00000000..678ad5d0 --- /dev/null +++ b/pkg/server/api/auth.go @@ -0,0 +1,181 @@ +/* Copyright (C) 2019, 2020 Monomax Software Pty Ltd + * + * This file is part of Dnote. + * + * Dnote is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Dnote is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Dnote. If not, see . + */ + +package api + +import ( + "encoding/json" + "net/http" + "time" + + "github.com/dnote/dnote/pkg/server/database" + "github.com/dnote/dnote/pkg/server/handlers" + "github.com/dnote/dnote/pkg/server/helpers" + "github.com/dnote/dnote/pkg/server/log" + "github.com/dnote/dnote/pkg/server/mailer" + "github.com/dnote/dnote/pkg/server/session" + "github.com/dnote/dnote/pkg/server/token" + "github.com/pkg/errors" + "golang.org/x/crypto/bcrypt" +) + +// GetMeResponse is the response for getMe endpoint +type GetMeResponse struct { + User session.Session `json:"user"` +} + +func (a *API) getMe(w http.ResponseWriter, r *http.Request) { + user, ok := r.Context().Value(helpers.KeyUser).(database.User) + if !ok { + handlers.DoError(w, "No authenticated user found", nil, http.StatusInternalServerError) + return + } + + var account database.Account + if err := a.App.DB.Where("user_id = ?", user.ID).First(&account).Error; err != nil { + handlers.DoError(w, "finding account", err, http.StatusInternalServerError) + return + } + + tx := a.App.DB.Begin() + if err := a.App.TouchLastLoginAt(user, tx); err != nil { + tx.Rollback() + // In case of an error, gracefully continue to avoid disturbing the service + log.ErrorWrap(err, "error touching last_login_at") + } + tx.Commit() + + response := GetMeResponse{ + User: session.New(user, account), + } + handlers.RespondJSON(w, http.StatusOK, response) +} + +type createResetTokenPayload struct { + Email string `json:"email"` +} + +func (a *API) createResetToken(w http.ResponseWriter, r *http.Request) { + var params createResetTokenPayload + if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil { + http.Error(w, "invalid payload", http.StatusBadRequest) + return + } + + var account database.Account + conn := a.App.DB.Where("email = ?", params.Email).First(&account) + if conn.RecordNotFound() { + return + } + if err := conn.Error; err != nil { + handlers.DoError(w, errors.Wrap(err, "finding account").Error(), nil, http.StatusInternalServerError) + return + } + + resetToken, err := token.Create(a.App.DB, account.UserID, database.TokenTypeResetPassword) + if err != nil { + handlers.DoError(w, errors.Wrap(err, "generating token").Error(), nil, http.StatusInternalServerError) + return + } + + if err := a.App.SendPasswordResetEmail(account.Email.String, resetToken.Value); err != nil { + if errors.Cause(err) == mailer.ErrSMTPNotConfigured { + handlers.RespondInvalidSMTPConfig(w) + } else { + handlers.DoError(w, errors.Wrap(err, "sending password reset email").Error(), nil, http.StatusInternalServerError) + } + + return + } +} + +type resetPasswordPayload struct { + Password string `json:"password"` + Token string `json:"token"` +} + +func (a *API) resetPassword(w http.ResponseWriter, r *http.Request) { + var params resetPasswordPayload + if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil { + http.Error(w, "invalid payload", http.StatusBadRequest) + return + } + + var token database.Token + conn := a.App.DB.Where("value = ? AND type =? AND used_at IS NULL", params.Token, database.TokenTypeResetPassword).First(&token) + if conn.RecordNotFound() { + http.Error(w, "invalid token", http.StatusBadRequest) + return + } + if err := conn.Error; err != nil { + handlers.DoError(w, errors.Wrap(err, "finding token").Error(), nil, http.StatusInternalServerError) + return + } + + if token.UsedAt != nil { + http.Error(w, "invalid token", http.StatusBadRequest) + return + } + + // Expire after 10 minutes + if time.Since(token.CreatedAt).Minutes() > 10 { + http.Error(w, "This link has been expired. Please request a new password reset link.", http.StatusGone) + return + } + + tx := a.App.DB.Begin() + + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(params.Password), bcrypt.DefaultCost) + if err != nil { + tx.Rollback() + handlers.DoError(w, errors.Wrap(err, "hashing password").Error(), nil, http.StatusInternalServerError) + return + } + + var account database.Account + if err := a.App.DB.Where("user_id = ?", token.UserID).First(&account).Error; err != nil { + tx.Rollback() + handlers.DoError(w, errors.Wrap(err, "finding user").Error(), nil, http.StatusInternalServerError) + return + } + + if err := tx.Model(&account).Update("password", string(hashedPassword)).Error; err != nil { + tx.Rollback() + handlers.DoError(w, errors.Wrap(err, "updating password").Error(), nil, http.StatusInternalServerError) + return + } + if err := tx.Model(&token).Update("used_at", time.Now()).Error; err != nil { + tx.Rollback() + handlers.DoError(w, errors.Wrap(err, "updating password reset token").Error(), nil, http.StatusInternalServerError) + return + } + + tx.Commit() + + var user database.User + if err := a.App.DB.Where("id = ?", account.UserID).First(&user).Error; err != nil { + handlers.DoError(w, errors.Wrap(err, "finding user").Error(), nil, http.StatusInternalServerError) + return + } + + a.respondWithSession(a.App.DB, w, user.ID, http.StatusOK) + + if err := a.App.SendPasswordResetAlertEmail(account.Email.String); err != nil { + log.ErrorWrap(err, "sending password reset email") + } +} diff --git a/pkg/server/handlers/auth_test.go b/pkg/server/api/auth_test.go similarity index 84% rename from pkg/server/handlers/auth_test.go rename to pkg/server/api/auth_test.go index 8ca3574b..3f6beabd 100644 --- a/pkg/server/handlers/auth_test.go +++ b/pkg/server/api/auth_test.go @@ -16,9 +16,11 @@ * along with Dnote. If not, see . */ -package handlers +package api import ( + "encoding/json" + "fmt" "net/http" "testing" "time" @@ -27,13 +29,15 @@ import ( "github.com/dnote/dnote/pkg/clock" "github.com/dnote/dnote/pkg/server/app" "github.com/dnote/dnote/pkg/server/database" + "github.com/dnote/dnote/pkg/server/session" "github.com/dnote/dnote/pkg/server/testutils" + "github.com/pkg/errors" "golang.org/x/crypto/bcrypt" ) func TestGetMe(t *testing.T) { testutils.InitTestDB() - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) // Setup server := MustNewServer(t, &app.App{ @@ -41,26 +45,64 @@ func TestGetMe(t *testing.T) { }) defer server.Close() - u := testutils.SetupUserData() - testutils.SetupAccountData(u, "alice@example.com", "somepassword") + u1 := testutils.SetupUserData() + a1 := testutils.SetupAccountData(u1, "alice@example.com", "somepassword") - dat := `{"email": "alice@example.com"}` - req := testutils.MakeReq(server.URL, "POST", "/reset-token", dat) + u2 := testutils.SetupUserData() + testutils.MustExec(t, testutils.DB.Model(&u2).Update("cloud", false), "preparing u2 cloud") + a2 := testutils.SetupAccountData(u2, "bob@example.com", "somepassword") - // Execute - res := testutils.HTTPAuthDo(t, req, u) + testCases := []struct { + user database.User + account database.Account + expectedPro bool + }{ + { + user: u1, + account: a1, + expectedPro: true, + }, + { + user: u2, + account: a2, + expectedPro: false, + }, + } - // Test - assert.StatusCodeEquals(t, res, http.StatusOK, "Status code mismtach") + for _, tc := range testCases { + t.Run(fmt.Sprintf("user pro %t", tc.expectedPro), func(t *testing.T) { + // Execute + req := testutils.MakeReq(server.URL, "GET", "/me", "") + res := testutils.HTTPAuthDo(t, req, tc.user) - var user database.User - testutils.MustExec(t, testutils.DB.Where("id = ?", u.ID).First(&user), "finding user") - assert.Equal(t, user.LastLoginAt, (*time.Time)(nil), "LastLoginAt mismatch") + // Test + assert.StatusCodeEquals(t, res, http.StatusOK, "") + + var payload GetMeResponse + if err := json.NewDecoder(res.Body).Decode(&payload); err != nil { + t.Fatal(errors.Wrap(err, "decoding payload")) + } + + expectedPayload := GetMeResponse{ + User: session.Session{ + UUID: tc.user.UUID, + Pro: tc.expectedPro, + Email: tc.account.Email.String, + EmailVerified: tc.account.EmailVerified, + }, + } + assert.DeepEqual(t, payload, expectedPayload, "payload mismatch") + + var user database.User + testutils.MustExec(t, testutils.DB.Where("id = ?", tc.user.ID).First(&user), "finding user") + assert.NotEqual(t, user.LastLoginAt, nil, "LastLoginAt mismatch") + }) + } } func TestCreateResetToken(t *testing.T) { t.Run("success", func(t *testing.T) { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) // Setup server := MustNewServer(t, &app.App{ @@ -94,7 +136,7 @@ func TestCreateResetToken(t *testing.T) { t.Run("nonexistent email", func(t *testing.T) { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) // Setup server := MustNewServer(t, &app.App{ @@ -124,7 +166,7 @@ func TestCreateResetToken(t *testing.T) { func TestResetPassword(t *testing.T) { t.Run("success", func(t *testing.T) { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) // Setup server := MustNewServer(t, &app.App{ @@ -171,7 +213,7 @@ func TestResetPassword(t *testing.T) { t.Run("nonexistent token", func(t *testing.T) { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) // Setup server := MustNewServer(t, &app.App{ @@ -210,7 +252,7 @@ func TestResetPassword(t *testing.T) { t.Run("expired token", func(t *testing.T) { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) // Setup server := MustNewServer(t, &app.App{ @@ -248,7 +290,7 @@ func TestResetPassword(t *testing.T) { t.Run("used token", func(t *testing.T) { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) // Setup server := MustNewServer(t, &app.App{ @@ -297,7 +339,7 @@ func TestResetPassword(t *testing.T) { t.Run("using wrong type token: email_verification", func(t *testing.T) { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) // Setup server := MustNewServer(t, &app.App{ diff --git a/pkg/server/handlers/health.go b/pkg/server/api/health.go similarity index 98% rename from pkg/server/handlers/health.go rename to pkg/server/api/health.go index 496f753e..739e8859 100644 --- a/pkg/server/handlers/health.go +++ b/pkg/server/api/health.go @@ -16,7 +16,7 @@ * along with Dnote. If not, see . */ -package handlers +package api import ( "net/http" diff --git a/pkg/server/handlers/health_test.go b/pkg/server/api/health_test.go similarity index 98% rename from pkg/server/handlers/health_test.go rename to pkg/server/api/health_test.go index 32f34379..193a2cd9 100644 --- a/pkg/server/handlers/health_test.go +++ b/pkg/server/api/health_test.go @@ -16,7 +16,7 @@ * along with Dnote. If not, see . */ -package handlers +package api import ( "net/http" diff --git a/pkg/server/api/helpers.go b/pkg/server/api/helpers.go new file mode 100644 index 00000000..8137939f --- /dev/null +++ b/pkg/server/api/helpers.go @@ -0,0 +1,85 @@ +/* Copyright (C) 2019, 2020 Monomax Software Pty Ltd + * + * This file is part of Dnote. + * + * Dnote is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Dnote is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Dnote. If not, see . + */ + +package api + +import ( + "net/http" + "strings" + + "github.com/dnote/dnote/pkg/server/database" + "github.com/jinzhu/gorm" + "github.com/pkg/errors" +) + +func paginate(conn *gorm.DB, page int) *gorm.DB { + limit := 30 + + // Paginate + if page > 0 { + offset := limit * (page - 1) + conn = conn.Offset(offset) + } + + conn = conn.Limit(limit) + + return conn +} + +func getBookIDs(books []database.Book) []int { + ret := []int{} + + for _, book := range books { + ret = append(ret, book.ID) + } + + return ret +} + +func validatePassword(password string) error { + if len(password) < 8 { + return errors.New("Password should be longer than 8 characters") + } + + return nil +} + +func getClientType(r *http.Request) string { + origin := r.Header.Get("Origin") + + if strings.HasPrefix(origin, "moz-extension://") { + return "firefox-extension" + } + + if strings.HasPrefix(origin, "chrome-extension://") { + return "chrome-extension" + } + + userAgent := r.Header.Get("User-Agent") + if strings.HasPrefix(userAgent, "Go-http-client") { + return "cli" + } + + return "web" +} + +// notSupported is the handler for the route that is no longer supported +func (a *API) notSupported(w http.ResponseWriter, r *http.Request) { + http.Error(w, "API version is not supported. Please upgrade your client.", http.StatusGone) + return +} diff --git a/web/src/components/Settings/Billing/PaymentMethodModal/Form.scss b/pkg/server/api/main_test.go similarity index 77% rename from web/src/components/Settings/Billing/PaymentMethodModal/Form.scss rename to pkg/server/api/main_test.go index 700c94bd..ee70a07d 100644 --- a/web/src/components/Settings/Billing/PaymentMethodModal/Form.scss +++ b/pkg/server/api/main_test.go @@ -16,12 +16,20 @@ * along with Dnote. If not, see . */ -@import '../../../App/rem'; -@import '../../../App/font'; -@import '../../../App/theme'; +package api -.input-row { - &:not(:first-child) { - margin-top: rem(10px); - } +import ( + "os" + "testing" + + "github.com/dnote/dnote/pkg/server/testutils" +) + +func TestMain(m *testing.M) { + testutils.InitTestDB() + + code := m.Run() + testutils.ClearData(testutils.DB) + + os.Exit(code) } diff --git a/pkg/server/handlers/notes.go b/pkg/server/api/notes.go similarity index 89% rename from pkg/server/handlers/notes.go rename to pkg/server/api/notes.go index 1890914e..8a745e41 100644 --- a/pkg/server/handlers/notes.go +++ b/pkg/server/api/notes.go @@ -16,7 +16,7 @@ * along with Dnote. If not, see . */ -package handlers +package api import ( "fmt" @@ -27,6 +27,7 @@ import ( "time" "github.com/dnote/dnote/pkg/server/database" + "github.com/dnote/dnote/pkg/server/handlers" "github.com/dnote/dnote/pkg/server/helpers" "github.com/dnote/dnote/pkg/server/operations" "github.com/dnote/dnote/pkg/server/presenters" @@ -77,7 +78,7 @@ ts_headline('english_nostop', notes.body, plainto_tsquery('english_nostop', ?), func respondWithNote(w http.ResponseWriter, note database.Note) { presentedNote := presenters.PresentNote(note) - respondJSON(w, http.StatusOK, presentedNote) + handlers.RespondJSON(w, http.StatusOK, presentedNote) } func parseSearchQuery(q url.Values) string { @@ -100,9 +101,9 @@ func getNoteBaseQuery(db *gorm.DB, noteUUID string, search string) *gorm.DB { } func (a *API) getNote(w http.ResponseWriter, r *http.Request) { - user, _, err := AuthWithSession(a.App.DB, r, nil) + user, _, err := handlers.AuthWithSession(a.App.DB, r, nil) if err != nil { - HandleError(w, "authenticating", err, http.StatusInternalServerError) + handlers.DoError(w, "authenticating", err, http.StatusInternalServerError) return } @@ -111,11 +112,11 @@ func (a *API) getNote(w http.ResponseWriter, r *http.Request) { note, ok, err := operations.GetNote(a.App.DB, noteUUID, user) if !ok { - RespondNotFound(w) + handlers.RespondNotFound(w) return } if err != nil { - HandleError(w, "finding note", err, http.StatusInternalServerError) + handlers.DoError(w, "finding note", err, http.StatusInternalServerError) return } @@ -138,7 +139,7 @@ type dateRange struct { func (a *API) getNotes(w http.ResponseWriter, r *http.Request) { user, ok := r.Context().Value(helpers.KeyUser).(database.User) if !ok { - HandleError(w, "No authenticated user found", nil, http.StatusInternalServerError) + handlers.DoError(w, "No authenticated user found", nil, http.StatusInternalServerError) return } query := r.URL.Query() @@ -157,7 +158,7 @@ func respondGetNotes(db *gorm.DB, userID int, query url.Values, w http.ResponseW var total int if err := conn.Model(database.Note{}).Count(&total).Error; err != nil { - HandleError(w, "counting total", err, http.StatusInternalServerError) + handlers.DoError(w, "counting total", err, http.StatusInternalServerError) return } @@ -168,7 +169,7 @@ func respondGetNotes(db *gorm.DB, userID int, query url.Values, w http.ResponseW conn = paginate(conn, q.Page) if err := conn.Find(¬es).Error; err != nil { - HandleError(w, "finding notes", err, http.StatusInternalServerError) + handlers.DoError(w, "finding notes", err, http.StatusInternalServerError) return } } @@ -177,7 +178,7 @@ func respondGetNotes(db *gorm.DB, userID int, query url.Values, w http.ResponseW Notes: presenters.PresentNotes(notes), Total: total, } - respondJSON(w, http.StatusOK, response) + handlers.RespondJSON(w, http.StatusOK, response) } type getNotesQuery struct { @@ -308,16 +309,16 @@ func escapeSearchQuery(searchQuery string) string { func (a *API) legacyGetNotes(w http.ResponseWriter, r *http.Request) { user, ok := r.Context().Value(helpers.KeyUser).(database.User) if !ok { - HandleError(w, "No authenticated user found", nil, http.StatusInternalServerError) + handlers.DoError(w, "No authenticated user found", nil, http.StatusInternalServerError) return } var notes []database.Note if err := a.App.DB.Where("user_id = ? AND encrypted = true", user.ID).Find(¬es).Error; err != nil { - HandleError(w, "finding notes", err, http.StatusInternalServerError) + handlers.DoError(w, "finding notes", err, http.StatusInternalServerError) return } presented := presenters.PresentNotes(notes) - respondJSON(w, http.StatusOK, presented) + handlers.RespondJSON(w, http.StatusOK, presented) } diff --git a/pkg/server/handlers/notes_test.go b/pkg/server/api/notes_test.go similarity index 99% rename from pkg/server/handlers/notes_test.go rename to pkg/server/api/notes_test.go index 9f7734ab..d0b0617b 100644 --- a/pkg/server/handlers/notes_test.go +++ b/pkg/server/api/notes_test.go @@ -16,7 +16,7 @@ * along with Dnote. If not, see . */ -package handlers +package api import ( "encoding/json" @@ -55,7 +55,7 @@ func getExpectedNotePayload(n database.Note, b database.Book, u database.User) p } func TestGetNotes(t *testing.T) { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) // Setup server := MustNewServer(t, &app.App{ @@ -165,7 +165,7 @@ func TestGetNotes(t *testing.T) { } func TestGetNote(t *testing.T) { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) // Setup server := MustNewServer(t, &app.App{ diff --git a/pkg/server/api/routes.go b/pkg/server/api/routes.go new file mode 100644 index 00000000..a1d8385d --- /dev/null +++ b/pkg/server/api/routes.go @@ -0,0 +1,116 @@ +/* Copyright (C) 2019, 2020 Monomax Software Pty Ltd + * + * This file is part of Dnote. + * + * Dnote is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Dnote is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Dnote. If not, see . + */ + +package api + +import ( + "net/http" + "os" + + "github.com/dnote/dnote/pkg/server/app" + "github.com/dnote/dnote/pkg/server/database" + "github.com/dnote/dnote/pkg/server/handlers" + "github.com/gorilla/mux" + "github.com/pkg/errors" +) + +// API is a web API configuration +type API struct { + App *app.App +} + +// init sets up the application based on the configuration +func (a *API) init() error { + if err := a.App.Validate(); err != nil { + return errors.Wrap(err, "validating the app parameters") + } + + return nil +} + +func applyMiddleware(h http.HandlerFunc, rateLimit bool) http.Handler { + ret := h + ret = handlers.Logging(ret) + + if rateLimit && os.Getenv("GO_ENV") != "TEST" { + ret = handlers.Limit(ret) + } + + return ret +} + +// NewRouter creates and returns a new router +func NewRouter(a *API) (*mux.Router, error) { + if err := a.init(); err != nil { + return nil, errors.Wrap(err, "initializing app") + } + + proOnly := handlers.AuthParams{ProOnly: true} + app := a.App + + var routes = []handlers.Route{ + // internal + {Method: "GET", Pattern: "/health", HandlerFunc: a.checkHealth, RateLimit: false}, + {Method: "GET", Pattern: "/me", HandlerFunc: handlers.Auth(app, a.getMe, nil), RateLimit: true}, + {Method: "POST", Pattern: "/verification-token", HandlerFunc: handlers.Auth(app, a.createVerificationToken, nil), RateLimit: true}, + {Method: "PATCH", Pattern: "/verify-email", HandlerFunc: a.verifyEmail, RateLimit: true}, + {Method: "POST", Pattern: "/reset-token", HandlerFunc: a.createResetToken, RateLimit: true}, + {Method: "PATCH", Pattern: "/reset-password", HandlerFunc: a.resetPassword, RateLimit: true}, + {Method: "PATCH", Pattern: "/account/profile", HandlerFunc: handlers.Auth(app, a.updateProfile, nil), RateLimit: true}, + {Method: "PATCH", Pattern: "/account/password", HandlerFunc: handlers.Auth(app, a.updatePassword, nil), RateLimit: true}, + {Method: "GET", Pattern: "/account/email-preference", HandlerFunc: handlers.TokenAuth(app, a.getEmailPreference, database.TokenTypeEmailPreference, nil), RateLimit: true}, + {Method: "PATCH", Pattern: "/account/email-preference", HandlerFunc: handlers.TokenAuth(app, a.updateEmailPreference, database.TokenTypeEmailPreference, nil), RateLimit: true}, + {Method: "GET", Pattern: "/notes", HandlerFunc: handlers.Auth(app, a.getNotes, nil), RateLimit: false}, + {Method: "GET", Pattern: "/notes/{noteUUID}", HandlerFunc: a.getNote, RateLimit: true}, + {Method: "GET", Pattern: "/calendar", HandlerFunc: handlers.Auth(app, a.getCalendar, nil), RateLimit: true}, + + // v3 + {Method: "GET", Pattern: "/v3/sync/fragment", HandlerFunc: handlers.Cors(handlers.Auth(app, a.GetSyncFragment, &proOnly)), RateLimit: false}, + {Method: "GET", Pattern: "/v3/sync/state", HandlerFunc: handlers.Cors(handlers.Auth(app, a.GetSyncState, &proOnly)), RateLimit: false}, + {Method: "OPTIONS", Pattern: "/v3/books", HandlerFunc: handlers.Cors(a.BooksOptions), RateLimit: true}, + {Method: "GET", Pattern: "/v3/books", HandlerFunc: handlers.Cors(handlers.Auth(app, a.GetBooks, &proOnly)), RateLimit: true}, + {Method: "GET", Pattern: "/v3/books/{bookUUID}", HandlerFunc: handlers.Cors(handlers.Auth(app, a.GetBook, &proOnly)), RateLimit: true}, + {Method: "POST", Pattern: "/v3/books", HandlerFunc: handlers.Cors(handlers.Auth(app, a.CreateBook, &proOnly)), RateLimit: false}, + {Method: "PATCH", Pattern: "/v3/books/{bookUUID}", HandlerFunc: handlers.Cors(handlers.Auth(app, a.UpdateBook, &proOnly)), RateLimit: false}, + {Method: "DELETE", Pattern: "/v3/books/{bookUUID}", HandlerFunc: handlers.Cors(handlers.Auth(app, a.DeleteBook, &proOnly)), RateLimit: false}, + {Method: "OPTIONS", Pattern: "/v3/notes", HandlerFunc: handlers.Cors(a.NotesOptions), RateLimit: true}, + {Method: "POST", Pattern: "/v3/notes", HandlerFunc: handlers.Cors(handlers.Auth(app, a.CreateNote, &proOnly)), RateLimit: false}, + {Method: "PATCH", Pattern: "/v3/notes/{noteUUID}", HandlerFunc: handlers.Auth(app, a.UpdateNote, &proOnly), RateLimit: false}, + {Method: "DELETE", Pattern: "/v3/notes/{noteUUID}", HandlerFunc: handlers.Auth(app, a.DeleteNote, &proOnly), RateLimit: false}, + {Method: "POST", Pattern: "/v3/signin", HandlerFunc: handlers.Cors(a.signin), RateLimit: true}, + {Method: "OPTIONS", Pattern: "/v3/signout", HandlerFunc: handlers.Cors(a.signoutOptions), RateLimit: true}, + {Method: "POST", Pattern: "/v3/signout", HandlerFunc: handlers.Cors(a.signout), RateLimit: true}, + {Method: "POST", Pattern: "/v3/register", HandlerFunc: a.register, RateLimit: true}, + } + + router := mux.NewRouter().StrictSlash(true) + + router.PathPrefix("/v1").Handler(applyMiddleware(handlers.NotSupported, true)) + router.PathPrefix("/v2").Handler(applyMiddleware(handlers.NotSupported, true)) + + for _, route := range routes { + handler := route.HandlerFunc + + router. + Methods(route.Method). + Path(route.Pattern). + Handler(applyMiddleware(handler, route.RateLimit)) + } + + return router, nil +} diff --git a/pkg/server/api/routes_test.go b/pkg/server/api/routes_test.go new file mode 100644 index 00000000..8a395f01 --- /dev/null +++ b/pkg/server/api/routes_test.go @@ -0,0 +1,161 @@ +/* Copyright (C) 2019, 2020 Monomax Software Pty Ltd + * + * This file is part of Dnote. + * + * Dnote is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Dnote is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Dnote. If not, see . + */ + +package api + +import ( + "fmt" + "net/http" + "testing" + + "github.com/dnote/dnote/pkg/assert" + "github.com/dnote/dnote/pkg/clock" + "github.com/dnote/dnote/pkg/server/app" + "github.com/dnote/dnote/pkg/server/config" + "github.com/dnote/dnote/pkg/server/mailer" + "github.com/dnote/dnote/pkg/server/testutils" + "github.com/jinzhu/gorm" + "github.com/pkg/errors" +) + +func TestNotSupportedVersions(t *testing.T) { + testCases := []struct { + path string + }{ + // v1 + { + path: "/v1", + }, + { + path: "/v1/foo", + }, + { + path: "/v1/bar/baz", + }, + // v2 + { + path: "/v2", + }, + { + path: "/v2/foo", + }, + { + path: "/v2/bar/baz", + }, + } + + // setup + server := MustNewServer(t, &app.App{ + DB: &gorm.DB{}, + Clock: clock.NewMock(), + }) + defer server.Close() + + for _, tc := range testCases { + t.Run(tc.path, func(t *testing.T) { + // execute + req := testutils.MakeReq(server.URL, "GET", tc.path, "") + res := testutils.HTTPDo(t, req) + + // test + assert.Equal(t, res.StatusCode, http.StatusGone, "status code mismatch") + }) + } +} + +func TestNewRouter_AppValidate(t *testing.T) { + c := config.Load() + + configWithoutWebURL := config.Load() + configWithoutWebURL.WebURL = "" + + testCases := []struct { + app app.App + expectedErr error + }{ + { + app: app.App{ + DB: &gorm.DB{}, + Clock: clock.NewMock(), + EmailTemplates: mailer.Templates{}, + EmailBackend: &testutils.MockEmailbackendImplementation{}, + Config: c, + }, + expectedErr: nil, + }, + { + app: app.App{ + DB: nil, + Clock: clock.NewMock(), + EmailTemplates: mailer.Templates{}, + EmailBackend: &testutils.MockEmailbackendImplementation{}, + Config: c, + }, + expectedErr: app.ErrEmptyDB, + }, + { + app: app.App{ + DB: &gorm.DB{}, + Clock: nil, + EmailTemplates: mailer.Templates{}, + EmailBackend: &testutils.MockEmailbackendImplementation{}, + Config: c, + }, + expectedErr: app.ErrEmptyClock, + }, + { + app: app.App{ + DB: &gorm.DB{}, + Clock: clock.NewMock(), + EmailTemplates: nil, + EmailBackend: &testutils.MockEmailbackendImplementation{}, + Config: c, + }, + expectedErr: app.ErrEmptyEmailTemplates, + }, + { + app: app.App{ + DB: &gorm.DB{}, + Clock: clock.NewMock(), + EmailTemplates: mailer.Templates{}, + EmailBackend: nil, + Config: c, + }, + expectedErr: app.ErrEmptyEmailBackend, + }, + { + app: app.App{ + DB: &gorm.DB{}, + Clock: clock.NewMock(), + EmailTemplates: mailer.Templates{}, + EmailBackend: &testutils.MockEmailbackendImplementation{}, + Config: configWithoutWebURL, + }, + expectedErr: app.ErrEmptyWebURL, + }, + } + + for idx, tc := range testCases { + t.Run(fmt.Sprintf("test case %d", idx), func(t *testing.T) { + api := API{App: &tc.app} + _, err := NewRouter(&api) + + assert.Equal(t, errors.Cause(err), tc.expectedErr, "error mismatch") + }) + } +} diff --git a/pkg/server/handlers/testutils.go b/pkg/server/api/testutils.go similarity index 96% rename from pkg/server/handlers/testutils.go rename to pkg/server/api/testutils.go index c19b7532..b8896475 100644 --- a/pkg/server/handlers/testutils.go +++ b/pkg/server/api/testutils.go @@ -16,7 +16,7 @@ * along with Dnote. If not, see . */ -package handlers +package api import ( "net/http/httptest" @@ -30,8 +30,7 @@ import ( // with the given app paratmers func MustNewServer(t *testing.T, appParams *app.App) *httptest.Server { api := NewTestAPI(appParams) - - r, err := api.NewRouter() + r, err := NewRouter(&api) if err != nil { t.Fatal(errors.Wrap(err, "initializing server")) } diff --git a/pkg/server/handlers/user.go b/pkg/server/api/user.go similarity index 75% rename from pkg/server/handlers/user.go rename to pkg/server/api/user.go index 578dc6da..04e41149 100644 --- a/pkg/server/handlers/user.go +++ b/pkg/server/api/user.go @@ -16,7 +16,7 @@ * along with Dnote. If not, see . */ -package handlers +package api import ( "encoding/json" @@ -24,10 +24,12 @@ import ( "time" "github.com/dnote/dnote/pkg/server/database" + "github.com/dnote/dnote/pkg/server/handlers" "github.com/dnote/dnote/pkg/server/helpers" "github.com/dnote/dnote/pkg/server/log" "github.com/dnote/dnote/pkg/server/mailer" "github.com/dnote/dnote/pkg/server/presenters" + "github.com/dnote/dnote/pkg/server/session" "github.com/dnote/dnote/pkg/server/token" "github.com/jinzhu/gorm" "github.com/pkg/errors" @@ -35,14 +37,21 @@ import ( ) type updateProfilePayload struct { - Email string `json:"email"` + Email string `json:"email"` + Password string `json:"password"` } // updateProfile updates user func (a *API) updateProfile(w http.ResponseWriter, r *http.Request) { user, ok := r.Context().Value(helpers.KeyUser).(database.User) if !ok { - HandleError(w, "No authenticated user found", nil, http.StatusInternalServerError) + handlers.DoError(w, "No authenticated user found", nil, http.StatusInternalServerError) + return + } + + var account database.Account + if err := a.App.DB.Where("user_id = ?", user.ID).First(&account).Error; err != nil { + handlers.DoError(w, "getting account", nil, http.StatusInternalServerError) return } @@ -53,23 +62,25 @@ func (a *API) updateProfile(w http.ResponseWriter, r *http.Request) { return } + password := []byte(params.Password) + if err := bcrypt.CompareHashAndPassword([]byte(account.Password.String), password); err != nil { + log.WithFields(log.Fields{ + "user_id": user.ID, + }).Warn("invalid email update attempt") + http.Error(w, "Wrong password", http.StatusUnauthorized) + return + } + // Validate if len(params.Email) > 60 { http.Error(w, "Email is too long", http.StatusBadRequest) return } - var account database.Account - err = a.App.DB.Where("user_id = ?", user.ID).First(&account).Error - if err != nil { - HandleError(w, "finding account", err, http.StatusInternalServerError) - return - } - tx := a.App.DB.Begin() if err := tx.Save(&user).Error; err != nil { tx.Rollback() - HandleError(w, "saving user", err, http.StatusInternalServerError) + handlers.DoError(w, "saving user", err, http.StatusInternalServerError) return } @@ -81,7 +92,7 @@ func (a *API) updateProfile(w http.ResponseWriter, r *http.Request) { if err := tx.Save(&account).Error; err != nil { tx.Rollback() - HandleError(w, "saving account", err, http.StatusInternalServerError) + handlers.DoError(w, "saving account", err, http.StatusInternalServerError) return } @@ -104,7 +115,7 @@ func respondWithCalendar(db *gorm.DB, w http.ResponseWriter, userID int) { Order("added_date DESC").Rows() if err != nil { - HandleError(w, "Failed to count lessons", err, http.StatusInternalServerError) + handlers.DoError(w, "Failed to count lessons", err, http.StatusInternalServerError) return } @@ -115,18 +126,18 @@ func respondWithCalendar(db *gorm.DB, w http.ResponseWriter, userID int) { var d time.Time if err := rows.Scan(&count, &d); err != nil { - HandleError(w, "counting notes", err, http.StatusInternalServerError) + handlers.DoError(w, "counting notes", err, http.StatusInternalServerError) } payload[d.Format("2006-1-2")] = count } - respondJSON(w, http.StatusOK, payload) + handlers.RespondJSON(w, http.StatusOK, payload) } func (a *API) getCalendar(w http.ResponseWriter, r *http.Request) { user, ok := r.Context().Value(helpers.KeyUser).(database.User) if !ok { - HandleError(w, "No authenticated user found", nil, http.StatusInternalServerError) + handlers.DoError(w, "No authenticated user found", nil, http.StatusInternalServerError) return } @@ -136,14 +147,14 @@ func (a *API) getCalendar(w http.ResponseWriter, r *http.Request) { func (a *API) createVerificationToken(w http.ResponseWriter, r *http.Request) { user, ok := r.Context().Value(helpers.KeyUser).(database.User) if !ok { - HandleError(w, "No authenticated user found", nil, http.StatusInternalServerError) + handlers.DoError(w, "No authenticated user found", nil, http.StatusInternalServerError) return } var account database.Account err := a.App.DB.Where("user_id = ?", user.ID).First(&account).Error if err != nil { - HandleError(w, "finding account", err, http.StatusInternalServerError) + handlers.DoError(w, "finding account", err, http.StatusInternalServerError) return } @@ -158,15 +169,15 @@ func (a *API) createVerificationToken(w http.ResponseWriter, r *http.Request) { tok, err := token.Create(a.App.DB, account.UserID, database.TokenTypeEmailVerification) if err != nil { - HandleError(w, "saving token", err, http.StatusInternalServerError) + handlers.DoError(w, "saving token", err, http.StatusInternalServerError) return } if err := a.App.SendVerificationEmail(account.Email.String, tok.Value); err != nil { if errors.Cause(err) == mailer.ErrSMTPNotConfigured { - respondInvalidSMTPConfig(w) + handlers.RespondInvalidSMTPConfig(w) } else { - HandleError(w, errors.Wrap(err, "sending verification email").Error(), nil, http.StatusInternalServerError) + handlers.DoError(w, errors.Wrap(err, "sending verification email").Error(), nil, http.StatusInternalServerError) } return @@ -182,7 +193,7 @@ type verifyEmailPayload struct { func (a *API) verifyEmail(w http.ResponseWriter, r *http.Request) { var params verifyEmailPayload if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil { - HandleError(w, "decoding payload", err, http.StatusInternalServerError) + handlers.DoError(w, "decoding payload", err, http.StatusInternalServerError) return } @@ -207,7 +218,7 @@ func (a *API) verifyEmail(w http.ResponseWriter, r *http.Request) { var account database.Account if err := a.App.DB.Where("user_id = ?", token.UserID).First(&account).Error; err != nil { - HandleError(w, "finding account", err, http.StatusInternalServerError) + handlers.DoError(w, "finding account", err, http.StatusInternalServerError) return } if account.EmailVerified { @@ -219,24 +230,24 @@ func (a *API) verifyEmail(w http.ResponseWriter, r *http.Request) { account.EmailVerified = true if err := tx.Save(&account).Error; err != nil { tx.Rollback() - HandleError(w, "updating email_verified", err, http.StatusInternalServerError) + handlers.DoError(w, "updating email_verified", err, http.StatusInternalServerError) return } if err := tx.Model(&token).Update("used_at", time.Now()).Error; err != nil { tx.Rollback() - HandleError(w, "updating reset token", err, http.StatusInternalServerError) + handlers.DoError(w, "updating reset token", err, http.StatusInternalServerError) return } tx.Commit() var user database.User if err := a.App.DB.Where("id = ?", token.UserID).First(&user).Error; err != nil { - HandleError(w, "finding user", err, http.StatusInternalServerError) + handlers.DoError(w, "finding user", err, http.StatusInternalServerError) return } - session := makeSession(user, account) - respondJSON(w, http.StatusOK, session) + s := session.New(user, account) + handlers.RespondJSON(w, http.StatusOK, s) } type emailPreferernceParams struct { @@ -263,19 +274,19 @@ func (p emailPreferernceParams) getProductUpdate() bool { func (a *API) updateEmailPreference(w http.ResponseWriter, r *http.Request) { user, ok := r.Context().Value(helpers.KeyUser).(database.User) if !ok { - HandleError(w, "No authenticated user found", nil, http.StatusInternalServerError) + handlers.DoError(w, "No authenticated user found", nil, http.StatusInternalServerError) return } var params emailPreferernceParams if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil { - HandleError(w, "decoding payload", err, http.StatusInternalServerError) + handlers.DoError(w, "decoding payload", err, http.StatusInternalServerError) return } var pref database.EmailPreference if err := a.App.DB.Where(database.EmailPreference{UserID: user.ID}).FirstOrCreate(&pref).Error; err != nil { - HandleError(w, "finding pref", err, http.StatusInternalServerError) + handlers.DoError(w, "finding pref", err, http.StatusInternalServerError) return } @@ -290,7 +301,7 @@ func (a *API) updateEmailPreference(w http.ResponseWriter, r *http.Request) { if err := tx.Save(&pref).Error; err != nil { tx.Rollback() - HandleError(w, "saving pref", err, http.StatusInternalServerError) + handlers.DoError(w, "saving pref", err, http.StatusInternalServerError) return } @@ -299,31 +310,31 @@ func (a *API) updateEmailPreference(w http.ResponseWriter, r *http.Request) { // Mark token as used if the user was authenticated by token if err := tx.Model(&token).Update("used_at", time.Now()).Error; err != nil { tx.Rollback() - HandleError(w, "updating reset token", err, http.StatusInternalServerError) + handlers.DoError(w, "updating reset token", err, http.StatusInternalServerError) return } } tx.Commit() - respondJSON(w, http.StatusOK, pref) + handlers.RespondJSON(w, http.StatusOK, pref) } func (a *API) getEmailPreference(w http.ResponseWriter, r *http.Request) { user, ok := r.Context().Value(helpers.KeyUser).(database.User) if !ok { - HandleError(w, "No authenticated user found", nil, http.StatusInternalServerError) + handlers.DoError(w, "No authenticated user found", nil, http.StatusInternalServerError) return } var pref database.EmailPreference if err := a.App.DB.Where(database.EmailPreference{UserID: user.ID}).First(&pref).Error; err != nil { - HandleError(w, "finding pref", err, http.StatusInternalServerError) + handlers.DoError(w, "finding pref", err, http.StatusInternalServerError) return } presented := presenters.PresentEmailPreference(pref) - respondJSON(w, http.StatusOK, presented) + handlers.RespondJSON(w, http.StatusOK, presented) } type updatePasswordPayload struct { @@ -334,7 +345,7 @@ type updatePasswordPayload struct { func (a *API) updatePassword(w http.ResponseWriter, r *http.Request) { user, ok := r.Context().Value(helpers.KeyUser).(database.User) if !ok { - HandleError(w, "No authenticated user found", nil, http.StatusInternalServerError) + handlers.DoError(w, "No authenticated user found", nil, http.StatusInternalServerError) return } @@ -350,7 +361,7 @@ func (a *API) updatePassword(w http.ResponseWriter, r *http.Request) { var account database.Account if err := a.App.DB.Where("user_id = ?", user.ID).First(&account).Error; err != nil { - HandleError(w, "getting user", nil, http.StatusInternalServerError) + handlers.DoError(w, "getting account", nil, http.StatusInternalServerError) return } diff --git a/pkg/server/handlers/user_test.go b/pkg/server/api/user_test.go similarity index 91% rename from pkg/server/handlers/user_test.go rename to pkg/server/api/user_test.go index 986dbe28..19376300 100644 --- a/pkg/server/handlers/user_test.go +++ b/pkg/server/api/user_test.go @@ -16,7 +16,7 @@ * along with Dnote. If not, see . */ -package handlers +package api import ( "encoding/json" @@ -37,8 +37,7 @@ import ( func TestUpdatePassword(t *testing.T) { t.Run("success", func(t *testing.T) { - - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) // Setup server := MustNewServer(t, &app.App{ @@ -65,8 +64,7 @@ func TestUpdatePassword(t *testing.T) { }) t.Run("old password mismatch", func(t *testing.T) { - - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) // Setup server := MustNewServer(t, &app.App{ @@ -92,8 +90,7 @@ func TestUpdatePassword(t *testing.T) { }) t.Run("password too short", func(t *testing.T) { - - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) // Setup server := MustNewServer(t, &app.App{ @@ -121,7 +118,7 @@ func TestUpdatePassword(t *testing.T) { func TestCreateVerificationToken(t *testing.T) { t.Run("success", func(t *testing.T) { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) // Setup emailBackend := testutils.MockEmailbackendImplementation{} @@ -157,7 +154,7 @@ func TestCreateVerificationToken(t *testing.T) { t.Run("already verified", func(t *testing.T) { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) // Setup server := MustNewServer(t, &app.App{ @@ -191,7 +188,7 @@ func TestCreateVerificationToken(t *testing.T) { func TestVerifyEmail(t *testing.T) { t.Run("success", func(t *testing.T) { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) // Setup server := MustNewServer(t, &app.App{ @@ -232,7 +229,7 @@ func TestVerifyEmail(t *testing.T) { t.Run("used token", func(t *testing.T) { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) // Setup server := MustNewServer(t, &app.App{ @@ -276,7 +273,7 @@ func TestVerifyEmail(t *testing.T) { t.Run("expired token", func(t *testing.T) { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) // Setup server := MustNewServer(t, &app.App{ @@ -318,7 +315,7 @@ func TestVerifyEmail(t *testing.T) { t.Run("already verified", func(t *testing.T) { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) // Setup server := MustNewServer(t, &app.App{ @@ -362,7 +359,7 @@ func TestVerifyEmail(t *testing.T) { func TestUpdateEmail(t *testing.T) { t.Run("success", func(t *testing.T) { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) // Setup server := MustNewServer(t, &app.App{ @@ -376,7 +373,7 @@ func TestUpdateEmail(t *testing.T) { testutils.MustExec(t, testutils.DB.Save(&a), "updating email_verified") // Execute - dat := `{"email": "alice-new@example.com"}` + dat := `{"email": "alice-new@example.com", "password": "pass1234"}` req := testutils.MakeReq(server.URL, "PATCH", "/account/profile", dat) res := testutils.HTTPAuthDo(t, req, u) @@ -391,11 +388,42 @@ func TestUpdateEmail(t *testing.T) { assert.Equal(t, account.Email.String, "alice-new@example.com", "email mismatch") assert.Equal(t, account.EmailVerified, false, "EmailVerified mismatch") }) + + t.Run("password mismatch", func(t *testing.T) { + defer testutils.ClearData(testutils.DB) + + // Setup + server := MustNewServer(t, &app.App{ + Clock: clock.NewMock(), + }) + defer server.Close() + + u := testutils.SetupUserData() + a := testutils.SetupAccountData(u, "alice@example.com", "pass1234") + a.EmailVerified = true + testutils.MustExec(t, testutils.DB.Save(&a), "updating email_verified") + + // Execute + dat := `{"email": "alice-new@example.com", "password": "wrongpassword"}` + req := testutils.MakeReq(server.URL, "PATCH", "/account/profile", dat) + res := testutils.HTTPAuthDo(t, req, u) + + // Test + assert.StatusCodeEquals(t, res, http.StatusUnauthorized, "Status code mismsatch") + + var user database.User + var account database.Account + testutils.MustExec(t, testutils.DB.Where("id = ?", u.ID).First(&user), "finding user") + testutils.MustExec(t, testutils.DB.Where("user_id = ?", u.ID).First(&account), "finding account") + + assert.Equal(t, account.Email.String, "alice@example.com", "email mismatch") + assert.Equal(t, account.EmailVerified, true, "EmailVerified mismatch") + }) } func TestUpdateEmailPreference(t *testing.T) { t.Run("with login", func(t *testing.T) { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) // Setup server := MustNewServer(t, &app.App{ @@ -420,7 +448,7 @@ func TestUpdateEmailPreference(t *testing.T) { }) t.Run("with an unused token", func(t *testing.T) { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) // Setup server := MustNewServer(t, &app.App{ @@ -459,7 +487,7 @@ func TestUpdateEmailPreference(t *testing.T) { }) t.Run("with nonexistent token", func(t *testing.T) { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) // Setup server := MustNewServer(t, &app.App{ @@ -492,7 +520,7 @@ func TestUpdateEmailPreference(t *testing.T) { }) t.Run("with expired token", func(t *testing.T) { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) // Setup server := MustNewServer(t, &app.App{ @@ -529,7 +557,7 @@ func TestUpdateEmailPreference(t *testing.T) { t.Run("with a used but unexpired token", func(t *testing.T) { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) // Setup server := MustNewServer(t, &app.App{ @@ -566,7 +594,7 @@ func TestUpdateEmailPreference(t *testing.T) { t.Run("no user and no token", func(t *testing.T) { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) // Setup server := MustNewServer(t, &app.App{ @@ -593,7 +621,7 @@ func TestUpdateEmailPreference(t *testing.T) { t.Run("create a record if not exists", func(t *testing.T) { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) // Setup server := MustNewServer(t, &app.App{ @@ -630,7 +658,7 @@ func TestUpdateEmailPreference(t *testing.T) { } func TestGetEmailPreference(t *testing.T) { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) // Setup server := MustNewServer(t, &app.App{ diff --git a/pkg/server/handlers/v3_auth.go b/pkg/server/api/v3_auth.go similarity index 84% rename from pkg/server/handlers/v3_auth.go rename to pkg/server/api/v3_auth.go index 23b03267..1c9ab145 100644 --- a/pkg/server/handlers/v3_auth.go +++ b/pkg/server/api/v3_auth.go @@ -16,7 +16,7 @@ * along with Dnote. If not, see . */ -package handlers +package api import ( "encoding/json" @@ -24,6 +24,7 @@ import ( "time" "github.com/dnote/dnote/pkg/server/database" + "github.com/dnote/dnote/pkg/server/handlers" "github.com/dnote/dnote/pkg/server/log" "github.com/jinzhu/gorm" "github.com/pkg/errors" @@ -50,20 +51,6 @@ func setSessionCookie(w http.ResponseWriter, key string, expires time.Time) { http.SetCookie(w, &cookie) } -func unsetSessionCookie(w http.ResponseWriter) { - expire := time.Now().Add(time.Hour * -24 * 30) - cookie := http.Cookie{ - Name: "id", - Value: "", - Expires: expire, - Path: "/", - HttpOnly: true, - } - - w.Header().Set("Cache-Control", "no-cache") - http.SetCookie(w, &cookie) -} - func touchLastLoginAt(db *gorm.DB, user database.User) error { t := time.Now() if err := db.Model(&user).Update(database.User{LastLoginAt: &t}).Error; err != nil { @@ -82,7 +69,7 @@ func (a *API) signin(w http.ResponseWriter, r *http.Request) { var params signinPayload err := json.NewDecoder(r.Body).Decode(¶ms) if err != nil { - HandleError(w, "decoding payload", err, http.StatusInternalServerError) + handlers.DoError(w, "decoding payload", err, http.StatusInternalServerError) return } if params.Email == "" || params.Password == "" { @@ -96,7 +83,7 @@ func (a *API) signin(w http.ResponseWriter, r *http.Request) { http.Error(w, ErrLoginFailure.Error(), http.StatusUnauthorized) return } else if conn.Error != nil { - HandleError(w, "getting user", err, http.StatusInternalServerError) + handlers.DoError(w, "getting user", err, http.StatusInternalServerError) return } @@ -110,7 +97,7 @@ func (a *API) signin(w http.ResponseWriter, r *http.Request) { var user database.User err = a.App.DB.Where("id = ?", account.UserID).First(&user).Error if err != nil { - HandleError(w, "finding user", err, http.StatusInternalServerError) + handlers.DoError(w, "finding user", err, http.StatusInternalServerError) return } @@ -129,9 +116,9 @@ func (a *API) signoutOptions(w http.ResponseWriter, r *http.Request) { } func (a *API) signout(w http.ResponseWriter, r *http.Request) { - key, err := getCredential(r) + key, err := handlers.GetCredential(r) if err != nil { - HandleError(w, "getting credential", nil, http.StatusInternalServerError) + handlers.DoError(w, "getting credential", nil, http.StatusInternalServerError) return } @@ -142,11 +129,11 @@ func (a *API) signout(w http.ResponseWriter, r *http.Request) { err = a.App.DeleteSession(key) if err != nil { - HandleError(w, "deleting session", nil, http.StatusInternalServerError) + handlers.DoError(w, "deleting session", nil, http.StatusInternalServerError) return } - unsetSessionCookie(w) + handlers.UnsetSessionCookie(w) w.WriteHeader(http.StatusNoContent) } @@ -177,7 +164,7 @@ func parseRegisterPaylaod(r *http.Request) (registerPayload, error) { func (a *API) register(w http.ResponseWriter, r *http.Request) { if a.App.Config.DisableRegistration { - respondForbidden(w) + handlers.RespondForbidden(w) return } @@ -193,7 +180,7 @@ func (a *API) register(w http.ResponseWriter, r *http.Request) { var count int if err := a.App.DB.Model(database.Account{}).Where("email = ?", params.Email).Count(&count).Error; err != nil { - HandleError(w, "checking duplicate user", err, http.StatusInternalServerError) + handlers.DoError(w, "checking duplicate user", err, http.StatusInternalServerError) return } if count > 0 { @@ -203,7 +190,7 @@ func (a *API) register(w http.ResponseWriter, r *http.Request) { user, err := a.App.CreateUser(params.Email, params.Password) if err != nil { - HandleError(w, "creating user", err, http.StatusInternalServerError) + handlers.DoError(w, "creating user", err, http.StatusInternalServerError) return } @@ -219,7 +206,7 @@ func (a *API) register(w http.ResponseWriter, r *http.Request) { func (a *API) respondWithSession(db *gorm.DB, w http.ResponseWriter, userID int, statusCode int) { session, err := a.App.CreateSession(userID) if err != nil { - HandleError(w, "creating session", nil, http.StatusBadRequest) + handlers.DoError(w, "creating session", nil, http.StatusBadRequest) return } @@ -233,7 +220,7 @@ func (a *API) respondWithSession(db *gorm.DB, w http.ResponseWriter, userID int, w.Header().Set("Content-Type", "application/json") w.WriteHeader(statusCode) if err := json.NewEncoder(w).Encode(response); err != nil { - HandleError(w, "encoding response", err, http.StatusInternalServerError) + handlers.DoError(w, "encoding response", err, http.StatusInternalServerError) return } } diff --git a/pkg/server/handlers/v3_auth_test.go b/pkg/server/api/v3_auth_test.go similarity index 96% rename from pkg/server/handlers/v3_auth_test.go rename to pkg/server/api/v3_auth_test.go index 964a6c6f..f08eea2b 100644 --- a/pkg/server/handlers/v3_auth_test.go +++ b/pkg/server/api/v3_auth_test.go @@ -16,7 +16,7 @@ * along with Dnote. If not, see . */ -package handlers +package api import ( "encoding/json" @@ -94,7 +94,7 @@ func TestRegister(t *testing.T) { for _, tc := range testCases { t.Run(fmt.Sprintf("register %s %s", tc.email, tc.password), func(t *testing.T) { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) c := config.Load() c.SetOnPremise(tc.onPremise) @@ -127,7 +127,6 @@ func TestRegister(t *testing.T) { var user database.User testutils.MustExec(t, testutils.DB.Where("id = ?", account.UserID).First(&user), "finding user") assert.Equal(t, user.Cloud, tc.expectedPro, "Cloud mismatch") - assert.Equal(t, user.StripeCustomerID, "", "StripeCustomerID mismatch") assert.Equal(t, user.MaxUSN, 0, "MaxUSN mismatch") // welcome email @@ -142,7 +141,7 @@ func TestRegister(t *testing.T) { func TestRegisterMissingParams(t *testing.T) { t.Run("missing email", func(t *testing.T) { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) // Setup server := MustNewServer(t, &app.App{ @@ -169,7 +168,7 @@ func TestRegisterMissingParams(t *testing.T) { t.Run("missing password", func(t *testing.T) { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) // Setup server := MustNewServer(t, &app.App{ @@ -196,7 +195,7 @@ func TestRegisterMissingParams(t *testing.T) { } func TestRegisterDuplicateEmail(t *testing.T) { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) // Setup server := MustNewServer(t, &app.App{ @@ -231,7 +230,7 @@ func TestRegisterDuplicateEmail(t *testing.T) { } func TestRegisterDisabled(t *testing.T) { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) c := config.Load() c.DisableRegistration = true @@ -262,7 +261,7 @@ func TestRegisterDisabled(t *testing.T) { func TestSignIn(t *testing.T) { t.Run("success", func(t *testing.T) { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) // Setup server := MustNewServer(t, &app.App{ @@ -292,7 +291,7 @@ func TestSignIn(t *testing.T) { }) t.Run("wrong password", func(t *testing.T) { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) // Setup server := MustNewServer(t, &app.App{ @@ -324,7 +323,7 @@ func TestSignIn(t *testing.T) { t.Run("wrong email", func(t *testing.T) { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) // Setup server := MustNewServer(t, &app.App{ @@ -356,7 +355,7 @@ func TestSignIn(t *testing.T) { t.Run("nonexistent email", func(t *testing.T) { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) // Setup server := MustNewServer(t, &app.App{ @@ -383,7 +382,7 @@ func TestSignIn(t *testing.T) { func TestSignout(t *testing.T) { t.Run("authenticated", func(t *testing.T) { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) aliceUser := testutils.SetupUserData() testutils.SetupAccountData(aliceUser, "alice@example.com", "pass1234") @@ -435,7 +434,7 @@ func TestSignout(t *testing.T) { t.Run("unauthenticated", func(t *testing.T) { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) aliceUser := testutils.SetupUserData() testutils.SetupAccountData(aliceUser, "alice@example.com", "pass1234") diff --git a/pkg/server/handlers/v3_books.go b/pkg/server/api/v3_books.go similarity index 81% rename from pkg/server/handlers/v3_books.go rename to pkg/server/api/v3_books.go index 5b76fb40..962d38af 100644 --- a/pkg/server/handlers/v3_books.go +++ b/pkg/server/api/v3_books.go @@ -16,7 +16,7 @@ * along with Dnote. If not, see . */ -package handlers +package api import ( "encoding/json" @@ -25,6 +25,7 @@ import ( "net/url" "github.com/dnote/dnote/pkg/server/database" + "github.com/dnote/dnote/pkg/server/handlers" "github.com/dnote/dnote/pkg/server/helpers" "github.com/dnote/dnote/pkg/server/presenters" "github.com/gorilla/mux" @@ -59,13 +60,13 @@ func (a *API) CreateBook(w http.ResponseWriter, r *http.Request) { var params createBookPayload err := json.NewDecoder(r.Body).Decode(¶ms) if err != nil { - HandleError(w, "decoding payload", err, http.StatusInternalServerError) + handlers.DoError(w, "decoding payload", err, http.StatusInternalServerError) return } err = validateCreateBookPayload(params) if err != nil { - HandleError(w, "validating payload", err, http.StatusBadRequest) + handlers.DoError(w, "validating payload", err, http.StatusBadRequest) return } @@ -74,7 +75,7 @@ func (a *API) CreateBook(w http.ResponseWriter, r *http.Request) { Where("user_id = ? AND label = ?", user.ID, params.Name). Count(&bookCount).Error if err != nil { - HandleError(w, "checking duplicate", err, http.StatusInternalServerError) + handlers.DoError(w, "checking duplicate", err, http.StatusInternalServerError) return } if bookCount > 0 { @@ -84,12 +85,12 @@ func (a *API) CreateBook(w http.ResponseWriter, r *http.Request) { book, err := a.App.CreateBook(user, params.Name) if err != nil { - HandleError(w, "inserting book", err, http.StatusInternalServerError) + handlers.DoError(w, "inserting book", err, http.StatusInternalServerError) } resp := CreateBookResp{ Book: presenters.PresentBook(book), } - respondJSON(w, http.StatusCreated, resp) + handlers.RespondJSON(w, http.StatusCreated, resp) } // BooksOptions is a handler for OPTIONS endpoint for notes @@ -120,12 +121,12 @@ func respondWithBooks(db *gorm.DB, userID int, query url.Values, w http.Response } if err := conn.Find(&books).Error; err != nil { - HandleError(w, "finding books", err, http.StatusInternalServerError) + handlers.DoError(w, "finding books", err, http.StatusInternalServerError) return } presentedBooks := presenters.PresentBooks(books) - respondJSON(w, http.StatusOK, presentedBooks) + handlers.RespondJSON(w, http.StatusOK, presentedBooks) } // GetBooks returns books for the user @@ -158,12 +159,12 @@ func (a *API) GetBook(w http.ResponseWriter, r *http.Request) { return } if err := conn.Error; err != nil { - HandleError(w, "finding book", err, http.StatusInternalServerError) + handlers.DoError(w, "finding book", err, http.StatusInternalServerError) return } p := presenters.PresentBook(book) - respondJSON(w, http.StatusOK, p) + handlers.RespondJSON(w, http.StatusOK, p) } type updateBookPayload struct { @@ -189,21 +190,21 @@ func (a *API) UpdateBook(w http.ResponseWriter, r *http.Request) { var book database.Book if err := tx.Where("user_id = ? AND uuid = ?", user.ID, uuid).First(&book).Error; err != nil { - HandleError(w, "finding book", err, http.StatusInternalServerError) + handlers.DoError(w, "finding book", err, http.StatusInternalServerError) return } var params updateBookPayload err := json.NewDecoder(r.Body).Decode(¶ms) if err != nil { - HandleError(w, "decoding payload", err, http.StatusInternalServerError) + handlers.DoError(w, "decoding payload", err, http.StatusInternalServerError) return } book, err = a.App.UpdateBook(tx, user, book, params.Name) if err != nil { tx.Rollback() - HandleError(w, "updating a book", err, http.StatusInternalServerError) + handlers.DoError(w, "updating a book", err, http.StatusInternalServerError) } tx.Commit() @@ -211,7 +212,7 @@ func (a *API) UpdateBook(w http.ResponseWriter, r *http.Request) { resp := UpdateBookResp{ Book: presenters.PresentBook(book), } - respondJSON(w, http.StatusOK, resp) + handlers.RespondJSON(w, http.StatusOK, resp) } // DeleteBookResp is the response from create book api @@ -234,25 +235,25 @@ func (a *API) DeleteBook(w http.ResponseWriter, r *http.Request) { var book database.Book if err := tx.Where("user_id = ? AND uuid = ?", user.ID, uuid).First(&book).Error; err != nil { - HandleError(w, "finding book", err, http.StatusInternalServerError) + handlers.DoError(w, "finding book", err, http.StatusInternalServerError) return } var notes []database.Note if err := tx.Where("book_uuid = ? AND NOT deleted", uuid).Order("usn ASC").Find(¬es).Error; err != nil { - HandleError(w, "finding notes", err, http.StatusInternalServerError) + handlers.DoError(w, "finding notes", err, http.StatusInternalServerError) return } for _, note := range notes { if _, err := a.App.DeleteNote(tx, user, note); err != nil { - HandleError(w, "deleting a note", err, http.StatusInternalServerError) + handlers.DoError(w, "deleting a note", err, http.StatusInternalServerError) return } } b, err := a.App.DeleteBook(tx, user, book) if err != nil { - HandleError(w, "deleting book", err, http.StatusInternalServerError) + handlers.DoError(w, "deleting book", err, http.StatusInternalServerError) return } @@ -262,5 +263,5 @@ func (a *API) DeleteBook(w http.ResponseWriter, r *http.Request) { Status: http.StatusOK, Book: presenters.PresentBook(b), } - respondJSON(w, http.StatusOK, resp) + handlers.RespondJSON(w, http.StatusOK, resp) } diff --git a/pkg/server/handlers/v3_books_test.go b/pkg/server/api/v3_books_test.go similarity index 98% rename from pkg/server/handlers/v3_books_test.go rename to pkg/server/api/v3_books_test.go index 2603cfab..0dbeaae3 100644 --- a/pkg/server/handlers/v3_books_test.go +++ b/pkg/server/api/v3_books_test.go @@ -16,7 +16,7 @@ * along with Dnote. If not, see . */ -package handlers +package api import ( "encoding/json" @@ -35,7 +35,7 @@ import ( func TestGetBooks(t *testing.T) { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) // Setup server := MustNewServer(t, &app.App{ @@ -115,7 +115,7 @@ func TestGetBooks(t *testing.T) { func TestGetBooksByName(t *testing.T) { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) // Setup server := MustNewServer(t, &app.App{ @@ -201,7 +201,7 @@ func TestDeleteBook(t *testing.T) { for _, tc := range testCases { t.Run(fmt.Sprintf("originally deleted %t", tc.deleted), func(t *testing.T) { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) // Setup server := MustNewServer(t, &app.App{ @@ -352,7 +352,7 @@ func TestDeleteBook(t *testing.T) { func TestCreateBook(t *testing.T) { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) // Setup server := MustNewServer(t, &app.App{ @@ -412,7 +412,7 @@ func TestCreateBook(t *testing.T) { func TestCreateBookDuplicate(t *testing.T) { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) // Setup server := MustNewServer(t, &app.App{ @@ -492,7 +492,7 @@ func TestUpdateBook(t *testing.T) { for idx, tc := range testCases { func() { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) // Setup server := MustNewServer(t, &app.App{ diff --git a/pkg/server/handlers/v3_notes.go b/pkg/server/api/v3_notes.go similarity index 78% rename from pkg/server/handlers/v3_notes.go rename to pkg/server/api/v3_notes.go index 14f2478b..b6e7c750 100644 --- a/pkg/server/handlers/v3_notes.go +++ b/pkg/server/api/v3_notes.go @@ -16,7 +16,7 @@ * along with Dnote. If not, see . */ -package handlers +package api import ( "encoding/json" @@ -25,6 +25,7 @@ import ( "github.com/dnote/dnote/pkg/server/app" "github.com/dnote/dnote/pkg/server/database" + "github.com/dnote/dnote/pkg/server/handlers" "github.com/dnote/dnote/pkg/server/helpers" "github.com/dnote/dnote/pkg/server/presenters" "github.com/gorilla/mux" @@ -53,25 +54,25 @@ func (a *API) UpdateNote(w http.ResponseWriter, r *http.Request) { user, ok := r.Context().Value(helpers.KeyUser).(database.User) if !ok { - HandleError(w, "No authenticated user found", nil, http.StatusInternalServerError) + handlers.DoError(w, "No authenticated user found", nil, http.StatusInternalServerError) return } var params updateNotePayload err := json.NewDecoder(r.Body).Decode(¶ms) if err != nil { - HandleError(w, "decoding params", err, http.StatusInternalServerError) + handlers.DoError(w, "decoding params", err, http.StatusInternalServerError) return } if ok := validateUpdateNotePayload(params); !ok { - HandleError(w, "Invalid payload", nil, http.StatusBadRequest) + handlers.DoError(w, "Invalid payload", nil, http.StatusBadRequest) return } var note database.Note if err := a.App.DB.Where("uuid = ? AND user_id = ?", noteUUID, user.ID).First(¬e).Error; err != nil { - HandleError(w, "finding note", err, http.StatusInternalServerError) + handlers.DoError(w, "finding note", err, http.StatusInternalServerError) return } @@ -84,14 +85,14 @@ func (a *API) UpdateNote(w http.ResponseWriter, r *http.Request) { }) if err != nil { tx.Rollback() - HandleError(w, "updating note", err, http.StatusInternalServerError) + handlers.DoError(w, "updating note", err, http.StatusInternalServerError) return } var book database.Book if err := tx.Where("uuid = ? AND user_id = ?", note.BookUUID, user.ID).First(&book).Error; err != nil { tx.Rollback() - HandleError(w, fmt.Sprintf("finding book %s to preload", note.BookUUID), err, http.StatusInternalServerError) + handlers.DoError(w, fmt.Sprintf("finding book %s to preload", note.BookUUID), err, http.StatusInternalServerError) return } @@ -105,7 +106,7 @@ func (a *API) UpdateNote(w http.ResponseWriter, r *http.Request) { Status: http.StatusOK, Result: presenters.PresentNote(note), } - respondJSON(w, http.StatusOK, resp) + handlers.RespondJSON(w, http.StatusOK, resp) } type deleteNoteResp struct { @@ -120,13 +121,13 @@ func (a *API) DeleteNote(w http.ResponseWriter, r *http.Request) { user, ok := r.Context().Value(helpers.KeyUser).(database.User) if !ok { - HandleError(w, "No authenticated user found", nil, http.StatusInternalServerError) + handlers.DoError(w, "No authenticated user found", nil, http.StatusInternalServerError) return } var note database.Note if err := a.App.DB.Where("uuid = ? AND user_id = ?", noteUUID, user.ID).Preload("Book").First(¬e).Error; err != nil { - HandleError(w, "finding note", err, http.StatusInternalServerError) + handlers.DoError(w, "finding note", err, http.StatusInternalServerError) return } @@ -135,7 +136,7 @@ func (a *API) DeleteNote(w http.ResponseWriter, r *http.Request) { n, err := a.App.DeleteNote(tx, user, note) if err != nil { tx.Rollback() - HandleError(w, "deleting note", err, http.StatusInternalServerError) + handlers.DoError(w, "deleting note", err, http.StatusInternalServerError) return } @@ -145,7 +146,7 @@ func (a *API) DeleteNote(w http.ResponseWriter, r *http.Request) { Status: http.StatusNoContent, Result: presenters.PresentNote(n), } - respondJSON(w, http.StatusOK, resp) + handlers.RespondJSON(w, http.StatusOK, resp) } type createNotePayload struct { @@ -172,33 +173,33 @@ type CreateNoteResp struct { func (a *API) CreateNote(w http.ResponseWriter, r *http.Request) { user, ok := r.Context().Value(helpers.KeyUser).(database.User) if !ok { - HandleError(w, "No authenticated user found", nil, http.StatusInternalServerError) + handlers.DoError(w, "No authenticated user found", nil, http.StatusInternalServerError) return } var params createNotePayload err := json.NewDecoder(r.Body).Decode(¶ms) if err != nil { - HandleError(w, "decoding payload", err, http.StatusInternalServerError) + handlers.DoError(w, "decoding payload", err, http.StatusInternalServerError) return } err = validateCreateNotePayload(params) if err != nil { - HandleError(w, "validating payload", err, http.StatusBadRequest) + handlers.DoError(w, "validating payload", err, http.StatusBadRequest) return } var book database.Book if err := a.App.DB.Where("uuid = ? AND user_id = ?", params.BookUUID, user.ID).First(&book).Error; err != nil { - HandleError(w, "finding book", err, http.StatusInternalServerError) + handlers.DoError(w, "finding book", err, http.StatusInternalServerError) return } client := getClientType(r) note, err := a.App.CreateNote(user, params.BookUUID, params.Content, params.AddedOn, params.EditedOn, false, client) if err != nil { - HandleError(w, "creating note", err, http.StatusInternalServerError) + handlers.DoError(w, "creating note", err, http.StatusInternalServerError) return } @@ -209,7 +210,7 @@ func (a *API) CreateNote(w http.ResponseWriter, r *http.Request) { resp := CreateNoteResp{ Result: presenters.PresentNote(note), } - respondJSON(w, http.StatusCreated, resp) + handlers.RespondJSON(w, http.StatusCreated, resp) } // NotesOptions is a handler for OPTIONS endpoint for notes diff --git a/pkg/server/handlers/v3_notes_test.go b/pkg/server/api/v3_notes_test.go similarity index 98% rename from pkg/server/handlers/v3_notes_test.go rename to pkg/server/api/v3_notes_test.go index e17126bb..3c57fe27 100644 --- a/pkg/server/handlers/v3_notes_test.go +++ b/pkg/server/api/v3_notes_test.go @@ -16,7 +16,7 @@ * along with Dnote. If not, see . */ -package handlers +package api import ( "fmt" @@ -32,7 +32,7 @@ import ( func TestCreateNote(t *testing.T) { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) // Setup server := MustNewServer(t, &app.App{ @@ -237,7 +237,7 @@ func TestUpdateNote(t *testing.T) { for idx, tc := range testCases { t.Run(fmt.Sprintf("test case %d", idx), func(t *testing.T) { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) // Setup server := MustNewServer(t, &app.App{ @@ -333,7 +333,7 @@ func TestDeleteNote(t *testing.T) { for _, tc := range testCases { t.Run(fmt.Sprintf("originally deleted %t", tc.deleted), func(t *testing.T) { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) // Setup server := MustNewServer(t, &app.App{ diff --git a/pkg/server/handlers/v3_sync.go b/pkg/server/api/v3_sync.go similarity index 93% rename from pkg/server/handlers/v3_sync.go rename to pkg/server/api/v3_sync.go index dfe73d22..a04f41fa 100644 --- a/pkg/server/handlers/v3_sync.go +++ b/pkg/server/api/v3_sync.go @@ -16,7 +16,7 @@ * along with Dnote. If not, see . */ -package handlers +package api import ( "fmt" @@ -27,6 +27,7 @@ import ( "time" "github.com/dnote/dnote/pkg/server/database" + "github.com/dnote/dnote/pkg/server/handlers" "github.com/dnote/dnote/pkg/server/helpers" "github.com/dnote/dnote/pkg/server/log" "github.com/pkg/errors" @@ -250,26 +251,26 @@ type GetSyncFragmentResp struct { func (a *API) GetSyncFragment(w http.ResponseWriter, r *http.Request) { user, ok := r.Context().Value(helpers.KeyUser).(database.User) if !ok { - HandleError(w, "No authenticated user found", nil, http.StatusInternalServerError) + handlers.DoError(w, "No authenticated user found", nil, http.StatusInternalServerError) return } afterUSN, limit, err := parseGetSyncFragmentQuery(r.URL.Query()) if err != nil { - HandleError(w, "parsing query params", err, http.StatusInternalServerError) + handlers.DoError(w, "parsing query params", err, http.StatusInternalServerError) return } fragment, err := a.newFragment(user.ID, user.MaxUSN, afterUSN, limit) if err != nil { - HandleError(w, "getting fragment", err, http.StatusInternalServerError) + handlers.DoError(w, "getting fragment", err, http.StatusInternalServerError) return } response := GetSyncFragmentResp{ Fragment: fragment, } - respondJSON(w, http.StatusOK, response) + handlers.RespondJSON(w, http.StatusOK, response) } // GetSyncStateResp represents a response from GetSyncFragment handler @@ -283,7 +284,7 @@ type GetSyncStateResp struct { func (a *API) GetSyncState(w http.ResponseWriter, r *http.Request) { user, ok := r.Context().Value(helpers.KeyUser).(database.User) if !ok { - HandleError(w, "No authenticated user found", nil, http.StatusInternalServerError) + handlers.DoError(w, "No authenticated user found", nil, http.StatusInternalServerError) return } @@ -299,5 +300,5 @@ func (a *API) GetSyncState(w http.ResponseWriter, r *http.Request) { "resp": response, }).Info("getting sync state") - respondJSON(w, http.StatusOK, response) + handlers.RespondJSON(w, http.StatusOK, response) } diff --git a/pkg/server/handlers/v3_sync_test.go b/pkg/server/api/v3_sync_test.go similarity index 99% rename from pkg/server/handlers/v3_sync_test.go rename to pkg/server/api/v3_sync_test.go index d40acc14..2da52aac 100644 --- a/pkg/server/handlers/v3_sync_test.go +++ b/pkg/server/api/v3_sync_test.go @@ -16,7 +16,7 @@ * along with Dnote. If not, see . */ -package handlers +package api import ( "fmt" diff --git a/pkg/server/app/app.go b/pkg/server/app/app.go index 04d23153..8f1b50f8 100644 --- a/pkg/server/app/app.go +++ b/pkg/server/app/app.go @@ -24,7 +24,6 @@ import ( "github.com/dnote/dnote/pkg/server/mailer" "github.com/jinzhu/gorm" "github.com/pkg/errors" - "github.com/stripe/stripe-go" ) var ( @@ -42,12 +41,11 @@ var ( // App is an application context type App struct { - DB *gorm.DB - Clock clock.Clock - StripeAPIBackend stripe.Backend - EmailTemplates mailer.Templates - EmailBackend mailer.Backend - Config config.Config + DB *gorm.DB + Clock clock.Clock + EmailTemplates mailer.Templates + EmailBackend mailer.Backend + Config config.Config } // Validate validates the app configuration diff --git a/pkg/server/app/books_test.go b/pkg/server/app/books_test.go index 1d08d7d5..17c2ddcb 100644 --- a/pkg/server/app/books_test.go +++ b/pkg/server/app/books_test.go @@ -54,7 +54,7 @@ func TestCreateBook(t *testing.T) { for idx, tc := range testCases { func() { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) user := testutils.SetupUserData() testutils.MustExec(t, testutils.DB.Model(&user).Update("max_usn", tc.userUSN), fmt.Sprintf("preparing user max_usn for test case %d", idx)) @@ -120,7 +120,7 @@ func TestDeleteBook(t *testing.T) { for idx, tc := range testCases { func() { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) user := testutils.SetupUserData() testutils.MustExec(t, testutils.DB.Model(&user).Update("max_usn", tc.userUSN), fmt.Sprintf("preparing user max_usn for test case %d", idx)) @@ -198,7 +198,7 @@ func TestUpdateBook(t *testing.T) { for idx, tc := range testCases { func() { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) user := testutils.SetupUserData() testutils.MustExec(t, testutils.DB.Model(&user).Update("max_usn", tc.userUSN), fmt.Sprintf("preparing user max_usn for test case %d", idx)) diff --git a/pkg/server/app/helpers_test.go b/pkg/server/app/helpers_test.go index 510fa087..b0b32d23 100644 --- a/pkg/server/app/helpers_test.go +++ b/pkg/server/app/helpers_test.go @@ -46,7 +46,7 @@ func TestIncremenetUserUSN(t *testing.T) { // set up for idx, tc := range testCases { func() { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) user := testutils.SetupUserData() testutils.MustExec(t, testutils.DB.Model(&user).Update("max_usn", tc.maxUSN), fmt.Sprintf("preparing user max_usn for test case %d", idx)) diff --git a/pkg/server/app/main_test.go b/pkg/server/app/main_test.go index b9f388eb..b3e34574 100644 --- a/pkg/server/app/main_test.go +++ b/pkg/server/app/main_test.go @@ -29,7 +29,7 @@ func TestMain(m *testing.M) { testutils.InitTestDB() code := m.Run() - testutils.ClearData() + testutils.ClearData(testutils.DB) os.Exit(code) } diff --git a/pkg/server/app/notes_test.go b/pkg/server/app/notes_test.go index e589dc7e..4ce6c701 100644 --- a/pkg/server/app/notes_test.go +++ b/pkg/server/app/notes_test.go @@ -74,7 +74,7 @@ func TestCreateNote(t *testing.T) { for idx, tc := range testCases { func() { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) user := testutils.SetupUserData() testutils.MustExec(t, testutils.DB.Model(&user).Update("max_usn", tc.userUSN), fmt.Sprintf("preparing user max_usn for test case %d", idx)) @@ -137,7 +137,7 @@ func TestUpdateNote(t *testing.T) { for idx, tc := range testCases { t.Run(fmt.Sprintf("test case %d", idx), func(t *testing.T) { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) user := testutils.SetupUserData() testutils.MustExec(t, testutils.DB.Model(&user).Update("max_usn", tc.userUSN), "preparing user max_usn for test case") @@ -212,7 +212,7 @@ func TestDeleteNote(t *testing.T) { for idx, tc := range testCases { func() { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) user := testutils.SetupUserData() testutils.MustExec(t, testutils.DB.Model(&user).Update("max_usn", tc.userUSN), fmt.Sprintf("preparing user max_usn for test case %d", idx)) diff --git a/pkg/server/app/subscriptions.go b/pkg/server/app/subscriptions.go deleted file mode 100644 index b20078b5..00000000 --- a/pkg/server/app/subscriptions.go +++ /dev/null @@ -1,80 +0,0 @@ -/* Copyright (C) 2019, 2020 Monomax Software Pty Ltd - * - * This file is part of Dnote. - * - * Dnote is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Dnote is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Dnote. If not, see . - */ - -package app - -import ( - "github.com/dnote/dnote/pkg/server/database" - "github.com/pkg/errors" - - "github.com/stripe/stripe-go" - "github.com/stripe/stripe-go/sub" -) - -// ErrSubscriptionActive is an error indicating that the subscription is active -// and therefore cannot be reactivated -var ErrSubscriptionActive = errors.New("The subscription is currently active") - -// CancelSub cancels the subscription of the given user -func (a *App) CancelSub(subscriptionID string, user database.User) error { - updateParams := &stripe.SubscriptionParams{ - CancelAtPeriodEnd: stripe.Bool(true), - } - - _, err := sub.Update(subscriptionID, updateParams) - if err != nil { - return errors.Wrap(err, "updating subscription on Stripe") - } - - return nil -} - -// ReactivateSub reactivates the subscription of the given user -func (a *App) ReactivateSub(subscriptionID string, user database.User) error { - s, err := sub.Get(subscriptionID, nil) - if err != nil { - return errors.Wrap(err, "fetching subscription") - } - - if !s.CancelAtPeriodEnd { - return ErrSubscriptionActive - } - - updateParams := &stripe.SubscriptionParams{ - CancelAtPeriodEnd: stripe.Bool(false), - } - if _, err := sub.Update(subscriptionID, updateParams); err != nil { - return errors.Wrap(err, "updating subscription on Stripe") - } - - return nil -} - -// MarkUnsubscribed marks the user unsubscribed -func (a *App) MarkUnsubscribed(stripeCustomerID string) error { - var user database.User - if err := a.DB.Where("stripe_customer_id = ?", stripeCustomerID).First(&user).Error; err != nil { - return errors.Wrap(err, "finding user") - } - - if err := a.DB.Model(&user).Update("cloud", false).Error; err != nil { - return errors.Wrap(err, "updating user") - } - - return nil -} diff --git a/pkg/server/app/testutils.go b/pkg/server/app/testutils.go index 162fd56b..8accf480 100644 --- a/pkg/server/app/testutils.go +++ b/pkg/server/app/testutils.go @@ -34,12 +34,11 @@ func NewTest(appParams *App) App { c.SetOnPremise(false) a := App{ - DB: testutils.DB, - Clock: clock.NewMock(), - EmailTemplates: mailer.NewTemplates(&emailTmplDir), - EmailBackend: &testutils.MockEmailbackendImplementation{}, - StripeAPIBackend: nil, - Config: c, + DB: testutils.DB, + Clock: clock.NewMock(), + EmailTemplates: mailer.NewTemplates(&emailTmplDir), + EmailBackend: &testutils.MockEmailbackendImplementation{}, + Config: c, } // Allow to override with appParams @@ -52,9 +51,6 @@ func NewTest(appParams *App) App { if appParams != nil && appParams.EmailTemplates != nil { a.EmailTemplates = appParams.EmailTemplates } - if appParams != nil && appParams.StripeAPIBackend != nil { - a.StripeAPIBackend = appParams.StripeAPIBackend - } if appParams != nil && appParams.Config.OnPremise { a.Config.OnPremise = appParams.Config.OnPremise } diff --git a/pkg/server/app/users.go b/pkg/server/app/users.go index 68f73935..ca902775 100644 --- a/pkg/server/app/users.go +++ b/pkg/server/app/users.go @@ -19,8 +19,6 @@ package app import ( - "time" - "github.com/dnote/dnote/pkg/server/database" "github.com/dnote/dnote/pkg/server/token" "github.com/jinzhu/gorm" @@ -30,7 +28,7 @@ import ( // TouchLastLoginAt updates the last login timestamp func (a *App) TouchLastLoginAt(user database.User, tx *gorm.DB) error { - t := time.Now() + t := a.Clock.Now() if err := tx.Model(&user).Update(database.User{LastLoginAt: &t}).Error; err != nil { return errors.Wrap(err, "updating last_login_at") } diff --git a/pkg/server/app/users_test.go b/pkg/server/app/users_test.go index a7b08b1e..580bed25 100644 --- a/pkg/server/app/users_test.go +++ b/pkg/server/app/users_test.go @@ -49,7 +49,7 @@ func TestCreateUser(t *testing.T) { c := config.Load() c.SetOnPremise(tc.onPremise) - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) a := NewTest(&App{ Config: c, diff --git a/pkg/server/handlers/auth.go b/pkg/server/handlers/auth.go index 05b762b1..2917e1b7 100644 --- a/pkg/server/handlers/auth.go +++ b/pkg/server/handlers/auth.go @@ -1,196 +1,168 @@ -/* Copyright (C) 2019, 2020 Monomax Software Pty Ltd - * - * This file is part of Dnote. - * - * Dnote is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Dnote is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Dnote. If not, see . - */ - package handlers import ( - "encoding/json" + "context" "net/http" + "strings" "time" + "github.com/dnote/dnote/pkg/server/app" "github.com/dnote/dnote/pkg/server/database" "github.com/dnote/dnote/pkg/server/helpers" "github.com/dnote/dnote/pkg/server/log" - "github.com/dnote/dnote/pkg/server/mailer" - "github.com/dnote/dnote/pkg/server/token" + "github.com/jinzhu/gorm" "github.com/pkg/errors" - "golang.org/x/crypto/bcrypt" ) -// Session represents user session -type Session struct { - UUID string `json:"uuid"` - Email string `json:"email"` - EmailVerified bool `json:"email_verified"` - Pro bool `json:"pro"` -} +func authWithToken(db *gorm.DB, r *http.Request, tokenType string, p *AuthParams) (database.User, database.Token, bool, error) { + var user database.User + var token database.Token -func makeSession(user database.User, account database.Account) Session { - return Session{ - UUID: user.UUID, - Pro: user.Cloud, - Email: account.Email.String, - EmailVerified: account.EmailVerified, - } -} - -func (a *API) getMe(w http.ResponseWriter, r *http.Request) { - user, ok := r.Context().Value(helpers.KeyUser).(database.User) - if !ok { - HandleError(w, "No authenticated user found", nil, http.StatusInternalServerError) - return + query := r.URL.Query() + tokenValue := query.Get("token") + if tokenValue == "" { + return user, token, false, nil } - var account database.Account - if err := a.App.DB.Where("user_id = ?", user.ID).First(&account).Error; err != nil { - HandleError(w, "finding account", err, http.StatusInternalServerError) - return - } - - session := makeSession(user, account) - - response := struct { - User Session `json:"user"` - }{ - User: session, - } - - tx := a.App.DB.Begin() - if err := a.App.TouchLastLoginAt(user, tx); err != nil { - tx.Rollback() - // In case of an error, gracefully continue to avoid disturbing the service - log.ErrorWrap(err, "error touching last_login_at") - } - tx.Commit() - - respondJSON(w, http.StatusOK, response) -} - -type createResetTokenPayload struct { - Email string `json:"email"` -} - -func (a *API) createResetToken(w http.ResponseWriter, r *http.Request) { - var params createResetTokenPayload - if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil { - http.Error(w, "invalid payload", http.StatusBadRequest) - return - } - - var account database.Account - conn := a.App.DB.Where("email = ?", params.Email).First(&account) + conn := db.Where("value = ? AND type = ?", tokenValue, tokenType).First(&token) if conn.RecordNotFound() { - return - } - if err := conn.Error; err != nil { - HandleError(w, errors.Wrap(err, "finding account").Error(), nil, http.StatusInternalServerError) - return + return user, token, false, nil + } else if err := conn.Error; err != nil { + return user, token, false, errors.Wrap(err, "finding token") } - resetToken, err := token.Create(a.App.DB, account.UserID, database.TokenTypeResetPassword) - if err != nil { - HandleError(w, errors.Wrap(err, "generating token").Error(), nil, http.StatusInternalServerError) - return + if token.UsedAt != nil && time.Since(*token.UsedAt).Minutes() > 10 { + return user, token, false, nil } - if err := a.App.SendPasswordResetEmail(account.Email.String, resetToken.Value); err != nil { - if errors.Cause(err) == mailer.ErrSMTPNotConfigured { - respondInvalidSMTPConfig(w) - } else { - HandleError(w, errors.Wrap(err, "sending password reset email").Error(), nil, http.StatusInternalServerError) + if err := db.Where("id = ?", token.UserID).First(&user).Error; err != nil { + return user, token, false, errors.Wrap(err, "finding user") + } + + return user, token, true, nil +} + +// Cors allows browser extensions to load resources +func Cors(next http.HandlerFunc) http.HandlerFunc { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + origin := r.Header.Get("Origin") + + // Allow browser extensions + if strings.HasPrefix(origin, "moz-extension://") || strings.HasPrefix(origin, "chrome-extension://") { + w.Header().Set("Access-Control-Allow-Origin", origin) } - return - } + next.ServeHTTP(w, r) + }) } -type resetPasswordPayload struct { - Password string `json:"password"` - Token string `json:"token"` +// AuthParams is the params for the authentication middleware +type AuthParams struct { + ProOnly bool + RedirectGuestsToLogin bool } -func (a *API) resetPassword(w http.ResponseWriter, r *http.Request) { - var params resetPasswordPayload - if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil { - http.Error(w, "invalid payload", http.StatusBadRequest) - return - } +// Auth is an authentication middleware +func Auth(a *app.App, next http.HandlerFunc, p *AuthParams) http.HandlerFunc { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + user, ok, err := AuthWithSession(a.DB, r, p) + if !ok { + if p != nil && p.RedirectGuestsToLogin { + http.Redirect(w, r, "/login", http.StatusFound) + return + } - var token database.Token - conn := a.App.DB.Where("value = ? AND type =? AND used_at IS NULL", params.Token, database.TokenTypeResetPassword).First(&token) - if conn.RecordNotFound() { - http.Error(w, "invalid token", http.StatusBadRequest) - return - } - if err := conn.Error; err != nil { - HandleError(w, errors.Wrap(err, "finding token").Error(), nil, http.StatusInternalServerError) - return - } + RespondUnauthorized(w) + return + } + if err != nil { + DoError(w, "authenticating with session", err, http.StatusInternalServerError) + return + } - if token.UsedAt != nil { - http.Error(w, "invalid token", http.StatusBadRequest) - return - } + if p != nil && p.ProOnly { + if !user.Cloud { + RespondForbidden(w) + return + } + } - // Expire after 10 minutes - if time.Since(token.CreatedAt).Minutes() > 10 { - http.Error(w, "This link has been expired. Please request a new password reset link.", http.StatusGone) - return - } + ctx := context.WithValue(r.Context(), helpers.KeyUser, user) + next.ServeHTTP(w, r.WithContext(ctx)) + }) +} - tx := a.App.DB.Begin() +// TokenAuth is an authentication middleware with token +func TokenAuth(a *app.App, next http.HandlerFunc, tokenType string, p *AuthParams) http.HandlerFunc { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + user, token, ok, err := authWithToken(a.DB, r, tokenType, p) + if err != nil { + // log the error and continue + log.ErrorWrap(err, "authenticating with token") + } - hashedPassword, err := bcrypt.GenerateFromPassword([]byte(params.Password), bcrypt.DefaultCost) - if err != nil { - tx.Rollback() - HandleError(w, errors.Wrap(err, "hashing password").Error(), nil, http.StatusInternalServerError) - return - } + ctx := r.Context() - var account database.Account - if err := a.App.DB.Where("user_id = ?", token.UserID).First(&account).Error; err != nil { - tx.Rollback() - HandleError(w, errors.Wrap(err, "finding user").Error(), nil, http.StatusInternalServerError) - return - } + if ok { + ctx = context.WithValue(ctx, helpers.KeyToken, token) + } else { + // If token-based auth fails, fall back to session-based auth + user, ok, err = AuthWithSession(a.DB, r, p) + if err != nil { + DoError(w, "authenticating with session", err, http.StatusInternalServerError) + return + } - if err := tx.Model(&account).Update("password", string(hashedPassword)).Error; err != nil { - tx.Rollback() - HandleError(w, errors.Wrap(err, "updating password").Error(), nil, http.StatusInternalServerError) - return - } - if err := tx.Model(&token).Update("used_at", time.Now()).Error; err != nil { - tx.Rollback() - HandleError(w, errors.Wrap(err, "updating password reset token").Error(), nil, http.StatusInternalServerError) - return - } + if !ok { + RespondUnauthorized(w) + return + } + } - tx.Commit() + if p != nil && p.ProOnly { + if !user.Cloud { + RespondForbidden(w) + return + } + } + ctx = context.WithValue(ctx, helpers.KeyUser, user) + next.ServeHTTP(w, r.WithContext(ctx)) + }) +} + +// AuthWithSession performs user authentication with session +func AuthWithSession(db *gorm.DB, r *http.Request, p *AuthParams) (database.User, bool, error) { var user database.User - if err := a.App.DB.Where("id = ?", account.UserID).First(&user).Error; err != nil { - HandleError(w, errors.Wrap(err, "finding user").Error(), nil, http.StatusInternalServerError) - return + + sessionKey, err := GetCredential(r) + if err != nil { + return user, false, errors.Wrap(err, "getting credential") + } + if sessionKey == "" { + return user, false, nil } - a.respondWithSession(a.App.DB, w, user.ID, http.StatusOK) + var session database.Session + conn := db.Where("key = ?", sessionKey).First(&session) - if err := a.App.SendPasswordResetAlertEmail(account.Email.String); err != nil { - log.ErrorWrap(err, "sending password reset email") + if conn.RecordNotFound() { + return user, false, nil + } else if err := conn.Error; err != nil { + return user, false, errors.Wrap(err, "finding session") } + + if session.ExpiresAt.Before(time.Now()) { + return user, false, nil + } + + conn = db.Where("id = ?", session.UserID).First(&user) + + if conn.RecordNotFound() { + return user, false, nil + } else if err := conn.Error; err != nil { + return user, false, errors.Wrap(err, "finding user from token") + } + + return user, true, nil } diff --git a/pkg/server/handlers/helpers.go b/pkg/server/handlers/helpers.go index 10becb81..4a274941 100644 --- a/pkg/server/handlers/helpers.go +++ b/pkg/server/handlers/helpers.go @@ -1,87 +1,62 @@ -/* Copyright (C) 2019, 2020 Monomax Software Pty Ltd - * - * This file is part of Dnote. - * - * Dnote is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Dnote is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Dnote. If not, see . - */ - package handlers import ( "encoding/json" "net/http" "strings" + "time" - "github.com/dnote/dnote/pkg/server/database" "github.com/dnote/dnote/pkg/server/log" - "github.com/jinzhu/gorm" "github.com/pkg/errors" ) -func paginate(conn *gorm.DB, page int) *gorm.DB { - limit := 30 - - // Paginate - if page > 0 { - offset := limit * (page - 1) - conn = conn.Offset(offset) - } - - conn = conn.Limit(limit) - - return conn +// Route represents a single route +type Route struct { + Method string + Pattern string + HandlerFunc http.HandlerFunc + RateLimit bool } -func getBookIDs(books []database.Book) []int { - ret := []int{} - - for _, book := range books { - ret = append(ret, book.ID) - } - - return ret +// RespondForbidden responds with forbidden +func RespondForbidden(w http.ResponseWriter) { + http.Error(w, "forbidden", http.StatusForbidden) } -func validatePassword(password string) error { - if len(password) < 8 { - return errors.New("Password should be longer than 8 characters") - } - - return nil +// RespondUnauthorized responds with unauthorized +func RespondUnauthorized(w http.ResponseWriter) { + UnsetSessionCookie(w) + w.Header().Add("WWW-Authenticate", `Bearer realm="Dnote Pro", charset="UTF-8"`) + http.Error(w, "unauthorized", http.StatusUnauthorized) } -func getClientType(r *http.Request) string { - origin := r.Header.Get("Origin") - - if strings.HasPrefix(origin, "moz-extension://") { - return "firefox-extension" - } - - if strings.HasPrefix(origin, "chrome-extension://") { - return "chrome-extension" - } - - userAgent := r.Header.Get("User-Agent") - if strings.HasPrefix(userAgent, "Go-http-client") { - return "cli" - } - - return "web" +// RespondNotFound responds with not found +func RespondNotFound(w http.ResponseWriter) { + http.Error(w, "not found", http.StatusNotFound) } -// HandleError logs the error and responds with the given status code with a generic status text -func HandleError(w http.ResponseWriter, msg string, err error, statusCode int) { +// RespondInvalidSMTPConfig responds with invalid SMTP config error +func RespondInvalidSMTPConfig(w http.ResponseWriter) { + http.Error(w, "SMTP is not configured", http.StatusInternalServerError) +} + +// UnsetSessionCookie unsets the session cookie +func UnsetSessionCookie(w http.ResponseWriter) { + expire := time.Now().Add(time.Hour * -24 * 30) + cookie := http.Cookie{ + Name: "id", + Value: "", + Expires: expire, + Path: "/", + HttpOnly: true, + } + + w.Header().Set("Cache-Control", "no-cache") + http.SetCookie(w, &cookie) +} + +// DoError logs the error and responds with the given status code with a generic status text +func DoError(w http.ResponseWriter, msg string, err error, statusCode int) { var message string if err == nil { message = msg @@ -97,37 +72,90 @@ func HandleError(w http.ResponseWriter, msg string, err error, statusCode int) { http.Error(w, statusText, statusCode) } -// respondJSON encodes the given payload into a JSON format and writes it to the given response writer -func respondJSON(w http.ResponseWriter, statusCode int, payload interface{}) { +// RespondJSON encodes the given payload into a JSON format and writes it to the given response writer +func RespondJSON(w http.ResponseWriter, statusCode int, payload interface{}) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(statusCode) if err := json.NewEncoder(w).Encode(payload); err != nil { - HandleError(w, "encoding response", err, http.StatusInternalServerError) + DoError(w, "encoding response", err, http.StatusInternalServerError) } } -// notSupported is the handler for the route that is no longer supported -func (a *API) notSupported(w http.ResponseWriter, r *http.Request) { +// NotSupported is the handler for the route that is no longer supported +func NotSupported(w http.ResponseWriter, r *http.Request) { http.Error(w, "API version is not supported. Please upgrade your client.", http.StatusGone) return } -func respondForbidden(w http.ResponseWriter) { - http.Error(w, "forbidden", http.StatusForbidden) +// getSessionKeyFromCookie reads and returns a session key from the cookie sent by the +// request. If no session key is found, it returns an empty string +func getSessionKeyFromCookie(r *http.Request) (string, error) { + c, err := r.Cookie("id") + + if err == http.ErrNoCookie { + return "", nil + } else if err != nil { + return "", errors.Wrap(err, "reading cookie") + } + + return c.Value, nil } -func respondUnauthorized(w http.ResponseWriter) { - unsetSessionCookie(w) - w.Header().Add("WWW-Authenticate", `Bearer realm="Dnote Pro", charset="UTF-8"`) - http.Error(w, "unauthorized", http.StatusUnauthorized) +type authHeader struct { + scheme string + credential string } -// RespondNotFound responds with not found -func RespondNotFound(w http.ResponseWriter) { - http.Error(w, "not found", http.StatusNotFound) +func parseAuthHeader(h string) (authHeader, error) { + parts := strings.Split(h, " ") + + if len(parts) != 2 { + return authHeader{}, errors.New("Invalid authorization header") + } + + parsed := authHeader{ + scheme: parts[0], + credential: parts[1], + } + + return parsed, nil } -func respondInvalidSMTPConfig(w http.ResponseWriter) { - http.Error(w, "SMTP is not configured", http.StatusInternalServerError) +// getSessionKeyFromAuth reads and returns a session key from the Authorization header +func getSessionKeyFromAuth(r *http.Request) (string, error) { + h := r.Header.Get("Authorization") + if h == "" { + return "", nil + } + + payload, err := parseAuthHeader(h) + if err != nil { + return "", errors.Wrap(err, "parsing the authorization header") + } + if payload.scheme != "Bearer" { + return "", errors.New("unsupported scheme") + } + + return payload.credential, nil +} + +// GetCredential extracts a session key from the request from the request header. Concretely, +// it first looks at the 'Cookie' and then the 'Authorization' header. If no credential is found, +// it returns an empty string. +func GetCredential(r *http.Request) (string, error) { + ret, err := getSessionKeyFromCookie(r) + if err != nil { + return "", errors.Wrap(err, "getting session key from cookie") + } + if ret != "" { + return ret, nil + } + + ret, err = getSessionKeyFromAuth(r) + if err != nil { + return "", errors.Wrap(err, "getting session key from Authorization header") + } + + return ret, nil } diff --git a/pkg/server/handlers/routes_test.go b/pkg/server/handlers/helpers_test.go similarity index 79% rename from pkg/server/handlers/routes_test.go rename to pkg/server/handlers/helpers_test.go index 07a1683c..9c1121bc 100644 --- a/pkg/server/handlers/routes_test.go +++ b/pkg/server/handlers/helpers_test.go @@ -26,13 +26,9 @@ import ( "time" "github.com/dnote/dnote/pkg/assert" - "github.com/dnote/dnote/pkg/clock" "github.com/dnote/dnote/pkg/server/app" - "github.com/dnote/dnote/pkg/server/config" "github.com/dnote/dnote/pkg/server/database" - "github.com/dnote/dnote/pkg/server/mailer" "github.com/dnote/dnote/pkg/server/testutils" - "github.com/jinzhu/gorm" "github.com/pkg/errors" ) @@ -175,7 +171,7 @@ func TestGetCredential(t *testing.T) { for _, tc := range testCases { // execute - got, err := getCredential(tc.request) + got, err := GetCredential(tc.request) if err != nil { t.Fatal(errors.Wrap(err, "executing")) } @@ -185,7 +181,7 @@ func TestGetCredential(t *testing.T) { } func TestAuthMiddleware(t *testing.T) { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) user := testutils.SetupUserData() session := database.Session{ @@ -204,8 +200,8 @@ func TestAuthMiddleware(t *testing.T) { handler := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) } - api := API{App: &app.App{DB: testutils.DB}} - server := httptest.NewServer(api.auth(handler, nil)) + a := &app.App{DB: testutils.DB} + server := httptest.NewServer(Auth(a, handler, nil)) defer server.Close() t.Run("with header", func(t *testing.T) { @@ -298,7 +294,7 @@ func TestAuthMiddleware(t *testing.T) { } func TestAuthMiddleware_ProOnly(t *testing.T) { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) user := testutils.SetupUserData() testutils.MustExec(t, testutils.DB.Model(&user).Update("cloud", false), "preparing session") @@ -312,10 +308,12 @@ func TestAuthMiddleware_ProOnly(t *testing.T) { handler := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) } - api := API{App: &app.App{DB: testutils.DB}} - server := httptest.NewServer(api.auth(handler, &AuthMiddlewareParams{ + + a := &app.App{DB: testutils.DB} + server := httptest.NewServer(Auth(a, handler, &AuthParams{ ProOnly: true, })) + defer server.Close() t.Run("with header", func(t *testing.T) { @@ -385,8 +383,56 @@ func TestAuthMiddleware_ProOnly(t *testing.T) { }) } +func TestAuthMiddleware_RedirectGuestsToLogin(t *testing.T) { + defer testutils.ClearData(testutils.DB) + + handler := func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + } + + a := &app.App{DB: testutils.DB} + server := httptest.NewServer(Auth(a, handler, &AuthParams{ + RedirectGuestsToLogin: true, + })) + + defer server.Close() + + t.Run("guest", func(t *testing.T) { + req := testutils.MakeReq(server.URL, "GET", "/", "") + + // execute + res := testutils.HTTPDo(t, req) + + // test + assert.Equal(t, res.StatusCode, http.StatusFound, "status code mismatch") + assert.Equal(t, res.Header.Get("Location"), "/login", "location header mismatch") + }) + + t.Run("logged in user", func(t *testing.T) { + req := testutils.MakeReq(server.URL, "GET", "/", "") + + user := testutils.SetupUserData() + testutils.MustExec(t, testutils.DB.Model(&user).Update("cloud", false), "preparing session") + session := database.Session{ + Key: "A9xgggqzTHETy++GDi1NpDNe0iyqosPm9bitdeNGkJU=", + UserID: user.ID, + ExpiresAt: time.Now().Add(time.Hour * 24), + } + testutils.MustExec(t, testutils.DB.Save(&session), "preparing session") + + // execute + res := testutils.HTTPAuthDo(t, req, user) + req.Header.Set("Authorization", session.Key) + + // test + assert.Equal(t, res.StatusCode, http.StatusOK, "status code mismatch") + assert.Equal(t, res.Header.Get("Location"), "", "location header mismatch") + }) + +} + func TestTokenAuthMiddleWare(t *testing.T) { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) user := testutils.SetupUserData() tok := database.Token{ @@ -405,8 +451,9 @@ func TestTokenAuthMiddleWare(t *testing.T) { handler := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) } - api := API{App: &app.App{DB: testutils.DB}} - server := httptest.NewServer(api.tokenAuth(handler, database.TokenTypeEmailPreference, nil)) + + a := &app.App{DB: testutils.DB} + server := httptest.NewServer(TokenAuth(a, handler, database.TokenTypeEmailPreference, nil)) defer server.Close() t.Run("with token", func(t *testing.T) { @@ -515,7 +562,7 @@ func TestTokenAuthMiddleWare(t *testing.T) { } func TestTokenAuthMiddleWare_ProOnly(t *testing.T) { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) user := testutils.SetupUserData() testutils.MustExec(t, testutils.DB.Model(&user).Update("cloud", false), "preparing session") @@ -535,10 +582,12 @@ func TestTokenAuthMiddleWare_ProOnly(t *testing.T) { handler := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) } - api := API{App: &app.App{DB: testutils.DB}} - server := httptest.NewServer(api.tokenAuth(handler, database.TokenTypeEmailPreference, &AuthMiddlewareParams{ + + a := &app.App{DB: testutils.DB} + server := httptest.NewServer(TokenAuth(a, handler, database.TokenTypeEmailPreference, &AuthParams{ ProOnly: true, })) + defer server.Close() t.Run("with token", func(t *testing.T) { @@ -645,136 +694,3 @@ func TestTokenAuthMiddleWare_ProOnly(t *testing.T) { assert.Equal(t, res.StatusCode, http.StatusUnauthorized, "status code mismatch") }) } - -func TestNotSupportedVersions(t *testing.T) { - testCases := []struct { - path string - }{ - // v1 - { - path: "/v1", - }, - { - path: "/v1/foo", - }, - { - path: "/v1/bar/baz", - }, - // v2 - { - path: "/v2", - }, - { - path: "/v2/foo", - }, - { - path: "/v2/bar/baz", - }, - } - - // setup - server := MustNewServer(t, &app.App{ - DB: &gorm.DB{}, - Clock: clock.NewMock(), - }) - defer server.Close() - - for _, tc := range testCases { - t.Run(tc.path, func(t *testing.T) { - // execute - req := testutils.MakeReq(server.URL, "GET", tc.path, "") - res := testutils.HTTPDo(t, req) - - // test - assert.Equal(t, res.StatusCode, http.StatusGone, "status code mismatch") - }) - } -} - -func TestNewRouter_AppValidate(t *testing.T) { - c := config.Load() - - configWithoutWebURL := config.Load() - configWithoutWebURL.WebURL = "" - - testCases := []struct { - app app.App - expectedErr error - }{ - { - app: app.App{ - DB: &gorm.DB{}, - Clock: clock.NewMock(), - StripeAPIBackend: nil, - EmailTemplates: mailer.Templates{}, - EmailBackend: &testutils.MockEmailbackendImplementation{}, - Config: c, - }, - expectedErr: nil, - }, - { - app: app.App{ - DB: nil, - Clock: clock.NewMock(), - StripeAPIBackend: nil, - EmailTemplates: mailer.Templates{}, - EmailBackend: &testutils.MockEmailbackendImplementation{}, - Config: c, - }, - expectedErr: app.ErrEmptyDB, - }, - { - app: app.App{ - DB: &gorm.DB{}, - Clock: nil, - StripeAPIBackend: nil, - EmailTemplates: mailer.Templates{}, - EmailBackend: &testutils.MockEmailbackendImplementation{}, - Config: c, - }, - expectedErr: app.ErrEmptyClock, - }, - { - app: app.App{ - DB: &gorm.DB{}, - Clock: clock.NewMock(), - StripeAPIBackend: nil, - EmailTemplates: nil, - EmailBackend: &testutils.MockEmailbackendImplementation{}, - Config: c, - }, - expectedErr: app.ErrEmptyEmailTemplates, - }, - { - app: app.App{ - DB: &gorm.DB{}, - Clock: clock.NewMock(), - StripeAPIBackend: nil, - EmailTemplates: mailer.Templates{}, - EmailBackend: nil, - Config: c, - }, - expectedErr: app.ErrEmptyEmailBackend, - }, - { - app: app.App{ - DB: &gorm.DB{}, - Clock: clock.NewMock(), - StripeAPIBackend: nil, - EmailTemplates: mailer.Templates{}, - EmailBackend: &testutils.MockEmailbackendImplementation{}, - Config: configWithoutWebURL, - }, - expectedErr: app.ErrEmptyWebURL, - }, - } - - for idx, tc := range testCases { - t.Run(fmt.Sprintf("test case %d", idx), func(t *testing.T) { - api := API{App: &tc.app} - _, err := api.NewRouter() - - assert.Equal(t, errors.Cause(err), tc.expectedErr, "error mismatch") - }) - } -} diff --git a/pkg/server/handlers/limit.go b/pkg/server/handlers/limit.go index 662b0e64..91d8685a 100644 --- a/pkg/server/handlers/limit.go +++ b/pkg/server/handlers/limit.go @@ -105,8 +105,8 @@ func lookupIP(r *http.Request) string { return r.RemoteAddr } -// limit is a middleware to rate limit the handler -func limit(next http.Handler) http.HandlerFunc { +// Limit is a middleware to rate limit the handler +func Limit(next http.Handler) http.HandlerFunc { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { identifier := lookupIP(r) limiter := getVisitor(identifier) diff --git a/pkg/server/handlers/logging.go b/pkg/server/handlers/logging.go new file mode 100644 index 00000000..d809fd1f --- /dev/null +++ b/pkg/server/handlers/logging.go @@ -0,0 +1,42 @@ +package handlers + +import ( + "fmt" + "net/http" + "time" + + "github.com/dnote/dnote/pkg/server/log" +) + +// logResponseWriter wraps http.ResponseWriter to expose HTTP status code for logging. +// The optional interfaces of http.ResponseWriter are lost because of the wrapping, and +// such interfaces should be implemented if needed. (i.e. http.Pusher, http.Flusher, etc.) +type logResponseWriter struct { + http.ResponseWriter + statusCode int +} + +func (w *logResponseWriter) WriteHeader(code int) { + w.statusCode = code + w.ResponseWriter.WriteHeader(code) +} + +// Logging is a logging middleware +func Logging(inner http.Handler) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + start := time.Now() + + lw := logResponseWriter{w, http.StatusOK} + inner.ServeHTTP(&lw, r) + + log.WithFields(log.Fields{ + "origin": r.Header.Get("Origin"), + "remoteAddr": lookupIP(r), + "uri": r.RequestURI, + "statusCode": lw.statusCode, + "method": r.Method, + "duration": fmt.Sprintf("%dms", time.Since(start)/1000000), + "userAgent": r.Header.Get("User-Agent"), + }).Info("incoming request") + } +} diff --git a/pkg/server/handlers/main_test.go b/pkg/server/handlers/main_test.go index aeb0a5eb..1263b952 100644 --- a/pkg/server/handlers/main_test.go +++ b/pkg/server/handlers/main_test.go @@ -29,7 +29,7 @@ func TestMain(m *testing.M) { testutils.InitTestDB() code := m.Run() - testutils.ClearData() + testutils.ClearData(testutils.DB) os.Exit(code) } diff --git a/pkg/server/handlers/routes.go b/pkg/server/handlers/routes.go deleted file mode 100644 index aeac4e30..00000000 --- a/pkg/server/handlers/routes.go +++ /dev/null @@ -1,390 +0,0 @@ -/* Copyright (C) 2019, 2020 Monomax Software Pty Ltd - * - * This file is part of Dnote. - * - * Dnote is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Dnote is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Dnote. If not, see . - */ - -package handlers - -import ( - "context" - "fmt" - "net/http" - "os" - "strings" - "time" - - "github.com/dnote/dnote/pkg/server/app" - "github.com/dnote/dnote/pkg/server/database" - "github.com/dnote/dnote/pkg/server/helpers" - "github.com/dnote/dnote/pkg/server/log" - "github.com/gorilla/mux" - "github.com/jinzhu/gorm" - "github.com/pkg/errors" - "github.com/stripe/stripe-go" -) - -// Route represents a single route -type Route struct { - Method string - Pattern string - HandlerFunc http.HandlerFunc - RateLimit bool -} - -type authHeader struct { - scheme string - credential string -} - -func parseAuthHeader(h string) (authHeader, error) { - parts := strings.Split(h, " ") - - if len(parts) != 2 { - return authHeader{}, errors.New("Invalid authorization header") - } - - parsed := authHeader{ - scheme: parts[0], - credential: parts[1], - } - - return parsed, nil -} - -// getSessionKeyFromCookie reads and returns a session key from the cookie sent by the -// request. If no session key is found, it returns an empty string -func getSessionKeyFromCookie(r *http.Request) (string, error) { - c, err := r.Cookie("id") - - if err == http.ErrNoCookie { - return "", nil - } else if err != nil { - return "", errors.Wrap(err, "reading cookie") - } - - return c.Value, nil -} - -// getSessionKeyFromAuth reads and returns a session key from the Authorization header -func getSessionKeyFromAuth(r *http.Request) (string, error) { - h := r.Header.Get("Authorization") - if h == "" { - return "", nil - } - - payload, err := parseAuthHeader(h) - if err != nil { - return "", errors.Wrap(err, "parsing the authorization header") - } - if payload.scheme != "Bearer" { - return "", errors.New("unsupported scheme") - } - - return payload.credential, nil -} - -// getCredential extracts a session key from the request from the request header. Concretely, -// it first looks at the 'Cookie' and then the 'Authorization' header. If no credential is found, -// it returns an empty string. -func getCredential(r *http.Request) (string, error) { - ret, err := getSessionKeyFromCookie(r) - if err != nil { - return "", errors.Wrap(err, "getting session key from cookie") - } - if ret != "" { - return ret, nil - } - - ret, err = getSessionKeyFromAuth(r) - if err != nil { - return "", errors.Wrap(err, "getting session key from Authorization header") - } - - return ret, nil -} - -// AuthWithSession performs user authentication with session -func AuthWithSession(db *gorm.DB, r *http.Request, p *AuthMiddlewareParams) (database.User, bool, error) { - var user database.User - - sessionKey, err := getCredential(r) - if err != nil { - return user, false, errors.Wrap(err, "getting credential") - } - if sessionKey == "" { - return user, false, nil - } - - var session database.Session - conn := db.Where("key = ?", sessionKey).First(&session) - - if conn.RecordNotFound() { - return user, false, nil - } else if err := conn.Error; err != nil { - return user, false, errors.Wrap(err, "finding session") - } - - if session.ExpiresAt.Before(time.Now()) { - return user, false, nil - } - - conn = db.Where("id = ?", session.UserID).First(&user) - - if conn.RecordNotFound() { - return user, false, nil - } else if err := conn.Error; err != nil { - return user, false, errors.Wrap(err, "finding user from token") - } - - return user, true, nil -} - -func authWithToken(db *gorm.DB, r *http.Request, tokenType string, p *AuthMiddlewareParams) (database.User, database.Token, bool, error) { - var user database.User - var token database.Token - - query := r.URL.Query() - tokenValue := query.Get("token") - if tokenValue == "" { - return user, token, false, nil - } - - conn := db.Where("value = ? AND type = ?", tokenValue, tokenType).First(&token) - if conn.RecordNotFound() { - return user, token, false, nil - } else if err := conn.Error; err != nil { - return user, token, false, errors.Wrap(err, "finding token") - } - - if token.UsedAt != nil && time.Since(*token.UsedAt).Minutes() > 10 { - return user, token, false, nil - } - - if err := db.Where("id = ?", token.UserID).First(&user).Error; err != nil { - return user, token, false, errors.Wrap(err, "finding user") - } - - return user, token, true, nil -} - -// AuthMiddlewareParams is the params for the authentication middleware -type AuthMiddlewareParams struct { - ProOnly bool -} - -func (a *API) auth(next http.HandlerFunc, p *AuthMiddlewareParams) http.HandlerFunc { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - user, ok, err := AuthWithSession(a.App.DB, r, p) - if !ok { - respondUnauthorized(w) - return - } - if err != nil { - HandleError(w, "authenticating with session", err, http.StatusInternalServerError) - return - } - - if p != nil && p.ProOnly { - if !user.Cloud { - respondForbidden(w) - return - } - } - - ctx := context.WithValue(r.Context(), helpers.KeyUser, user) - next.ServeHTTP(w, r.WithContext(ctx)) - }) -} - -func (a *API) tokenAuth(next http.HandlerFunc, tokenType string, p *AuthMiddlewareParams) http.HandlerFunc { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - user, token, ok, err := authWithToken(a.App.DB, r, tokenType, p) - if err != nil { - // log the error and continue - log.ErrorWrap(err, "authenticating with token") - } - - ctx := r.Context() - - if ok { - ctx = context.WithValue(ctx, helpers.KeyToken, token) - } else { - // If token-based auth fails, fall back to session-based auth - user, ok, err = AuthWithSession(a.App.DB, r, p) - if err != nil { - HandleError(w, "authenticating with session", err, http.StatusInternalServerError) - return - } - - if !ok { - respondUnauthorized(w) - return - } - } - - if p != nil && p.ProOnly { - if !user.Cloud { - respondForbidden(w) - return - } - } - - ctx = context.WithValue(ctx, helpers.KeyUser, user) - next.ServeHTTP(w, r.WithContext(ctx)) - }) -} - -func cors(next http.HandlerFunc) http.HandlerFunc { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - origin := r.Header.Get("Origin") - - // Allow browser extensions - if strings.HasPrefix(origin, "moz-extension://") || strings.HasPrefix(origin, "chrome-extension://") { - w.Header().Set("Access-Control-Allow-Origin", origin) - } - - next.ServeHTTP(w, r) - }) -} - -// logResponseWriter wraps http.ResponseWriter to expose HTTP status code for logging. -// The optional interfaces of http.ResponseWriter are lost because of the wrapping, and -// such interfaces should be implemented if needed. (i.e. http.Pusher, http.Flusher, etc.) -type logResponseWriter struct { - http.ResponseWriter - statusCode int -} - -func (w *logResponseWriter) WriteHeader(code int) { - w.statusCode = code - w.ResponseWriter.WriteHeader(code) -} - -func logging(inner http.Handler) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - start := time.Now() - - lw := logResponseWriter{w, http.StatusOK} - inner.ServeHTTP(&lw, r) - - log.WithFields(log.Fields{ - "origin": r.Header.Get("Origin"), - "remoteAddr": lookupIP(r), - "uri": r.RequestURI, - "statusCode": lw.statusCode, - "method": r.Method, - "duration": fmt.Sprintf("%dms", time.Since(start)/1000000), - "userAgent": r.Header.Get("User-Agent"), - }).Info("incoming request") - } -} - -func applyMiddleware(h http.HandlerFunc, rateLimit bool) http.Handler { - ret := h - ret = logging(ret) - - if rateLimit && os.Getenv("GO_ENV") != "TEST" { - ret = limit(ret) - } - - return ret -} - -// API is a web API configuration -type API struct { - App *app.App -} - -// init sets up the application based on the configuration -func (a *API) init() error { - if err := a.App.Validate(); err != nil { - return errors.Wrap(err, "validating the app parameters") - } - - stripe.Key = os.Getenv("StripeSecretKey") - - if a.App.StripeAPIBackend != nil { - stripe.SetBackend(stripe.APIBackend, a.App.StripeAPIBackend) - } - - return nil -} - -// NewRouter creates and returns a new router -func (a *API) NewRouter() (*mux.Router, error) { - if err := a.init(); err != nil { - return nil, errors.Wrap(err, "initializing app") - } - - proOnly := AuthMiddlewareParams{ProOnly: true} - - var routes = []Route{ - // internal - {"GET", "/health", a.checkHealth, false}, - {"GET", "/me", a.auth(a.getMe, nil), true}, - {"POST", "/verification-token", a.auth(a.createVerificationToken, nil), true}, - {"PATCH", "/verify-email", a.verifyEmail, true}, - {"POST", "/reset-token", a.createResetToken, true}, - {"PATCH", "/reset-password", a.resetPassword, true}, - {"PATCH", "/account/profile", a.auth(a.updateProfile, nil), true}, - {"PATCH", "/account/password", a.auth(a.updatePassword, nil), true}, - {"GET", "/account/email-preference", a.tokenAuth(a.getEmailPreference, database.TokenTypeEmailPreference, nil), true}, - {"PATCH", "/account/email-preference", a.tokenAuth(a.updateEmailPreference, database.TokenTypeEmailPreference, nil), true}, - {"POST", "/subscriptions", a.auth(a.createSub, nil), true}, - {"PATCH", "/subscriptions", a.auth(a.updateSub, nil), true}, - {"POST", "/webhooks/stripe", a.stripeWebhook, true}, - {"GET", "/subscriptions", a.auth(a.getSub, nil), true}, - {"GET", "/stripe_source", a.auth(a.getStripeSource, nil), true}, - {"PATCH", "/stripe_source", a.auth(a.updateStripeSource, nil), true}, - {"GET", "/notes", a.auth(a.getNotes, nil), false}, - {"GET", "/notes/{noteUUID}", a.getNote, true}, - {"GET", "/calendar", a.auth(a.getCalendar, nil), true}, - - // v3 - {"GET", "/v3/sync/fragment", cors(a.auth(a.GetSyncFragment, &proOnly)), false}, - {"GET", "/v3/sync/state", cors(a.auth(a.GetSyncState, &proOnly)), false}, - {"OPTIONS", "/v3/books", cors(a.BooksOptions), true}, - {"GET", "/v3/books", cors(a.auth(a.GetBooks, &proOnly)), true}, - {"GET", "/v3/books/{bookUUID}", cors(a.auth(a.GetBook, &proOnly)), true}, - {"POST", "/v3/books", cors(a.auth(a.CreateBook, &proOnly)), false}, - {"PATCH", "/v3/books/{bookUUID}", cors(a.auth(a.UpdateBook, &proOnly)), false}, - {"DELETE", "/v3/books/{bookUUID}", cors(a.auth(a.DeleteBook, &proOnly)), false}, - {"OPTIONS", "/v3/notes", cors(a.NotesOptions), true}, - {"POST", "/v3/notes", cors(a.auth(a.CreateNote, &proOnly)), false}, - {"PATCH", "/v3/notes/{noteUUID}", a.auth(a.UpdateNote, &proOnly), false}, - {"DELETE", "/v3/notes/{noteUUID}", a.auth(a.DeleteNote, &proOnly), false}, - {"POST", "/v3/signin", cors(a.signin), true}, - {"OPTIONS", "/v3/signout", cors(a.signoutOptions), true}, - {"POST", "/v3/signout", cors(a.signout), true}, - {"POST", "/v3/register", a.register, true}, - } - - router := mux.NewRouter().StrictSlash(true) - - router.PathPrefix("/v1").Handler(applyMiddleware(a.notSupported, true)) - router.PathPrefix("/v2").Handler(applyMiddleware(a.notSupported, true)) - - for _, route := range routes { - handler := route.HandlerFunc - - router. - Methods(route.Method). - Path(route.Pattern). - Handler(applyMiddleware(handler, route.RateLimit)) - } - - return router, nil -} diff --git a/pkg/server/handlers/semver.go b/pkg/server/handlers/semver.go deleted file mode 100644 index 398a60cd..00000000 --- a/pkg/server/handlers/semver.go +++ /dev/null @@ -1,62 +0,0 @@ -/* Copyright (C) 2019, 2020 Monomax Software Pty Ltd - * - * This file is part of Dnote. - * - * Dnote is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Dnote is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Dnote. If not, see . - */ - -package handlers - -import ( - "regexp" - "strconv" - - "github.com/pkg/errors" -) - -type semver struct { - Major int - Minor int - Patch int -} - -func parseSemver(version string) (semver, error) { - re := regexp.MustCompile(`(\d*)\.(\d*)\.(\d*)`) - match := re.FindStringSubmatch(version) - - if len(match) != 4 { - return semver{}, errors.Errorf("invalid semver %s", version) - } - - major, err := strconv.Atoi(match[1]) - if err != nil { - return semver{}, errors.Wrap(err, "converting major version to int") - } - minor, err := strconv.Atoi(match[2]) - if err != nil { - return semver{}, errors.Wrap(err, "converting minor version to int") - } - patch, err := strconv.Atoi(match[3]) - if err != nil { - return semver{}, errors.Wrap(err, "converting patch version to int") - } - - ret := semver{ - Major: major, - Minor: minor, - Patch: patch, - } - - return ret, nil -} diff --git a/pkg/server/handlers/subscription.go b/pkg/server/handlers/subscription.go deleted file mode 100644 index 783447d8..00000000 --- a/pkg/server/handlers/subscription.go +++ /dev/null @@ -1,561 +0,0 @@ -/* Copyright (C) 2019, 2020 Monomax Software Pty Ltd - * - * This file is part of Dnote. - * - * Dnote is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Dnote is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Dnote. If not, see . - */ - -package handlers - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "os" - "strings" - - "github.com/dnote/dnote/pkg/server/app" - "github.com/dnote/dnote/pkg/server/database" - "github.com/dnote/dnote/pkg/server/helpers" - "github.com/dnote/dnote/pkg/server/log" - "github.com/jinzhu/gorm" - "github.com/pkg/errors" - "github.com/stripe/stripe-go" - "github.com/stripe/stripe-go/card" - "github.com/stripe/stripe-go/customer" - "github.com/stripe/stripe-go/paymentsource" - "github.com/stripe/stripe-go/source" - "github.com/stripe/stripe-go/sub" - "github.com/stripe/stripe-go/webhook" -) - -func getOrCreateStripeCustomer(tx *gorm.DB, user database.User) (*stripe.Customer, error) { - if user.StripeCustomerID != "" { - c, err := customer.Get(user.StripeCustomerID, nil) - if err != nil { - return nil, errors.Wrap(err, "getting customer") - } - - return c, nil - } - - var account database.Account - if err := tx.Where("user_id = ?", user.ID).First(&account).Error; err != nil { - return nil, errors.Wrap(err, "finding account") - } - - customerParams := &stripe.CustomerParams{ - Email: &account.Email.String, - } - c, err := customer.New(customerParams) - if err != nil { - return nil, errors.Wrap(err, "creating customer") - } - - user.StripeCustomerID = c.ID - if err := tx.Save(&user).Error; err != nil { - return nil, errors.Wrap(err, "updating user") - } - - return c, nil -} - -func addCustomerSource(customerID, sourceID string) (*stripe.PaymentSource, error) { - params := &stripe.CustomerSourceParams{ - Customer: stripe.String(customerID), - Source: &stripe.SourceParams{ - Token: stripe.String(sourceID), - }, - } - - src, err := paymentsource.New(params) - if err != nil { - return nil, errors.Wrap(err, "creating source for customer") - } - - return src, nil -} - -func removeCustomerSource(customerID, sourceID string) (*stripe.Source, error) { - params := &stripe.SourceObjectDetachParams{ - Customer: stripe.String(customerID), - } - s, err := source.Detach(sourceID, params) - if err != nil { - return nil, err - } - - return s, nil -} - -func createCustomerSubscription(customerID, planID string) (*stripe.Subscription, error) { - subParams := &stripe.SubscriptionParams{ - Customer: stripe.String(customerID), - Items: []*stripe.SubscriptionItemsParams{ - { - Plan: stripe.String(planID), - }, - }, - } - - s, err := sub.New(subParams) - if err != nil { - return nil, errors.Wrap(err, "creating subscription for customer") - } - - return s, nil -} - -type createSubPayload struct { - Yearly bool `json:"yearly"` - Source stripe.Source `json:"source"` - Country string `json:"country"` -} - -// createSub creates a subscription for a the current user -func (a *API) createSub(w http.ResponseWriter, r *http.Request) { - user, ok := r.Context().Value(helpers.KeyUser).(database.User) - if !ok { - HandleError(w, "No authenticated user found", nil, http.StatusInternalServerError) - return - } - var account database.Account - if err := a.App.DB.Where("user_id = ?", user.ID).First(&account).Error; err != nil { - HandleError(w, "getting user", err, http.StatusInternalServerError) - return - } - - var payload createSubPayload - if err := json.NewDecoder(r.Body).Decode(&payload); err != nil { - HandleError(w, "decoding params", err, http.StatusBadRequest) - return - } - - tx := a.App.DB.Begin() - - if err := tx.Model(&user). - Update(map[string]interface{}{ - "cloud": true, - "billing_country": payload.Country, - }).Error; err != nil { - tx.Rollback() - HandleError(w, "updating user", err, http.StatusInternalServerError) - return - } - - customer, err := getOrCreateStripeCustomer(tx, user) - if err != nil { - tx.Rollback() - HandleError(w, "getting customer", err, http.StatusInternalServerError) - return - } - - if _, err = addCustomerSource(customer.ID, payload.Source.ID); err != nil { - tx.Rollback() - HandleError(w, "attaching source", err, http.StatusInternalServerError) - return - } - - var planID string - if payload.Yearly { - planID = os.Getenv("StripeYearlyPlanID") - } else { - planID = os.Getenv("StripeMonthlyPlanID") - } - - if _, err := createCustomerSubscription(customer.ID, planID); err != nil { - tx.Rollback() - HandleError(w, "creating subscription", err, http.StatusInternalServerError) - return - } - - if err := tx.Commit().Error; err != nil { - HandleError(w, "committing a subscription transaction", err, http.StatusInternalServerError) - return - } - - if err := a.App.SendSubscriptionConfirmationEmail(account.Email.String); err != nil { - log.ErrorWrap(err, "sending subscription confirmation email") - } - - w.WriteHeader(http.StatusOK) -} - -type updateSubPayload struct { - StripeSubcriptionID string `json:"stripe_subscription_id"` - Op string `json:"op"` - Body *interface{} `json:"body"` -} - -var ( - updateSubOpCancel = "cancel" - updateSubOpReactivate = "reactivate" -) - -var validUpdateSubOp = []string{ - updateSubOpCancel, - updateSubOpReactivate, -} - -func validateUpdateSubPayload(p updateSubPayload) error { - var isOpValid bool - - for _, op := range validUpdateSubOp { - if p.Op == op { - isOpValid = true - break - } - } - - if !isOpValid { - return errors.Errorf("Invalid operation %s", p.Op) - } - - if p.StripeSubcriptionID == "" { - return errors.New("stripe_subscription_id is required") - } - - return nil -} - -func (a *API) updateSub(w http.ResponseWriter, r *http.Request) { - user, ok := r.Context().Value(helpers.KeyUser).(database.User) - if !ok { - HandleError(w, "No authenticated user found", nil, http.StatusInternalServerError) - return - } - if user.StripeCustomerID == "" { - HandleError(w, "Customer does not exist", nil, http.StatusForbidden) - return - } - - var payload updateSubPayload - if err := json.NewDecoder(r.Body).Decode(&payload); err != nil { - HandleError(w, "decoding params", err, http.StatusBadRequest) - return - } - if err := validateUpdateSubPayload(payload); err != nil { - HandleError(w, "invalid payload", err, http.StatusBadRequest) - return - } - - var err error - if payload.Op == updateSubOpCancel { - err = a.App.CancelSub(payload.StripeSubcriptionID, user) - } else if payload.Op == updateSubOpReactivate { - err = a.App.ReactivateSub(payload.StripeSubcriptionID, user) - } - - if err != nil { - var statusCode int - if err == app.ErrSubscriptionActive { - statusCode = http.StatusBadRequest - } else { - statusCode = http.StatusInternalServerError - } - - HandleError(w, fmt.Sprintf("during operation %s", payload.Op), err, statusCode) - return - } - - w.WriteHeader(http.StatusOK) -} - -// GetSubResponseItem represents a subscription item in the response for get subscription -type GetSubResponseItem struct { - PlanID string `json:"plan_id"` - ProductID string `json:"product_id"` -} - -// GetSubResponse is a response for getSub -type GetSubResponse struct { - SubscriptionID string `json:"id"` - Items []GetSubResponseItem `json:"items"` - CurrentPeriodStart int64 `json:"current_period_start"` - CurrentPeriodEnd int64 `json:"current_period_end"` - Status stripe.SubscriptionStatus `json:"status"` - CancelAtPeriodEnd bool `json:"cancel_at_period_end"` -} - -func respondWithEmptySub(w http.ResponseWriter) { - emptyGetSubResponse := GetSubResponse{ - Items: []GetSubResponseItem{}, - } - - w.Header().Set("Content-Type", "application/json") - if err := json.NewEncoder(w).Encode(emptyGetSubResponse); err != nil { - HandleError(w, "encoding response", err, http.StatusInternalServerError) - return - } -} - -func (a *API) getSub(w http.ResponseWriter, r *http.Request) { - user, ok := r.Context().Value(helpers.KeyUser).(database.User) - if !ok { - HandleError(w, "No authenticated user found", nil, http.StatusInternalServerError) - return - } - if user.StripeCustomerID == "" { - respondWithEmptySub(w) - return - } - - listParams := &stripe.SubscriptionListParams{} - listParams.Filters.AddFilter("customer", "", user.StripeCustomerID) - listParams.Filters.AddFilter("status", "", "active") - i := sub.List(listParams) - - if !i.Next() { - if err := i.Err(); err != nil { - HandleError(w, "fetching subscription", err, http.StatusInternalServerError) - return - } - - // If no active subscription exists, respond with an empty subscription - respondWithEmptySub(w) - return - } - - s := i.Subscription() - - resp := GetSubResponse{ - SubscriptionID: s.ID, - CurrentPeriodStart: s.CurrentPeriodStart, - CurrentPeriodEnd: s.CurrentPeriodEnd, - Status: s.Status, - CancelAtPeriodEnd: s.CancelAtPeriodEnd, - } - - for _, item := range s.Items.Data { - i := GetSubResponseItem{ - PlanID: item.Plan.ID, - ProductID: item.Plan.Product.ID, - } - resp.Items = append(resp.Items, i) - } - - respondJSON(w, http.StatusOK, resp) -} - -// GetStripeSourceResponse is a response for getStripeToken -type GetStripeSourceResponse struct { - Brand string `json:"brand"` - Last4 string `json:"last4"` - ExpMonth uint8 `json:"exp_month"` - ExpYear uint16 `json:"exp_year"` -} - -func respondWithEmptyStripeToken(w http.ResponseWriter) { - var resp GetStripeSourceResponse - - respondJSON(w, http.StatusOK, resp) -} - -// getStripeCard retrieves card information from stripe and returns a stripe.Card -// It handles legacy 'card' resource which have 'card_' prefixes, as well as the -// more up-to-date 'source' resources which have 'src_' prefixes. -func getStripeCard(stripeCustomerID, sourceID string) (*stripe.Card, error) { - if strings.HasPrefix(sourceID, "card_") { - params := &stripe.CardParams{ - Customer: stripe.String(stripeCustomerID), - } - cd, err := card.Get(sourceID, params) - if err != nil { - return nil, errors.Wrap(err, "fetching card") - } - - return cd, nil - } else if strings.HasPrefix(sourceID, "src_") { - src, err := source.Get(sourceID, nil) - if err != nil { - return nil, errors.Wrap(err, "fetching source") - } - - brand, ok := src.TypeData["brand"].(string) - if !ok { - return nil, errors.New("casting brand") - } - last4, ok := src.TypeData["last4"].(string) - if !ok { - return nil, errors.New("casting last4") - } - expMonth, ok := src.TypeData["exp_month"].(float64) - if !ok { - return nil, errors.New("casting exp_month") - } - expYear, ok := src.TypeData["exp_year"].(float64) - if !ok { - return nil, errors.New("casting exp_year") - } - - cd := &stripe.Card{ - Brand: stripe.CardBrand(brand), - Last4: last4, - ExpMonth: uint8(expMonth), - ExpYear: uint16(expYear), - } - - return cd, nil - } - - return nil, errors.Errorf("malformed sourceID %s", sourceID) -} - -type updateStripeSourcePayload struct { - Source stripe.Source `json:"source"` - Country string `json:"country"` -} - -func validateUpdateStripeSourcePayload(p updateStripeSourcePayload) error { - if p.Source.ID == "" { - return errors.New("empty source id") - } - if p.Country == "" { - return errors.New("empty country") - } - - return nil -} - -func (a *API) updateStripeSource(w http.ResponseWriter, r *http.Request) { - user, ok := r.Context().Value(helpers.KeyUser).(database.User) - if !ok { - HandleError(w, "No authenticated user found", nil, http.StatusInternalServerError) - return - } - - var payload updateStripeSourcePayload - if err := json.NewDecoder(r.Body).Decode(&payload); err != nil { - HandleError(w, "decoding params", err, http.StatusBadRequest) - return - } - if err := validateUpdateStripeSourcePayload(payload); err != nil { - http.Error(w, errors.Wrap(err, "validating payload").Error(), http.StatusBadRequest) - return - } - - tx := a.App.DB.Begin() - - if err := tx.Model(&user). - Update(map[string]interface{}{ - "billing_country": payload.Country, - }).Error; err != nil { - tx.Rollback() - HandleError(w, "updating user", err, http.StatusInternalServerError) - return - } - - c, err := customer.Get(user.StripeCustomerID, nil) - if err != nil { - tx.Rollback() - HandleError(w, "retriving customer", err, http.StatusInternalServerError) - return - } - - if _, err := removeCustomerSource(user.StripeCustomerID, c.DefaultSource.ID); err != nil { - tx.Rollback() - HandleError(w, "removing source", err, http.StatusInternalServerError) - return - } - - if _, err := addCustomerSource(user.StripeCustomerID, payload.Source.ID); err != nil { - tx.Rollback() - HandleError(w, "attaching source", err, http.StatusInternalServerError) - return - } - - if err := tx.Commit().Error; err != nil { - tx.Rollback() - HandleError(w, "committing transaction", err, http.StatusInternalServerError) - return - } - - w.WriteHeader(http.StatusOK) -} - -func (a *API) getStripeSource(w http.ResponseWriter, r *http.Request) { - user, ok := r.Context().Value(helpers.KeyUser).(database.User) - if !ok { - HandleError(w, "No authenticated user found", nil, http.StatusInternalServerError) - return - } - if user.StripeCustomerID == "" { - respondWithEmptyStripeToken(w) - return - } - - c, err := customer.Get(user.StripeCustomerID, nil) - if err != nil { - HandleError(w, "fetching stripe customer", err, http.StatusInternalServerError) - return - } - - if c.DefaultSource == nil { - respondWithEmptyStripeToken(w) - return - } - - cd, err := getStripeCard(user.StripeCustomerID, c.DefaultSource.ID) - if err != nil { - HandleError(w, "fetching stripe source", err, http.StatusInternalServerError) - return - } - - resp := GetStripeSourceResponse{ - Brand: string(cd.Brand), - Last4: cd.Last4, - ExpMonth: cd.ExpMonth, - ExpYear: cd.ExpYear, - } - - respondJSON(w, http.StatusOK, resp) -} - -func (a *API) stripeWebhook(w http.ResponseWriter, req *http.Request) { - body, err := ioutil.ReadAll(req.Body) - if err != nil { - HandleError(w, "reading body", err, http.StatusServiceUnavailable) - return - } - - webhookSecret := os.Getenv("StripeWebhookSecret") - event, err := webhook.ConstructEvent(body, req.Header.Get("Stripe-Signature"), webhookSecret) - if err != nil { - HandleError(w, "verifying stripe webhook signature", err, http.StatusBadRequest) - return - } - - switch event.Type { - case "customer.subscription.deleted": - { - var subscription stripe.Subscription - if json.Unmarshal(event.Data.Raw, &subscription); err != nil { - HandleError(w, "unmarshaling payload", err, http.StatusBadRequest) - return - } - - a.App.MarkUnsubscribed(subscription.Customer.ID) - } - default: - { - msg := fmt.Sprintf("Unsupported webhook event type %s", event.Type) - HandleError(w, msg, err, http.StatusBadRequest) - return - } - } - - // Return a response to acknowledge receipt of the event - w.WriteHeader(http.StatusOK) -} diff --git a/pkg/server/job/remind/inactive_test.go b/pkg/server/job/remind/inactive_test.go index d8380d5f..2f63cdcf 100644 --- a/pkg/server/job/remind/inactive_test.go +++ b/pkg/server/job/remind/inactive_test.go @@ -46,7 +46,7 @@ func getTestContext(c clock.Clock, be *testutils.MockEmailbackendImplementation) } func TestDoInactive(t *testing.T) { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) t1 := time.Now() @@ -119,7 +119,7 @@ func TestDoInactive(t *testing.T) { } func TestDoInactive_Cooldown(t *testing.T) { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) // setup sets up an inactive user setup := func(t *testing.T, now time.Time, email string) database.User { diff --git a/pkg/server/job/remind/main_test.go b/pkg/server/job/remind/main_test.go index 8a6f5f73..c92e1bef 100644 --- a/pkg/server/job/remind/main_test.go +++ b/pkg/server/job/remind/main_test.go @@ -29,7 +29,7 @@ func TestMain(m *testing.M) { testutils.InitTestDB() code := m.Run() - testutils.ClearData() + testutils.ClearData(testutils.DB) os.Exit(code) } diff --git a/pkg/server/main.go b/pkg/server/main.go index 504fdf9d..a3108760 100644 --- a/pkg/server/main.go +++ b/pkg/server/main.go @@ -25,10 +25,10 @@ import ( "net/http" "github.com/dnote/dnote/pkg/clock" + "github.com/dnote/dnote/pkg/server/api" "github.com/dnote/dnote/pkg/server/app" "github.com/dnote/dnote/pkg/server/config" "github.com/dnote/dnote/pkg/server/database" - "github.com/dnote/dnote/pkg/server/handlers" "github.com/dnote/dnote/pkg/server/job" "github.com/dnote/dnote/pkg/server/mailer" "github.com/dnote/dnote/pkg/server/web" @@ -68,8 +68,7 @@ func initWebContext(db *gorm.DB) web.Context { } func initServer(a app.App) (*http.ServeMux, error) { - api := handlers.API{App: &a} - apiRouter, err := api.NewRouter() + apiRouter, err := api.NewRouter(&api.API{App: &a}) if err != nil { return nil, errors.Wrap(err, "initializing router") } @@ -104,12 +103,11 @@ func initApp(c config.Config) app.App { db := initDB(c) return app.App{ - DB: db, - Clock: clock.New(), - StripeAPIBackend: nil, - EmailTemplates: mailer.NewTemplates(nil), - EmailBackend: &mailer.SimpleBackendImplementation{}, - Config: c, + DB: db, + Clock: clock.New(), + EmailTemplates: mailer.NewTemplates(nil), + EmailBackend: &mailer.SimpleBackendImplementation{}, + Config: c, } } diff --git a/pkg/server/operations/main_test.go b/pkg/server/operations/main_test.go index 1bd6c07e..5421d78f 100644 --- a/pkg/server/operations/main_test.go +++ b/pkg/server/operations/main_test.go @@ -29,7 +29,7 @@ func TestMain(m *testing.M) { testutils.InitTestDB() code := m.Run() - testutils.ClearData() + testutils.ClearData(testutils.DB) os.Exit(code) } diff --git a/pkg/server/operations/notes_test.go b/pkg/server/operations/notes_test.go index 415950cb..a9f1e816 100644 --- a/pkg/server/operations/notes_test.go +++ b/pkg/server/operations/notes_test.go @@ -31,7 +31,7 @@ func TestGetNote(t *testing.T) { user := testutils.SetupUserData() anotherUser := testutils.SetupUserData() - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) b1 := database.Book{ UserID: user.ID, @@ -121,7 +121,7 @@ func TestGetNote(t *testing.T) { func TestGetNote_nonexistent(t *testing.T) { user := testutils.SetupUserData() - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) b1 := database.Book{ UserID: user.ID, diff --git a/pkg/server/permissions/permissions_test.go b/pkg/server/permissions/permissions_test.go index 60b46ffe..3508400c 100644 --- a/pkg/server/permissions/permissions_test.go +++ b/pkg/server/permissions/permissions_test.go @@ -31,7 +31,7 @@ func TestMain(m *testing.M) { testutils.InitTestDB() code := m.Run() - testutils.ClearData() + testutils.ClearData(testutils.DB) os.Exit(code) } @@ -40,7 +40,7 @@ func TestViewNote(t *testing.T) { user := testutils.SetupUserData() anotherUser := testutils.SetupUserData() - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) b1 := database.Book{ UserID: user.ID, diff --git a/pkg/server/session/session.go b/pkg/server/session/session.go new file mode 100644 index 00000000..cf0eacd2 --- /dev/null +++ b/pkg/server/session/session.go @@ -0,0 +1,23 @@ +package session + +import ( + "github.com/dnote/dnote/pkg/server/database" +) + +// Session represents user session +type Session struct { + UUID string `json:"uuid"` + Email string `json:"email"` + EmailVerified bool `json:"email_verified"` + Pro bool `json:"pro"` +} + +// New returns a new session for the given user +func New(user database.User, account database.Account) Session { + return Session{ + UUID: user.UUID, + Pro: user.Cloud, + Email: account.Email.String, + EmailVerified: account.EmailVerified, + } +} diff --git a/pkg/server/session/session_test.go b/pkg/server/session/session_test.go new file mode 100644 index 00000000..bd05f51e --- /dev/null +++ b/pkg/server/session/session_test.go @@ -0,0 +1,67 @@ +/* Copyright (C) 2019, 2020 Monomax Software Pty Ltd + * + * This file is part of Dnote. + * + * Dnote is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Dnote is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Dnote. If not, see . + */ + +package session + +import ( + "fmt" + "testing" + + "github.com/dnote/dnote/pkg/assert" + "github.com/dnote/dnote/pkg/server/database" +) + +func TestNew(t *testing.T) { + u1 := database.User{UUID: "0f5f0054-d23f-4be1-b5fb-57673109e9cb", Cloud: true} + a1 := database.Account{Email: database.ToNullString("alice@example.com"), EmailVerified: false} + + u2 := database.User{UUID: "718a1041-bbe6-496e-bbe4-ea7e572c295e", Cloud: false} + a2 := database.Account{Email: database.ToNullString("bob@example.com"), EmailVerified: false} + + testCases := []struct { + user database.User + account database.Account + expectedPro bool + }{ + { + user: u1, + account: a1, + expectedPro: true, + }, + { + user: u2, + account: a2, + expectedPro: false, + }, + } + + for _, tc := range testCases { + t.Run(fmt.Sprintf("user pro %t", tc.expectedPro), func(t *testing.T) { + // Execute + got := New(tc.user, tc.account) + expected := Session{ + UUID: tc.user.UUID, + Pro: tc.expectedPro, + Email: tc.account.Email.String, + EmailVerified: tc.account.EmailVerified, + } + + assert.DeepEqual(t, got, expected, "result mismatch") + }) + } +} diff --git a/pkg/server/testutils/main.go b/pkg/server/testutils/main.go index a37f538c..ba3cd5ae 100644 --- a/pkg/server/testutils/main.go +++ b/pkg/server/testutils/main.go @@ -25,7 +25,6 @@ import ( "fmt" "math/rand" "net/http" - "net/http/httptest" "strings" "sync" "testing" @@ -35,7 +34,6 @@ import ( "github.com/dnote/dnote/pkg/server/database" "github.com/jinzhu/gorm" "github.com/pkg/errors" - "github.com/stripe/stripe-go" "golang.org/x/crypto/bcrypt" ) @@ -59,29 +57,29 @@ func InitTestDB() { } // ClearData deletes all records from the database -func ClearData() { - if err := DB.Delete(&database.Book{}).Error; err != nil { +func ClearData(db *gorm.DB) { + if err := db.Delete(&database.Book{}).Error; err != nil { panic(errors.Wrap(err, "Failed to clear books")) } - if err := DB.Delete(&database.Note{}).Error; err != nil { + if err := db.Delete(&database.Note{}).Error; err != nil { panic(errors.Wrap(err, "Failed to clear notes")) } - if err := DB.Delete(&database.Notification{}).Error; err != nil { + if err := db.Delete(&database.Notification{}).Error; err != nil { panic(errors.Wrap(err, "Failed to clear notifications")) } - if err := DB.Delete(&database.User{}).Error; err != nil { + if err := db.Delete(&database.User{}).Error; err != nil { panic(errors.Wrap(err, "Failed to clear users")) } - if err := DB.Delete(&database.Account{}).Error; err != nil { + if err := db.Delete(&database.Account{}).Error; err != nil { panic(errors.Wrap(err, "Failed to clear accounts")) } - if err := DB.Delete(&database.Token{}).Error; err != nil { + if err := db.Delete(&database.Token{}).Error; err != nil { panic(errors.Wrap(err, "Failed to clear tokens")) } - if err := DB.Delete(&database.EmailPreference{}).Error; err != nil { + if err := db.Delete(&database.EmailPreference{}).Error; err != nil { panic(errors.Wrap(err, "Failed to clear email preferences")) } - if err := DB.Delete(&database.Session{}).Error; err != nil { + if err := db.Delete(&database.Session{}).Error; err != nil { panic(errors.Wrap(err, "Failed to clear sessions")) } } @@ -168,8 +166,8 @@ func HTTPDo(t *testing.T, req *http.Request) *http.Response { return res } -// HTTPAuthDo makes an HTTP request with an appropriate authorization header for a user -func HTTPAuthDo(t *testing.T, req *http.Request, user database.User) *http.Response { +// SetReqAuthHeader sets the authorization header in the given request for the given user +func SetReqAuthHeader(t *testing.T, req *http.Request, user database.User) { b := make([]byte, 32) if _, err := rand.Read(b); err != nil { t.Fatal(errors.Wrap(err, "reading random bits")) @@ -185,6 +183,11 @@ func HTTPAuthDo(t *testing.T, req *http.Request, user database.User) *http.Respo } req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", session.Key)) +} + +// HTTPAuthDo makes an HTTP request with an appropriate authorization header for a user +func HTTPAuthDo(t *testing.T, req *http.Request, user database.User) *http.Response { + SetReqAuthHeader(t, req, user) return HTTPDo(t, req) @@ -223,20 +226,6 @@ func GetCookieByName(cookies []*http.Cookie, name string) *http.Cookie { return ret } -// CreateMockStripeBackend returns a mock stripe backend that uses -// the given test server -func CreateMockStripeBackend(ts *httptest.Server) stripe.Backend { - stripeMockBackend := stripe.GetBackendWithConfig( - stripe.APIBackend, - &stripe.BackendConfig{ - URL: ts.URL, - HTTPClient: ts.Client(), - }, - ) - - return stripeMockBackend -} - // MustRespondJSON responds with the JSON-encoding of the given interface. If the encoding // fails, the test fails. It is used by test servers. func MustRespondJSON(t *testing.T, w http.ResponseWriter, i interface{}, message string) { diff --git a/pkg/server/tmpl/app_test.go b/pkg/server/tmpl/app_test.go index fd8c013f..6a1f08f5 100644 --- a/pkg/server/tmpl/app_test.go +++ b/pkg/server/tmpl/app_test.go @@ -50,7 +50,7 @@ func TestAppShellExecute(t *testing.T) { }) t.Run("note", func(t *testing.T) { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) user := testutils.SetupUserData() b1 := database.Book{ diff --git a/pkg/server/tmpl/main_test.go b/pkg/server/tmpl/main_test.go index 13041126..25a9741c 100644 --- a/pkg/server/tmpl/main_test.go +++ b/pkg/server/tmpl/main_test.go @@ -29,7 +29,7 @@ func TestMain(m *testing.M) { testutils.InitTestDB() code := m.Run() - testutils.ClearData() + testutils.ClearData(testutils.DB) os.Exit(code) } diff --git a/pkg/server/token/main_test.go b/pkg/server/token/main_test.go index e6440ef2..b9cfb697 100644 --- a/pkg/server/token/main_test.go +++ b/pkg/server/token/main_test.go @@ -29,7 +29,7 @@ func TestMain(m *testing.M) { testutils.InitTestDB() code := m.Run() - testutils.ClearData() + testutils.ClearData(testutils.DB) os.Exit(code) } diff --git a/pkg/server/token/token_test.go b/pkg/server/token/token_test.go index 92a4c23b..79c643da 100644 --- a/pkg/server/token/token_test.go +++ b/pkg/server/token/token_test.go @@ -39,7 +39,7 @@ func TestCreate(t *testing.T) { for _, tc := range testCases { t.Run(fmt.Sprintf("token type %s", tc.kind), func(t *testing.T) { - defer testutils.ClearData() + defer testutils.ClearData(testutils.DB) // Set up u := testutils.SetupUserData() diff --git a/pkg/server/web/handlers.go b/pkg/server/web/handlers.go index ba7a8de4..870408a8 100644 --- a/pkg/server/web/handlers.go +++ b/pkg/server/web/handlers.go @@ -108,7 +108,7 @@ func getRootHandler(c Context) http.HandlerFunc { if errors.Cause(err) == tmpl.ErrNotFound { handlers.RespondNotFound(w) } else { - handlers.HandleError(w, "executing app shell", err, http.StatusInternalServerError) + handlers.DoError(w, "executing app shell", err, http.StatusInternalServerError) } return } diff --git a/pkg/server/web/main_test.go b/pkg/server/web/main_test.go index 2d2b3e78..b398db05 100644 --- a/pkg/server/web/main_test.go +++ b/pkg/server/web/main_test.go @@ -29,7 +29,7 @@ func TestMain(m *testing.M) { testutils.InitTestDB() code := m.Run() - testutils.ClearData() + testutils.ClearData(testutils.DB) os.Exit(code) } diff --git a/scripts/web/build-prod.sh b/scripts/web/build-prod.sh index 5372700a..7680ed35 100755 --- a/scripts/web/build-prod.sh +++ b/scripts/web/build-prod.sh @@ -17,5 +17,6 @@ ASSET_BASE_URL="$assetBaseUrl" \ ROOT_URL="$rootUrl" \ PUBLIC_PATH="$publicPath" \ COMPILED_PATH="$compiledPath" \ +STANDALONE=true \ VERSION="$VERSION" \ "$dir/build.sh" diff --git a/scripts/web/build.sh b/scripts/web/build.sh index 94945e10..2c1f24ff 100755 --- a/scripts/web/build.sh +++ b/scripts/web/build.sh @@ -4,7 +4,7 @@ set -ex dir=$(dirname "${BASH_SOURCE[0]}") basePath="$dir/../.." -isTest=${IS_TEST:-false} +standalone=${STANDALONE:-true} set -u rm -rf "$basePath/web/public" @@ -22,7 +22,7 @@ pushd "$basePath/web" "$basePath"/web/node_modules/.bin/webpack\ --colors\ --display-error-details\ - --env.isTest="$isTest"\ + --env.standalone="$standalone"\ --config "$(realpath "$basePath/web/webpack/prod.config.js")" NODE_ENV=PRODUCTION \ diff --git a/scripts/web/dev.sh b/scripts/web/dev.sh index cbe972fa..96cfc6f7 100755 --- a/scripts/web/dev.sh +++ b/scripts/web/dev.sh @@ -29,7 +29,7 @@ set +a COMPILED_PATH="$appPath"/compiled \ PUBLIC_PATH="$appPath"/public \ COMPILED_PATH="$basePath/web/compiled" \ - IS_TEST=true \ + STANDALONE=true \ VERSION="$VERSION" \ WEBPACK_HOST="0.0.0.0" \ "$dir/webpack-dev.sh" diff --git a/scripts/web/webpack-dev.sh b/scripts/web/webpack-dev.sh index 6506dae2..7a44bc88 100755 --- a/scripts/web/webpack-dev.sh +++ b/scripts/web/webpack-dev.sh @@ -16,13 +16,12 @@ appPath="$basePath/web" ASSET_BASE_URL=$ASSET_BASE_URL \ COMPILED_PATH=$COMPILED_PATH \ PUBLIC_PATH=$PUBLIC_PATH \ - IS_TEST=true \ node "$dir/placeholder.js" && ROOT_URL=$ROOT_URL \ VERSION="$VERSION" \ "$appPath"/node_modules/.bin/webpack-dev-server \ - --env.isTest="$IS_TEST" \ + --env.standalone="$STANDALONE" \ --host "$WEBPACK_HOST" \ --config "$appPath"/webpack/dev.config.js ) diff --git a/web/declrations.d.ts b/web/declrations.d.ts index 7d93a96b..08b5aef0 100644 --- a/web/declrations.d.ts +++ b/web/declrations.d.ts @@ -20,7 +20,7 @@ declare module '*.scss'; // globals defined by webpack-define-plugin -declare const __STRIPE_PUBLIC_KEY__: string; declare const __ROOT_URL__: string; declare const __CDN_URL__: string; declare const __VERSION__: string; +declare const __STANDALONE__: string; diff --git a/web/package-lock.json b/web/package-lock.json index c853b243..812a22be 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -10631,14 +10631,6 @@ "shallowequal": "^1.0.1" } }, - "react-stripe-elements": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/react-stripe-elements/-/react-stripe-elements-5.1.0.tgz", - "integrity": "sha512-4UlzOLNdbJsZr4JwbTdJjxhedAfalDDtfEYLHEBo0MKG0KgSLRAeIJup0/6NSpKtBLK4ieOMAwvyln9ICOSilQ==", - "requires": { - "prop-types": "15.7.2" - } - }, "read-pkg": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", diff --git a/web/package.json b/web/package.json index 3eb1b68d..8745a296 100644 --- a/web/package.json +++ b/web/package.json @@ -65,7 +65,6 @@ "react-router": "^5.1.2", "react-router-config": "^5.1.1", "react-router-dom": "^5.1.2", - "react-stripe-elements": "^5.1.0", "redux": "^4.0.5", "redux-thunk": "^2.1.0", "regenerator-runtime": "^0.13.5", diff --git a/web/src/components/App/index.tsx b/web/src/components/App/index.tsx index db3e74cf..092b77e3 100644 --- a/web/src/components/App/index.tsx +++ b/web/src/components/App/index.tsx @@ -31,8 +31,7 @@ import { homePathDef, noFooterPaths, noHeaderPaths, - notePathDef, - subscriptionPaths + notePathDef } from 'web/libs/paths'; import render from '../../routes'; import { useDispatch, useSelector } from '../../store'; @@ -45,7 +44,6 @@ import MobileMenu from '../Common/MobileMenu'; import SystemMessage from '../Common/SystemMessage'; import NormalHeader from '../Header/Normal'; import NoteHeader from '../Header/Note'; -import SubscriptionHeader from '../Header/SubscriptionHeader'; import Splash from '../Splash'; import TabBar from '../TabBar'; import './App.global.scss'; @@ -179,7 +177,6 @@ const App: React.FunctionComponent = ({ location }) => { - diff --git a/web/src/components/Common/Note/Footer.tsx b/web/src/components/Common/Note/Footer.tsx index 72fc26d2..e9cc9bc8 100644 --- a/web/src/components/Common/Note/Footer.tsx +++ b/web/src/components/Common/Note/Footer.tsx @@ -20,13 +20,11 @@ import React from 'react'; import { NoteData } from 'jslib/operations/types'; import Time from '../../Common/Time'; -import { nanosecToMillisec } from '../../../helpers/time'; import formatTime from '../../../helpers/time/format'; import { timeAgo } from '../../../helpers/time'; import styles from './Note.scss'; -function formatAddedOn(ts: number): string { - const ms = nanosecToMillisec(ts); +function formatAddedOn(ms: number): string { const d = new Date(ms); return formatTime(d, '%MMMM %DD, %YYYY'); @@ -49,11 +47,13 @@ const Footer: React.FunctionComponent = ({ return null; } + const updatedAt = new Date(note.updatedAt).getTime(); + let timeText; if (useTimeAgo) { - timeText = timeAgo(nanosecToMillisec(note.addedOn)); + timeText = timeAgo(updatedAt); } else { - timeText = formatAddedOn(note.addedOn); + timeText = formatAddedOn(updatedAt); } return ( @@ -63,7 +63,7 @@ const Footer: React.FunctionComponent = ({