diff --git a/cloud/config/cors-studio-staging.json b/cloud/config/cors-studio-staging.json new file mode 100644 index 000000000..88bb91bcb --- /dev/null +++ b/cloud/config/cors-studio-staging.json @@ -0,0 +1,16 @@ +[ + { + "origin": [ + "http://localhost:3333", + "http://localhost:3334", + "https://deckdeckgo.app", + "https://deckdeckgo-studio-staging.web.app", + "https://deckdeckgo-studio-staging.firebaseapp.com", + "https://deckdeckgo-app-staging.web.app", + "https://deckdeckgo-app-staging.firebaseapp.com" + ], + "responseHeader": ["Content-Type"], + "method": ["GET"], + "maxAgeSeconds": 3600 + } +] diff --git a/cloud/config/set-cloud-config-dev.sh b/cloud/config/set-cloud-config-dev.sh new file mode 100755 index 000000000..e0bb5ae34 --- /dev/null +++ b/cloud/config/set-cloud-config-dev.sh @@ -0,0 +1,4 @@ +#!/bin/sh +firebase functions:config:set mailchimp.skip="true" info.mail.skip="true" github.skip="true" deckdeckgo.presentation.url="https://beta.deckdeckgo.io" deckdeckgo.api.skip="true" + +firebase functions:config:get diff --git a/cloud/config/set-cors-gcp.sh b/cloud/config/set-cors-gcp.sh new file mode 100755 index 000000000..bcaf380de --- /dev/null +++ b/cloud/config/set-cors-gcp.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +# https://cloud.google.com/storage/docs/configuring-cors?hl=fr + +gsutil cors set cors-studio-beta.json gs://deckdeckgo-studio-beta.appspot.com diff --git a/cloud/functions/package-lock.json b/cloud/functions/package-lock.json index 3cc3be2f3..c3287e67f 100644 --- a/cloud/functions/package-lock.json +++ b/cloud/functions/package-lock.json @@ -41,83 +41,106 @@ "integrity": "sha512-88h74TMQ6wXChPA6h9Q3E1Jg6TkTHep2+k63OWg3s0ozyGVMeY+TTOti7PFPzq5RhszQPQOoCi59es4MaRvgCw==" }, "@firebase/component": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.12.tgz", - "integrity": "sha512-03w800MxR/EW1m7N0Q46WNcngwdDIHDWpFPHTdbZEI6U/HuLks5RJQlBxWqb1P73nYPkN8YP3U8gTdqrDpqY3Q==", + "version": "0.1.18", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.18.tgz", + "integrity": "sha512-c8gd1k/e0sbBTR0xkLIYUN8nVkA0zWxcXGIvdfYtGEsNw6n7kh5HkcxKXOPB8S7bcPpqZkGgBIfvd94IyG2gaQ==", "requires": { - "@firebase/util": "0.2.47", - "tslib": "1.11.1" + "@firebase/util": "0.3.1", + "tslib": "^1.11.1" } }, "@firebase/database": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.6.3.tgz", - "integrity": "sha512-gHoCISHQVLoq+rGu+PorYxMkhsjhXov3ocBxz/0uVdznNhrbKkAZaEKF+dIAsUPDlwSYeZuwWuik7xcV3DtRaw==", + "version": "0.6.11", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.6.11.tgz", + "integrity": "sha512-QOHhB7+CdjVhEXG9CyX0roA9ARJcEuwbozz0Bix+ULuZqjQ58KUFHMH1apW6EEiUP22d/mYD7dNXsUGshjL9PA==", "requires": { "@firebase/auth-interop-types": "0.1.5", - "@firebase/component": "0.1.12", - "@firebase/database-types": "0.5.1", - "@firebase/logger": "0.2.4", - "@firebase/util": "0.2.47", + "@firebase/component": "0.1.18", + "@firebase/database-types": "0.5.2", + "@firebase/logger": "0.2.6", + "@firebase/util": "0.3.1", "faye-websocket": "0.11.3", - "tslib": "1.11.1" + "tslib": "^1.11.1" } }, "@firebase/database-types": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.5.1.tgz", - "integrity": "sha512-onQxom1ZBYBJ648w/VNRzUewovEDAH7lvnrrpCd69ukkyrMk6rGEO/PQ9BcNEbhlNtukpsqRS0oNOFlHs0FaSA==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.5.2.tgz", + "integrity": "sha512-ap2WQOS3LKmGuVFKUghFft7RxXTyZTDr0Xd8y2aqmWsbJVjgozi0huL/EUMgTjGFrATAjcf2A7aNs8AKKZ2a8g==", "requires": { "@firebase/app-types": "0.6.1" } }, "@firebase/logger": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.4.tgz", - "integrity": "sha512-akHkOU7izYB1okp/B5sxClGjjw6KvZdSHyjNM5pKd67Zg5W6PsbkI/GFNv21+y6LkUkJwDRbdeDgJoYXWT3mMA==" + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.6.tgz", + "integrity": "sha512-KIxcUvW/cRGWlzK9Vd2KB864HlUnCfdTH0taHE0sXW5Xl7+W68suaeau1oKNEqmc3l45azkd4NzXTCWZRZdXrw==" }, "@firebase/util": { - "version": "0.2.47", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.47.tgz", - "integrity": "sha512-RjcIvcfswyxYhf0OMXod+qeI/933wl9FGLIszf0/O1yMZ/s8moXcse7xnOpMjmQPRLB9vHzCMoxW5X90kKg/bQ==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.1.tgz", + "integrity": "sha512-zjVd9rfL08dRRdZILFn1RZTHb1euCcnD9N/9P56gdBcm2bvT5XsCC4G6t5toQBpE/H/jYe5h6MZMqfLu3EQLXw==", "requires": { - "tslib": "1.11.1" + "tslib": "^1.11.1" } }, "@google-cloud/common": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-2.4.0.tgz", - "integrity": "sha512-zWFjBS35eI9leAHhjfeOYlK5Plcuj/77EzstnrJIZbKgF/nkqjcQuGiMCpzCwOfPyUbz8ZaEOYgbHa759AKbjg==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-3.3.3.tgz", + "integrity": "sha512-2PwPDE47N4WiWQK/F35vE5aWVoCjKQ2NW8r8OFAg6QslkLMjX6WNcmUO8suYlSkavc58qOvzA4jG6eVkC90i8Q==", "optional": true, "requires": { - "@google-cloud/projectify": "^1.0.0", - "@google-cloud/promisify": "^1.0.0", - "arrify": "^2.0.0", - "duplexify": "^3.6.0", + "@google-cloud/projectify": "^2.0.0", + "@google-cloud/promisify": "^2.0.0", + "arrify": "^2.0.1", + "duplexify": "^4.1.1", "ent": "^2.2.0", "extend": "^3.0.2", - "google-auth-library": "^5.5.0", - "retry-request": "^4.0.0", - "teeny-request": "^6.0.0" + "google-auth-library": "^6.0.0", + "retry-request": "^4.1.1", + "teeny-request": "^7.0.0" + }, + "dependencies": { + "duplexify": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.1.tgz", + "integrity": "sha512-DY3xVEmVHTv1wSzKNbwoU6nVjzI369Y6sPoqfYr0/xlx3IdX2n94xIszTcjPO8W8ZIv0Wb0PXNcjuZyT4wiICA==", + "optional": true, + "requires": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.0" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "optional": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } } }, "@google-cloud/firestore": { - "version": "3.8.2", - "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-3.8.2.tgz", - "integrity": "sha512-COaQKb+kdboURMPRGOtMpklC2qFN2Q+SHGx+Euk4jwjBJQ5UZja+GIEcl4uo1FbJ2fAqKP9gzAqCU2BpOwFY1w==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-4.2.0.tgz", + "integrity": "sha512-YCiKaTYCbXSoEvZ8cTmpgg4ebAvmFUOu3hj/aX+lHiOK7LsoFVi4jgNknogSqIiv04bxAysTBodpgn8XoZ4l5g==", "optional": true, "requires": { - "deep-equal": "^2.0.0", + "fast-deep-equal": "^3.1.1", "functional-red-black-tree": "^1.0.1", - "google-gax": "^1.13.0", - "readable-stream": "^3.4.0", - "through2": "^3.0.0" + "google-gax": "^2.2.0" } }, "@google-cloud/paginator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-2.0.3.tgz", - "integrity": "sha512-kp/pkb2p/p0d8/SKUu4mOq8+HGwF8NPzHWkj+VKrIPQPyMRw8deZtrO/OcSiy9C/7bpfU5Txah5ltUNfPkgEXg==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-3.0.5.tgz", + "integrity": "sha512-N4Uk4BT1YuskfRhKXBs0n9Lg2YTROZc6IMpkO/8DIHODtm5s3xY8K5vVBo23v/2XulY3azwITQlYWgT4GdLsUw==", "optional": true, "requires": { "arrify": "^2.0.0", @@ -125,80 +148,93 @@ } }, "@google-cloud/projectify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-1.0.4.tgz", - "integrity": "sha512-ZdzQUN02eRsmTKfBj9FDL0KNDIFNjBn/d6tHQmA/+FImH5DO6ZV8E7FzxMgAUiVAUq41RFAkb25p1oHOZ8psfg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-2.0.1.tgz", + "integrity": "sha512-ZDG38U/Yy6Zr21LaR3BTiiLtpJl6RkPS/JwoRT453G+6Q1DhlV0waNf8Lfu+YVYGIIxgKnLayJRfYlFJfiI8iQ==", "optional": true }, "@google-cloud/promisify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-1.0.4.tgz", - "integrity": "sha512-VccZDcOql77obTnFh0TbNED/6ZbbmHDf8UMNnzO1d5g9V0Htfm4k5cllY8P1tJsRKC3zWYGRLaViiupcgVjBoQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-2.0.2.tgz", + "integrity": "sha512-EvuabjzzZ9E2+OaYf+7P9OAiiwbTxKYL0oGLnREQd+Su2NTQBpomkdlkBowFvyWsaV0d1sSGxrKpSNcrhPqbxg==", "optional": true }, "@google-cloud/storage": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-4.7.0.tgz", - "integrity": "sha512-f0guAlbeg7Z0m3gKjCfBCu7FG9qS3M3oL5OQQxlvGoPtK7/qg3+W+KQV73O2/sbuS54n0Kh2mvT5K2FWzF5vVQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-5.3.0.tgz", + "integrity": "sha512-3t5UF3SZ14Bw2kcBHubCai6EIugU2GnQOstYWVSFuoO8IJ94RAaIOPq/dtexvQbUTpBTAGpd5smVR9WPL1mJVw==", "optional": true, "requires": { - "@google-cloud/common": "^2.1.1", - "@google-cloud/paginator": "^2.0.0", - "@google-cloud/promisify": "^1.0.0", + "@google-cloud/common": "^3.3.0", + "@google-cloud/paginator": "^3.0.0", + "@google-cloud/promisify": "^2.0.0", "arrify": "^2.0.0", "compressible": "^2.0.12", "concat-stream": "^2.0.0", - "date-and-time": "^0.13.0", + "date-and-time": "^0.14.0", "duplexify": "^3.5.0", "extend": "^3.0.2", "gaxios": "^3.0.0", - "gcs-resumable-upload": "^2.2.4", + "gcs-resumable-upload": "^3.1.0", "hash-stream-validation": "^0.2.2", "mime": "^2.2.0", "mime-types": "^2.0.8", "onetime": "^5.1.0", - "p-limit": "^2.2.0", + "p-limit": "^3.0.1", "pumpify": "^2.0.0", - "readable-stream": "^3.4.0", "snakeize": "^0.1.0", "stream-events": "^1.0.1", - "through2": "^3.0.0", "xdg-basedir": "^4.0.0" + }, + "dependencies": { + "p-limit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz", + "integrity": "sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==", + "optional": true, + "requires": { + "p-try": "^2.0.0" + } + } } }, "@grpc/grpc-js": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.0.4.tgz", - "integrity": "sha512-Qawt6HUrEmljQMPWnLnIXpcjelmtIAydi3M9awiG02WWJ1CmIvFEx4IOC1EsWUWUlabOGksRbpfvoIeZKFTNXw==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.1.5.tgz", + "integrity": "sha512-2huf5z85TdZI4nLmJQ9Zdfd+6vmIyBDs7B4L71bTaHKA9pRsGKAH24XaktMk/xneKJIqAgeIZtg1cyivVZtvrg==", "optional": true, "requires": { + "@grpc/proto-loader": "^0.6.0-pre14", + "@types/node": "^12.12.47", "google-auth-library": "^6.0.0", "semver": "^6.2.0" }, "dependencies": { - "google-auth-library": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.0.0.tgz", - "integrity": "sha512-uLydy1t6SHN/EvYUJrtN3GCHFrnJ0c8HJjOxXiGjoTuYHIoCUT3jVxnzmjHwVnSdkfE9Akasm2rM6qG1COTXfQ==", + "@grpc/proto-loader": { + "version": "0.6.0-pre9", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.0-pre9.tgz", + "integrity": "sha512-oM+LjpEjNzW5pNJjt4/hq1HYayNeQT+eGrOPABJnYHv7TyNPDNzkQ76rDYZF86X5swJOa4EujEMzQ9iiTdPgww==", "optional": true, "requires": { - "arrify": "^2.0.0", - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "fast-text-encoding": "^1.0.0", - "gaxios": "^3.0.0", - "gcp-metadata": "^4.0.0", - "gtoken": "^5.0.0", - "jws": "^4.0.0", - "lru-cache": "^5.0.0" + "@types/long": "^4.0.1", + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^6.9.0", + "yargs": "^15.3.1" } + }, + "@types/node": { + "version": "12.12.55", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.55.tgz", + "integrity": "sha512-Vd6xQUVvPCTm7Nx1N7XHcpX6t047ltm7TgcsOr4gFHjeYgwZevo+V7I1lfzHnj5BT5frztZ42+RTG4MwYw63dw==", + "optional": true } } }, "@grpc/proto-loader": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.5.4.tgz", - "integrity": "sha512-HTM4QpI9B2XFkPz7pjwMyMgZchJ93TVkL3kWPW8GDMDKYxsMnmf4w2TNMJK7+KNiYHS5cJrCEAFlF+AwtXWVPA==", + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.5.5.tgz", + "integrity": "sha512-WwN9jVNdHRQoOBo9FDH7qU+mgfjPc8GygPYms3M+y3fbQLfnCe/Kv/E01t7JRgnrsOHH8euvSbed3mIalXhwqQ==", "optional": true, "requires": { "lodash.camelcase": "^4.3.0", @@ -300,8 +336,7 @@ "@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", - "dev": true + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" }, "@types/connect": { "version": "3.4.33", @@ -311,6 +346,15 @@ "@types/node": "*" } }, + "@types/cors": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.7.tgz", + "integrity": "sha512-sOdDRU3oRS7LBNTIqwDkPJyq0lpHYcbMTt0TrjzsXbk/e37hcLTH6eZX7CdbDeN0yJJvzw9hFBZkbtCSbk/jAQ==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, "@types/express": { "version": "4.17.3", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.3.tgz", @@ -322,24 +366,15 @@ } }, "@types/express-serve-static-core": { - "version": "4.17.7", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.7.tgz", - "integrity": "sha512-EMgTj/DF9qpgLXyc+Btimg+XoH7A2liE8uKul8qSmMTHCeNYzydDKFdsJskDvw42UsesCnhO63dO0Grbj8J4Dw==", + "version": "4.17.12", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.12.tgz", + "integrity": "sha512-EaEdY+Dty1jEU7U6J4CUWwxL+hyEGMkO5jan5gplfegUgCUsIUWqXxqw47uGjimeT4Qgkz/XUfwoau08+fgvKA==", "requires": { "@types/node": "*", "@types/qs": "*", "@types/range-parser": "*" } }, - "@types/fs-extra": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.1.tgz", - "integrity": "sha512-TcUlBem321DFQzBNuz8p0CLLKp0VvF/XH9E4KHNmgwyp4E3AfgI5cjiIVZWlbfThBop2qxFIh4+LeY6hVWWZ2w==", - "optional": true, - "requires": { - "@types/node": "*" - } - }, "@types/glob": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", @@ -356,6 +391,17 @@ "integrity": "sha512-RqTqKEenGBSa/vS3qHQuhudWE1d1NbollRDoArx85k1vUg4rugc+odFQW13c6O5re7hjf6zaRnWz9St/j8h15w==", "dev": true }, + "@types/jsdom": { + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-16.2.4.tgz", + "integrity": "sha512-RssgLa5ptjVKRkHho/Ex0+DJWkVsYuV8oh2PSG3gKxFp8n/VNyB7kOrZGQkk2zgPlcBkIKOItUc/T5BXit9uhg==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/parse5": "*", + "@types/tough-cookie": "*" + } + }, "@types/long": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", @@ -363,9 +409,9 @@ "optional": true }, "@types/mime": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.2.tgz", - "integrity": "sha512-4kPlzbljFcsttWEq6aBW0OZe6BDajAmyvr2xknBG92tejQnvdGtT9+kXSZ580DqpxY9qG2xeQVF9Dq0ymUTo5Q==" + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.3.tgz", + "integrity": "sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q==" }, "@types/mime-types": { "version": "2.1.0", @@ -421,6 +467,12 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "@types/parse5": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-5.0.3.tgz", + "integrity": "sha512-kUNnecmtkunAoQ3CnjmMkzNU/gtxG8guhi+Fk2U/kOpIKjIMKnXGp4IJCgQJrXSgMsWYimYG4TGjz/UzbGEBTw==", + "dev": true + }, "@types/puppeteer": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/puppeteer/-/puppeteer-2.0.1.tgz", @@ -431,9 +483,9 @@ } }, "@types/qs": { - "version": "6.9.3", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.3.tgz", - "integrity": "sha512-7s9EQWupR1fTc2pSMtXRQ9w9gLOcrJn+h7HOXw4evxyvVqMi4f+q7d2tnFe3ng3SNHjtK+0EzGMGFUQX4/AQRA==" + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.4.tgz", + "integrity": "sha512-+wYo+L6ZF6BMoEjtf8zB2esQsqdV6WsjRK/GP9WOgLPrq87PbNWgIxS76dS5uvl/QXtHGakZmwTznIfcPXcKlQ==" }, "@types/range-parser": { "version": "1.2.3", @@ -451,14 +503,25 @@ } }, "@types/serve-static": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.4.tgz", - "integrity": "sha512-jTDt0o/YbpNwZbQmE/+2e+lfjJEJJR0I3OFaKQKPWkASkCoW3i6fsUnqudSMcNAfbtmADGu8f4MV4q+GqULmug==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.5.tgz", + "integrity": "sha512-6M64P58N+OXjU432WoLLBQxbA0LRGBCRm7aAGQJ+SMC1IMl0dgRVi9EFfoDcS2a7Xogygk/eGN94CfwU9UF7UQ==", "requires": { "@types/express-serve-static-core": "*", "@types/mime": "*" } }, + "@types/tough-cookie": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.0.tgz", + "integrity": "sha512-I99sngh224D0M7XgW1s120zxCt3VYQ3IQsuw3P3jbq5GG4yc79+ZjyKznyOGIQrflfylLgcfekeZW/vk0yng6A==", + "dev": true + }, + "abab": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.4.tgz", + "integrity": "sha512-Eu9ELJWCz/c1e9gTiCY+FceWxcqzjYEbqMgtndnuSqZSUCOL73TWNK2mHfIj4Cw2E/ongOp+JISVNCmovt2KYQ==" + }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -482,10 +545,29 @@ "negotiator": "0.6.2" } }, - "agent-base": { + "acorn": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.0.tgz", + "integrity": "sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==" + }, + "acorn-globals": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.0.tgz", - "integrity": "sha512-j1Q7cSCqN+AwrmDd+pzgqc0/NpC655x2bUf5ZjRIO77DcNBFmh+OgRNzF6OKdCC9RSCb19fGd99+bhXFdkRNqw==", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", + "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "requires": { + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1" + } + }, + "acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==" + }, + "agent-base": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.1.tgz", + "integrity": "sha512-01q25QQDwLSsyfhrKbn8yuur+JNw0H+0Y4JiGIKd3z9aYk/w/2kxD/Upc+t2ZBBSUNff50VjPsSW2YxM8QYKVg==", "optional": true, "requires": { "debug": "4" @@ -502,6 +584,12 @@ "uri-js": "^4.2.2" } }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "optional": true + }, "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -526,12 +614,6 @@ "integrity": "sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==", "dev": true }, - "array-filter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-1.0.0.tgz", - "integrity": "sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=", - "optional": true - }, "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -571,15 +653,6 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, - "available-typed-arrays": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz", - "integrity": "sha512-XWX3OX8Onv97LMk/ftVyBibpGwY5a8SmuxZPzeOxqmuEqUCOM9ZE+uIaD1VNJ5QnvU2UQusvmKbuM1FR8QWGfQ==", - "optional": true, - "requires": { - "array-filter": "^1.0.0" - } - }, "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -610,9 +683,9 @@ } }, "bignumber.js": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-7.2.1.tgz", - "integrity": "sha512-S4XzBk5sMB+Rcb/LNcpzXr57VRTxgAvaAEDAl1AwRx27j00hT84O6OkteE7u8UB3NuaaygCRrEpqox4uDOrbdQ==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", + "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==", "optional": true }, "bluebird": { @@ -661,6 +734,11 @@ "concat-map": "0.0.1" } }, + "browser-process-hrtime": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==" + }, "buffer-crc32": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", @@ -693,6 +771,12 @@ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "optional": true + }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", @@ -720,6 +804,17 @@ "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", "dev": true }, + "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "optional": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -778,6 +873,19 @@ "inherits": "^2.0.3", "readable-stream": "^3.0.2", "typedarray": "^0.0.6" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "optional": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } } }, "config-chain": { @@ -877,6 +985,26 @@ "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", "optional": true }, + "cssom": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", + "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==" + }, + "cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "requires": { + "cssom": "~0.3.6" + }, + "dependencies": { + "cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==" + } + } + }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -885,10 +1013,20 @@ "assert-plus": "^1.0.0" } }, + "data-urls": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", + "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", + "requires": { + "abab": "^2.0.3", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.0.0" + } + }, "date-and-time": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/date-and-time/-/date-and-time-0.13.1.tgz", - "integrity": "sha512-/Uge9DJAT+s+oAcDxtBhyR8+sKjUnZbYmyhbmWjTHNtX7B7oWD8YyYdeXcBRbwSj6hVvj+IQegJam7m7czhbFw==", + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/date-and-time/-/date-and-time-0.14.1.tgz", + "integrity": "sha512-M4RggEH5OF2ZuCOxgOU67R6Z9ohjKbxGvAQz48vj53wLmL0bAgumkBvycR32f30pK+Og9pIR+RFDyChbaE4oLA==", "optional": true }, "debug": { @@ -899,35 +1037,21 @@ "ms": "^2.1.1" } }, - "deep-equal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.0.3.tgz", - "integrity": "sha512-Spqdl4H+ky45I9ByyJtXteOm9CaIrPmnIPmOhrkKGNYWeDgCvJ8jNYVCTjChxW4FqGuZnLHADc8EKRMX6+CgvA==", - "optional": true, - "requires": { - "es-abstract": "^1.17.5", - "es-get-iterator": "^1.1.0", - "is-arguments": "^1.0.4", - "is-date-object": "^1.0.2", - "is-regex": "^1.0.5", - "isarray": "^2.0.5", - "object-is": "^1.1.2", - "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "regexp.prototype.flags": "^1.3.0", - "side-channel": "^1.0.2", - "which-boxed-primitive": "^1.0.1", - "which-collection": "^1.0.1", - "which-typed-array": "^1.1.2" - } - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "requires": { - "object-keys": "^1.0.12" - } + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "optional": true + }, + "decimal.js": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.0.tgz", + "integrity": "sha512-vDPw+rDgn3bZe1+F/pyEwb1oMG2XTlRVgAa6B4KccTEpYgF8w6eQllVbQcfIJnZyvzFtFpxnpGtx8dd7DJp/Rw==" + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" }, "delayed-stream": { "version": "1.0.0", @@ -958,6 +1082,21 @@ "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true }, + "domexception": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", + "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", + "requires": { + "webidl-conversions": "^5.0.0" + }, + "dependencies": { + "webidl-conversions": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", + "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==" + } + } + }, "dot-prop": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", @@ -977,35 +1116,6 @@ "inherits": "^2.0.1", "readable-stream": "^2.0.0", "stream-shift": "^1.0.0" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "optional": true - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "optional": true - } } }, "ecc-jsbn": { @@ -1062,6 +1172,12 @@ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "optional": true + }, "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -1090,49 +1206,6 @@ "is-arrayish": "^0.2.1" } }, - "es-abstract": { - "version": "1.17.5", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz", - "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==", - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.1.5", - "is-regex": "^1.0.5", - "object-inspect": "^1.7.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "string.prototype.trimleft": "^2.1.1", - "string.prototype.trimright": "^2.1.1" - } - }, - "es-get-iterator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.0.tgz", - "integrity": "sha512-UfrmHuWQlNMTs35e1ypnvikg6jCz3SK8v8ImvmDsh36fCVUR1MqoFDiyn0/k52C8NqO3YsO8Oe0azeesNuqSsQ==", - "optional": true, - "requires": { - "es-abstract": "^1.17.4", - "has-symbols": "^1.0.1", - "is-arguments": "^1.0.4", - "is-map": "^2.0.1", - "is-set": "^2.0.1", - "is-string": "^1.0.5", - "isarray": "^2.0.5" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -1144,11 +1217,32 @@ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "dev": true }, + "escodegen": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", + "requires": { + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + } + }, "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" }, "etag": { "version": "1.8.1", @@ -1162,18 +1256,18 @@ "optional": true }, "execa": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-2.1.0.tgz", - "integrity": "sha512-Y/URAVapfbYy2Xp/gb6A0E7iR8xeqOCXsuuaoMn7A5PzrXUK84E1gyiEfq0wQd/GHA6GsoHWwhNq8anb0mleIw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.0.3.tgz", + "integrity": "sha512-WFDXGHckXPWZX19t1kCsXzOpqX9LWYNqn4C+HqZlk/V0imTkzJZqf87ZBhvpHaftERYknpk0fjSylnXVlVgI0A==", "dev": true, "requires": { "cross-spawn": "^7.0.0", "get-stream": "^5.0.0", + "human-signals": "^1.1.1", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", - "npm-run-path": "^3.0.0", + "npm-run-path": "^4.0.0", "onetime": "^5.1.0", - "p-finally": "^2.0.0", "signal-exit": "^3.0.2", "strip-final-newline": "^2.0.0" } @@ -1329,10 +1423,15 @@ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" + }, "fast-text-encoding": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.2.tgz", - "integrity": "sha512-5rQdinSsycpzvAoHga2EDn+LRX1d5xLFsuNG0Kg61JrAT/tASXcLL0nf/33v+sAxlQcfYmWbTURa1mmAf55jGw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz", + "integrity": "sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig==", "optional": true }, "faye-websocket": { @@ -1384,7 +1483,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, "requires": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -1400,30 +1498,24 @@ } }, "firebase-admin": { - "version": "8.12.1", - "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-8.12.1.tgz", - "integrity": "sha512-DZ4Q7QQJYaO2BhnhZLrhL+mGRTCLS5WrxjbJtuKGmbKRBepwMhx++EQA5yhnGnIXgDHnp5SrZnVKygNdXtH8BQ==", - "requires": { - "@firebase/database": "^0.6.0", - "@google-cloud/firestore": "^3.0.0", - "@google-cloud/storage": "^4.1.2", - "@types/node": "^8.10.59", + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-9.1.1.tgz", + "integrity": "sha512-HkzY9yN/kOe1EQgjheURAQ4pFBerI54TBL0+nj1fwzKnAnGCpcI73Bbwx99Pk3u2x4rj6bDcsZfz9bA8y7DWtQ==", + "requires": { + "@firebase/database": "^0.6.10", + "@firebase/database-types": "^0.5.2", + "@google-cloud/firestore": "^4.0.0", + "@google-cloud/storage": "^5.0.0", + "@types/node": "^10.10.0", "dicer": "^0.3.0", - "jsonwebtoken": "8.1.0", - "node-forge": "0.7.4" - }, - "dependencies": { - "@types/node": { - "version": "8.10.62", - "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.62.tgz", - "integrity": "sha512-76fupxOYVxk36kb7O/6KtrAPZ9jnSK3+qisAX4tQMEuGNdlvl7ycwatlHqjoE6jHfVtXFM3pCrCixZOidc5cuw==" - } + "jsonwebtoken": "^8.5.1", + "node-forge": "^0.9.1" } }, "firebase-functions": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-3.7.0.tgz", - "integrity": "sha512-+ROj2Gs2/KyM+T8jYo7AKaHynFsN49sXbgZMll3zuGa9/8oiDsXp9e1Iy2JMkFmSZg67jeYw5Ue2OSpz0XiqFQ==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-3.11.0.tgz", + "integrity": "sha512-i1uMhZ/M6i5SCI3ulKo7EWX0/LD+I5o6N+sk0HbOWfzyWfOl0iJTvQkR3BVDcjrlhPVC4xG1bDTLxd+DTkLqaw==", "requires": { "@types/express": "4.17.3", "cors": "^2.8.5", @@ -1431,12 +1523,6 @@ "lodash": "^4.17.14" } }, - "foreach": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", - "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", - "optional": true - }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -1475,11 +1561,6 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, "functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", @@ -1487,9 +1568,9 @@ "optional": true }, "gaxios": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-3.0.3.tgz", - "integrity": "sha512-PkzQludeIFhd535/yucALT/Wxyj/y2zLyrMwPcJmnLHDugmV49NvAi/vb+VUq/eWztATZCNcb8ue+ywPG+oLuw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-3.1.0.tgz", + "integrity": "sha512-DDTn3KXVJJigtz+g0J3vhcfbDbKtAroSTxauWsdnP57sM5KZ3d2c/3D9RKFJ86s43hfw6WULg6TXYw/AYiBlpA==", "optional": true, "requires": { "abort-controller": "^3.0.0", @@ -1500,48 +1581,40 @@ } }, "gcp-metadata": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.1.0.tgz", - "integrity": "sha512-r57SV28+olVsflPlKyVig3Muo/VDlcsObMtvDGOEtEJXj+DDE8bEl0coIkXh//hbkSDTvo+f5lbihZOndYXQQQ==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.1.4.tgz", + "integrity": "sha512-5J/GIH0yWt/56R3dNaNWPGQ/zXsZOddYECfJaqxFWgrZ9HC2Kvc5vl9upOgUUHKzURjAVf2N+f6tEJiojqXUuA==", "optional": true, "requires": { "gaxios": "^3.0.0", - "json-bigint": "^0.3.0" + "json-bigint": "^1.0.0" } }, "gcs-resumable-upload": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/gcs-resumable-upload/-/gcs-resumable-upload-2.3.3.tgz", - "integrity": "sha512-sf896I5CC/1AxeaGfSFg3vKMjUq/r+A3bscmVzZm10CElyRanN0XwPu/MxeIO4LSP+9uF6yKzXvNsaTsMXUG6Q==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/gcs-resumable-upload/-/gcs-resumable-upload-3.1.1.tgz", + "integrity": "sha512-RS1osvAicj9+MjCc6jAcVL1Pt3tg7NK2C2gXM5nqD1Gs0klF2kj5nnAFSBy97JrtslMIQzpb7iSuxaG8rFWd2A==", "optional": true, "requires": { "abort-controller": "^3.0.0", "configstore": "^5.0.0", - "gaxios": "^2.0.0", - "google-auth-library": "^5.0.0", + "extend": "^3.0.2", + "gaxios": "^3.0.0", + "google-auth-library": "^6.0.0", "pumpify": "^2.0.0", "stream-events": "^1.0.4" - }, - "dependencies": { - "gaxios": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-2.3.4.tgz", - "integrity": "sha512-US8UMj8C5pRnao3Zykc4AAVr+cffoNKRTg9Rsf2GiuZCW69vgJj38VK2PzlPuQU73FZ/nTk9/Av6/JGcE1N9vA==", - "optional": true, - "requires": { - "abort-controller": "^3.0.0", - "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", - "is-stream": "^2.0.0", - "node-fetch": "^2.3.0" - } - } } }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "optional": true + }, "get-stream": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", - "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", "dev": true, "requires": { "pump": "^3.0.0" @@ -1569,110 +1642,57 @@ } }, "google-auth-library": { - "version": "5.10.1", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-5.10.1.tgz", - "integrity": "sha512-rOlaok5vlpV9rSiUu5EpR0vVpc+PhN62oF4RyX/6++DG1VsaulAFEMlDYBLjJDDPI6OcNOCGAKy9UVB/3NIDXg==", + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.0.6.tgz", + "integrity": "sha512-fWYdRdg55HSJoRq9k568jJA1lrhg9i2xgfhVIMJbskUmbDpJGHsbv9l41DGhCDXM21F9Kn4kUwdysgxSYBYJUw==", "optional": true, "requires": { "arrify": "^2.0.0", "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "fast-text-encoding": "^1.0.0", - "gaxios": "^2.1.0", - "gcp-metadata": "^3.4.0", - "gtoken": "^4.1.0", + "gaxios": "^3.0.0", + "gcp-metadata": "^4.1.0", + "gtoken": "^5.0.0", "jws": "^4.0.0", - "lru-cache": "^5.0.0" - }, - "dependencies": { - "gaxios": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-2.3.4.tgz", - "integrity": "sha512-US8UMj8C5pRnao3Zykc4AAVr+cffoNKRTg9Rsf2GiuZCW69vgJj38VK2PzlPuQU73FZ/nTk9/Av6/JGcE1N9vA==", - "optional": true, - "requires": { - "abort-controller": "^3.0.0", - "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", - "is-stream": "^2.0.0", - "node-fetch": "^2.3.0" - } - }, - "gcp-metadata": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-3.5.0.tgz", - "integrity": "sha512-ZQf+DLZ5aKcRpLzYUyBS3yo3N0JSa82lNDO8rj3nMSlovLcz2riKFBsYgDzeXcv75oo5eqB2lx+B14UvPoCRnA==", - "optional": true, - "requires": { - "gaxios": "^2.1.0", - "json-bigint": "^0.3.0" - } - }, - "google-p12-pem": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-2.0.4.tgz", - "integrity": "sha512-S4blHBQWZRnEW44OcR7TL9WR+QCqByRvhNDZ/uuQfpxywfupikf/miba8js1jZi6ZOGv5slgSuoshCWh6EMDzg==", - "optional": true, - "requires": { - "node-forge": "^0.9.0" - } - }, - "gtoken": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-4.1.4.tgz", - "integrity": "sha512-VxirzD0SWoFUo5p8RDP8Jt2AGyOmyYcT/pOUgDKJCK+iSw0TMqwrVfY37RXTNmoKwrzmDHSk0GMT9FsgVmnVSA==", - "optional": true, - "requires": { - "gaxios": "^2.1.0", - "google-p12-pem": "^2.0.0", - "jws": "^4.0.0", - "mime": "^2.2.0" - } - }, - "node-forge": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.1.tgz", - "integrity": "sha512-G6RlQt5Sb4GMBzXvhfkeFmbqR6MzhtnT7VTHuLadjkii3rdYHNdw0m8zA4BTxVIh68FicCQ2NSUANpsqkr9jvQ==", - "optional": true - } + "lru-cache": "^6.0.0" } }, "google-gax": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-1.15.3.tgz", - "integrity": "sha512-3JKJCRumNm3x2EksUTw4P1Rad43FTpqrtW9jzpf3xSMYXx+ogaqTM1vGo7VixHB4xkAyATXVIa3OcNSh8H9zsQ==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-2.8.0.tgz", + "integrity": "sha512-MPaADY/FHittX5xfOUU2EVqIoE850e+OZ1ys8aO2GnUMaP4U0Bde2wop6kw5sp4fIOjKNlan4GATKAURsYbxSw==", "optional": true, "requires": { - "@grpc/grpc-js": "~1.0.3", + "@grpc/grpc-js": "~1.1.1", "@grpc/proto-loader": "^0.5.1", - "@types/fs-extra": "^8.0.1", "@types/long": "^4.0.0", "abort-controller": "^3.0.0", "duplexify": "^3.6.0", - "google-auth-library": "^5.0.0", + "google-auth-library": "^6.0.0", "is-stream-ended": "^0.1.4", "lodash.at": "^4.6.0", "lodash.has": "^4.5.2", "node-fetch": "^2.6.0", - "protobufjs": "^6.8.9", + "protobufjs": "^6.9.0", "retry-request": "^4.0.0", "semver": "^6.0.0", "walkdir": "^0.4.0" } }, "google-p12-pem": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.0.1.tgz", - "integrity": "sha512-VlQgtozgNVVVcYTXS36eQz4PXPt9gIPqLOhHN0QiV6W6h4qSCNVKPtKC5INtJsaHHF2r7+nOIa26MJeJMTaZEQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.0.3.tgz", + "integrity": "sha512-wS0ek4ZtFx/ACKYF3JhyGe5kzH7pgiQ7J5otlumqR9psmWMYc+U9cErKlCYVYHoUaidXHdZ2xbo34kB+S+24hA==", "optional": true, "requires": { - "node-forge": "^0.9.0" + "node-forge": "^0.10.0" }, "dependencies": { "node-forge": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.1.tgz", - "integrity": "sha512-G6RlQt5Sb4GMBzXvhfkeFmbqR6MzhtnT7VTHuLadjkii3rdYHNdw0m8zA4BTxVIh68FicCQ2NSUANpsqkr9jvQ==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", + "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==", "optional": true } } @@ -1684,9 +1704,9 @@ "optional": true }, "gtoken": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.0.1.tgz", - "integrity": "sha512-33w4FNDkUcyIOq/TqyC+drnKdI4PdXmWp9lZzssyEQKuvu9ZFN3KttaSnDKo52U3E51oujVGop93mKxmqO8HHg==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.0.3.tgz", + "integrity": "sha512-Nyd1wZCMRc2dj/mAD0LlfQLcAO06uKdpKJXvK85SGrF5+5+Bpfil9u/2aw35ltvEHjvl0h5FMKN5knEU+9JrOg==", "optional": true, "requires": { "gaxios": "^3.0.0", @@ -1709,71 +1729,24 @@ "har-schema": "^2.0.0" } }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "requires": { - "function-bind": "^1.1.1" - } - }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, - "has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" - }, "hash-stream-validation": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/hash-stream-validation/-/hash-stream-validation-0.2.3.tgz", - "integrity": "sha512-OEohGLoUOh+bwsIpHpdvhIXFyRGjeLqJbT8Yc5QTZPbRM7LKywagTQxnX/6mghLDOrD9YGz88hy5mLN2eKflYQ==", - "optional": true, + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/hash-stream-validation/-/hash-stream-validation-0.2.4.tgz", + "integrity": "sha512-Gjzu0Xn7IagXVkSu9cSFuK1fqzwtLwFhNhVL8IFJijRNMgUttFbBSIAzKuSIrsFMO1+g1RlsoN49zPIbwPDMGQ==", + "optional": true + }, + "html-encoding-sniffer": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", + "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", "requires": { - "through2": "^2.0.0" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "optional": true - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "optional": true - }, - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "optional": true, - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - } + "whatwg-encoding": "^1.0.5" } }, "http-errors": { @@ -1831,6 +1804,12 @@ "debug": "4" } }, + "human-signals": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", + "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", + "dev": true + }, "husky": { "version": "4.2.5", "resolved": "https://registry.npmjs.org/husky/-/husky-4.2.5.tgz", @@ -1955,55 +1934,26 @@ "resolved": "https://registry.npmjs.org/install/-/install-0.13.0.tgz", "integrity": "sha512-zDml/jzr2PKU9I8J/xyZBQn8rPCAY//UOYNmR01XwNwyfhEWObo2SWfSl1+0tm1u6PhxLwDnfsT/6jB7OUxqFA==" }, + "ip-regex": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", + "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=" + }, "ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" }, - "is-arguments": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", - "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==", - "optional": true - }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", "dev": true }, - "is-bigint": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.0.tgz", - "integrity": "sha512-t5mGUXC/xRheCK431ylNiSkGGpBp8bHENBcENTkDT6ppwPzEVxNGZRvgvmOEfbWkFhA7D2GEuE2mmQTr78sl2g==", - "optional": true - }, - "is-boolean-object": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.0.1.tgz", - "integrity": "sha512-TqZuVwa/sppcrhUCAYkGBk7w0yxfQQnxq28fjkO53tnK9FQXmdwz2JS5+GjsWQ6RByES1K40nI+yDic5c9/aAQ==", - "optional": true - }, - "is-callable": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", - "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==" - }, - "is-date-object": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", - "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==" - }, - "is-map": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.1.tgz", - "integrity": "sha512-T/S49scO8plUiAOA2DBTBG3JHpn1yiw0kRp6dgiZ0v2/6twi5eiB0rHtHFH9ZIrvlWc6+4O+m4zg5+Z833aXgw==", - "optional": true - }, - "is-number-object": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz", - "integrity": "sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==", + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "optional": true }, "is-obj": { @@ -2012,19 +1962,10 @@ "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", "optional": true }, - "is-regex": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", - "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", - "requires": { - "has": "^1.0.3" - } - }, - "is-set": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.1.tgz", - "integrity": "sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA==", - "optional": true + "is-potential-custom-element-name": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz", + "integrity": "sha1-DFLlS8yjkbssSUsh6GJtczbG45c=" }, "is-stream": { "version": "2.0.0", @@ -2037,53 +1978,15 @@ "integrity": "sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==", "optional": true }, - "is-string": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", - "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", - "optional": true - }, - "is-symbol": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", - "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", - "requires": { - "has-symbols": "^1.0.1" - } - }, - "is-typed-array": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.3.tgz", - "integrity": "sha512-BSYUBOK/HJibQ30wWkWold5txYwMUXQct9YHAQJr8fSwvZoiglcqB0pd7vEN23+Tsi9IUEjztdOSzl4qLVYGTQ==", - "optional": true, - "requires": { - "available-typed-arrays": "^1.0.0", - "es-abstract": "^1.17.4", - "foreach": "^2.0.5", - "has-symbols": "^1.0.1" - } - }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, - "is-weakmap": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", - "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", - "optional": true - }, - "is-weakset": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.1.tgz", - "integrity": "sha512-pi4vhbhVHGLxohUw7PhGsueT4vRGFoXhP7+RGN0jKIv9+8PWYCQTqtADngrxOm2g46hoH0+g8uZZBzMrvVGDmw==", - "optional": true - }, "isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "optional": true }, "isexe": { @@ -2137,13 +2040,63 @@ "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, + "jsdom": { + "version": "16.4.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.4.0.tgz", + "integrity": "sha512-lYMm3wYdgPhrl7pDcRmvzPhhrGVBeVhPIqeHjzeiHN3DFmD1RBpbExbi8vU7BJdH8VAZYovR8DMt0PNNDM7k8w==", + "requires": { + "abab": "^2.0.3", + "acorn": "^7.1.1", + "acorn-globals": "^6.0.0", + "cssom": "^0.4.4", + "cssstyle": "^2.2.0", + "data-urls": "^2.0.0", + "decimal.js": "^10.2.0", + "domexception": "^2.0.1", + "escodegen": "^1.14.1", + "html-encoding-sniffer": "^2.0.1", + "is-potential-custom-element-name": "^1.0.0", + "nwsapi": "^2.2.0", + "parse5": "5.1.1", + "request": "^2.88.2", + "request-promise-native": "^1.0.8", + "saxes": "^5.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^3.0.1", + "w3c-hr-time": "^1.0.2", + "w3c-xmlserializer": "^2.0.0", + "webidl-conversions": "^6.1.0", + "whatwg-encoding": "^1.0.5", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.0.0", + "ws": "^7.2.3", + "xml-name-validator": "^3.0.0" + }, + "dependencies": { + "tough-cookie": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz", + "integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==", + "requires": { + "ip-regex": "^2.1.0", + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "ws": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz", + "integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==" + } + } + }, "json-bigint": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-0.3.0.tgz", - "integrity": "sha1-DM2RLEuCcNBfBW+9E4FLU9OCWx4=", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", "optional": true, "requires": { - "bignumber.js": "^7.0.0" + "bignumber.js": "^9.0.0" } }, "json-parse-better-errors": { @@ -2168,11 +2121,11 @@ "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, "jsonwebtoken": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.1.0.tgz", - "integrity": "sha1-xjl80uX9WD1lwAeoPce7eOaYK4M=", + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", "requires": { - "jws": "^3.1.4", + "jws": "^3.2.2", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", @@ -2180,8 +2133,8 @@ "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", - "ms": "^2.0.0", - "xtend": "^4.0.1" + "ms": "^2.1.1", + "semver": "^5.6.0" }, "dependencies": { "jwa": { @@ -2202,6 +2155,11 @@ "jwa": "^1.4.1", "safe-buffer": "^5.0.1" } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" } } }, @@ -2237,6 +2195,15 @@ "safe-buffer": "^5.0.1" } }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, "lines-and-columns": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", @@ -2247,7 +2214,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, "requires": { "p-locate": "^4.1.0" } @@ -2310,6 +2276,11 @@ "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" }, + "lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=" + }, "long": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", @@ -2317,12 +2288,20 @@ "optional": true }, "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "optional": true, "requires": { - "yallist": "^3.0.2" + "yallist": "^4.0.0" + }, + "dependencies": { + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + } } }, "mailchimp-api-v3": { @@ -2372,9 +2351,9 @@ "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==" }, "mime-db": { - "version": "1.43.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", - "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==", + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", "optional": true }, "mime-types": { @@ -2436,9 +2415,9 @@ } }, "mri": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/mri/-/mri-1.1.5.tgz", - "integrity": "sha512-d2RKzMD4JNyHMbnbWnznPaa8vbdlq/4pNZ3IgdaGrVbBhebBsGUUE/6qorTMYNS6TwuH3ilfOlD2bf4Igh8CKg==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.1.6.tgz", + "integrity": "sha512-oi1b3MfbyGa7FJMP9GmLTttni5JoICpYBRlq+x5V16fZbLsnL9N3wFqqIm/nIG43FjUFkFh9Epzp/kzUGUnJxQ==", "dev": true }, "ms": { @@ -2470,14 +2449,14 @@ "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" }, "node-forge": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.4.tgz", - "integrity": "sha512-8Df0906+tq/omxuCZD6PqhPaQDYuyJ1d+VITgxoIA8zvQd1ru+nMJcDChHH324MWitIgbVkAkQoGEEVJNpn/PA==" + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.2.tgz", + "integrity": "sha512-naKSScof4Wn+aoHU6HBsifh92Zeicm1GDQKd1vp3Y/kOi8ub0DozCa9KpvYNCXslFHYRmLNiqRopGdTGwNLpNw==" }, "nodemailer": { - "version": "6.4.8", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.4.8.tgz", - "integrity": "sha512-UbJD0+g5e2H20bWv7Rpj3B+N3TMMJ0MLoLwaGVJ0k3Vo8upq0UltwHJ5BJfrpST1vFa91JQ8cf7cICK5DSIo1Q==" + "version": "6.4.11", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.4.11.tgz", + "integrity": "sha512-BVZBDi+aJV4O38rxsUh164Dk1NCqgh6Cm0rQSb9SK/DHGll/DrCMnycVDD7msJgZCnmVa8ASo8EZzR7jsgTukQ==" }, "nopt": { "version": "5.0.0", @@ -6004,14 +5983,19 @@ } }, "npm-run-path": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-3.1.0.tgz", - "integrity": "sha512-Dbl4A/VfiVGLgQv29URL9xshU8XDY1GeLy+fsaZ1AA8JDSfjvr5P5+pzRbWqRSBxk6/DW7MIh8lTM/PaGnP2kg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, "requires": { "path-key": "^3.0.0" } }, + "nwsapi": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", + "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==" + }, "oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", @@ -6022,37 +6006,6 @@ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, - "object-inspect": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", - "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==" - }, - "object-is": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.2.tgz", - "integrity": "sha512-5lHCz+0uufF6wZ7CRFWJN3hp8Jqblpgve06U5CMQ3f//6iDjPr2PEo9MWCjEssDsa+UZEL4PkFpr+BMop6aKzQ==", - "optional": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" - }, - "object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", - "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" - } - }, "on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", @@ -6070,9 +6023,9 @@ } }, "onetime": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", - "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "requires": { "mimic-fn": "^2.1.0" } @@ -6083,11 +6036,18 @@ "integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==", "dev": true }, - "p-finally": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-2.0.1.tgz", - "integrity": "sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw==", - "dev": true + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } }, "p-limit": { "version": "2.3.0", @@ -6101,7 +6061,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, "requires": { "p-limit": "^2.2.0" } @@ -6132,6 +6091,11 @@ "lines-and-columns": "^1.1.6" } }, + "parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==" + }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -6140,8 +6104,7 @@ "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" }, "path-is-absolute": { "version": "1.0.1", @@ -6199,24 +6162,81 @@ "semver-compare": "^1.0.0" } }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" + }, "prettier": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.1.0.tgz", - "integrity": "sha512-lz28cCbA1cDFHVuY8vvj6QuqOwIpyIfPUYkSl8AZ/vxH8qBXMMjE2knfLHCrZCmUsK/H1bg1P0tOo0dJkTJHvw==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.1.1.tgz", + "integrity": "sha512-9bY+5ZWCfqj3ghYBLxApy2zf6m+NJo5GzmLTpr9FsApsfjriNnS2dahWReHMi7qNPhhHl9SYHJs2cHZLgexNIw==", "dev": true }, "pretty-quick": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pretty-quick/-/pretty-quick-2.0.1.tgz", - "integrity": "sha512-y7bJt77XadjUr+P1uKqZxFWLddvj3SKY6EU4BuQtMxmmEFSMpbN132pUWdSG1g1mtUfO0noBvn7wBf0BVeomHg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pretty-quick/-/pretty-quick-3.0.0.tgz", + "integrity": "sha512-oIXlGQUcUxt3XpoNfQECEWvH1Q9PtKfelF2pdp6UvC1CSQ5QcB7gUYKu0kuJGlm3LMBZzJaO/vbRkxA61pWlcg==", "dev": true, "requires": { - "chalk": "^2.4.2", - "execa": "^2.1.0", + "chalk": "^3.0.0", + "execa": "^4.0.0", "find-up": "^4.1.0", "ignore": "^5.1.4", - "mri": "^1.1.4", + "mri": "^1.1.5", "multimatch": "^4.0.0" + }, + "dependencies": { + "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.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, "process-nextick-args": { @@ -6235,9 +6255,9 @@ "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=" }, "protobufjs": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.9.0.tgz", - "integrity": "sha512-LlGVfEWDXoI/STstRDdZZKb/qusoAWUnmLg9R8OLSO473mBLWHowx8clbX5/+mKDEI+v7GzjoK9tRPZMMcoTrg==", + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.1.tgz", + "integrity": "sha512-pb8kTchL+1Ceg4lFd5XUpK8PdWacbvV5SK2ULH2ebrYtl4GjJmS24m6CKME67jzV53tbJxHlnNOSqQHbTsR9JQ==", "optional": true, "requires": { "@protobufjs/aspromise": "^1.1.2", @@ -6256,9 +6276,9 @@ }, "dependencies": { "@types/node": { - "version": "13.13.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.9.tgz", - "integrity": "sha512-EPZBIGed5gNnfWCiwEIwTE2Jdg4813odnG8iNPMQGrqVxrI+wL68SPtPeCX+ZxGBaA6pKAVc6jaKgP/Q0QzfdQ==", + "version": "13.13.16", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.16.tgz", + "integrity": "sha512-dJ9vXxJ8MEwzNn4GkoAGauejhXoKuJyYKegsA6Af25ZpEDXomeVXt5HUWUNVHk5UN7+U0f6ghC6otwt+7PdSDg==", "optional": true } } @@ -6318,6 +6338,17 @@ "readable-stream": "^3.1.1", "stream-shift": "^1.0.0" } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "optional": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } } } }, @@ -6402,24 +6433,26 @@ } }, "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "optional": true, "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "regexp.prototype.flags": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz", - "integrity": "sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==", - "optional": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "optional": true + } } }, "request": { @@ -6461,6 +6494,43 @@ } } }, + "request-promise-core": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", + "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", + "requires": { + "lodash": "^4.17.19" + }, + "dependencies": { + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" + } + } + }, + "request-promise-native": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.9.tgz", + "integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==", + "requires": { + "request-promise-core": "1.1.4", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "optional": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "optional": true + }, "resolve": { "version": "1.17.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", @@ -6477,13 +6547,12 @@ "dev": true }, "retry-request": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.1.1.tgz", - "integrity": "sha512-BINDzVtLI2BDukjWmjAIRZ0oglnCAkpP2vQjM3jdLhmT62h0xnQgciPwBRDAvHqpkPT2Wo1XuUyLyn6nbGrZQQ==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.1.3.tgz", + "integrity": "sha512-QnRZUpuPNgX0+D1xVxul6DbJ9slvo4Rm6iV/dn63e048MvGbUZiKySVt6Tenp04JqmchxjiLltGerOJys7kJYQ==", "optional": true, "requires": { - "debug": "^4.1.1", - "through2": "^3.0.1" + "debug": "^4.1.1" } }, "rimraf": { @@ -6504,6 +6573,14 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "requires": { + "xmlchars": "^2.2.0" + } + }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -6580,6 +6657,12 @@ "send": "0.17.1" } }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "optional": true + }, "setprototypeof": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", @@ -6600,16 +6683,6 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, - "side-channel": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.2.tgz", - "integrity": "sha512-7rL9YlPHg7Ancea1S96Pa8/QWb4BtXL/TZvS6B8XFetGBeuhAsfmUspK6DokBeZ64+Kj9TCNRD/30pVz1BvQNA==", - "optional": true, - "requires": { - "es-abstract": "^1.17.0-next.1", - "object-inspect": "^1.7.0" - } - }, "sigmund": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", @@ -6621,9 +6694,9 @@ "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" }, "simple-git": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-2.19.0.tgz", - "integrity": "sha512-OZKxX9zHeH8JbCo8DMlERE8RsWox7Q9Jmh+lJKw/Zla8HQkiVP5I4LF5ZRfkud9XU27ZNgpccHezg1lbHw6VzQ==", + "version": "2.20.1", + "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-2.20.1.tgz", + "integrity": "sha512-aa9s2ZLjXlHCVGbDXQLInMLvLkxKEclqMU9X5HMXi3tLWLxbWObz1UgtyZha6ocHarQtFp0OjQW9KHVR1g6wbA==", "requires": { "@kwsites/file-exists": "^1.1.1", "@kwsites/promise-deferred": "^1.1.1", @@ -6642,6 +6715,12 @@ "integrity": "sha1-EMCI2LWOsHazIpu1oE4jLOEmQi0=", "optional": true }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "optional": true + }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -6669,6 +6748,11 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" }, + "stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" + }, "stream-events": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", @@ -6689,42 +6773,15 @@ "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" }, - "string.prototype.trimend": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", - "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, - "string.prototype.trimleft": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz", - "integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "string.prototype.trimstart": "^1.0.0" - } - }, - "string.prototype.trimright": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz", - "integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "string.prototype.trimend": "^1.0.0" - } - }, - "string.prototype.trimstart": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", - "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "optional": true, "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" } }, "string_decoder": { @@ -6742,6 +6799,15 @@ } } }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "optional": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, "strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", @@ -6763,6 +6829,11 @@ "has-flag": "^3.0.0" } }, + "symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" + }, "tar": { "version": "4.4.13", "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", @@ -6778,25 +6849,16 @@ } }, "teeny-request": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-6.0.3.tgz", - "integrity": "sha512-TZG/dfd2r6yeji19es1cUIwAlVD8y+/svB1kAC2Y0bjEyysrfbO8EZvJBRwIE6WkwmUoB7uvWLwTIhJbMXZ1Dw==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-7.0.0.tgz", + "integrity": "sha512-kWD3sdGmIix6w7c8ZdVKxWq+3YwVPGWz+Mq0wRZXayEKY/YHb63b8uphfBzcFDmyq8frD9+UTc3wLyOhltRbtg==", "optional": true, "requires": { "http-proxy-agent": "^4.0.0", "https-proxy-agent": "^5.0.0", "node-fetch": "^2.2.0", "stream-events": "^1.0.5", - "uuid": "^7.0.0" - } - }, - "through2": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz", - "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==", - "optional": true, - "requires": { - "readable-stream": "2 || 3" + "uuid": "^8.0.0" } }, "toidentifier": { @@ -6813,15 +6875,23 @@ "punycode": "^2.1.1" } }, + "tr46": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.0.2.tgz", + "integrity": "sha512-3n1qG+/5kg+jrbTzwAykB5yRYtQCTqOGKq5U5PE3b0a1/mzo6snDhjGS0zJVJunO0NrT3Dg1MLy5TjWP/UJppg==", + "requires": { + "punycode": "^2.1.1" + } + }, "tslib": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz", - "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==" + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", + "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==" }, "tslint": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-6.1.2.tgz", - "integrity": "sha512-UyNrLdK3E0fQG/xWNqAFAC5ugtFyPO4JJR1KyyfQAyzR8W0fTRrC91A8Wej4BntFzcvETdCSDa/4PnNYJQLYiA==", + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-6.1.3.tgz", + "integrity": "sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", @@ -6835,7 +6905,7 @@ "mkdirp": "^0.5.3", "resolve": "^1.3.2", "semver": "^5.3.0", - "tslib": "^1.10.0", + "tslib": "^1.13.0", "tsutils": "^2.29.0" }, "dependencies": { @@ -6884,6 +6954,14 @@ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "requires": { + "prelude-ls": "~1.1.2" + } + }, "type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -6908,9 +6986,9 @@ } }, "typescript": { - "version": "3.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.5.tgz", - "integrity": "sha512-hSAifV3k+i6lEoCJ2k6R2Z/rp/H3+8sdmcn5NrS3/3kE7+RyZXm9aqvxWqjEXHAd8b0pShatpcdMTvEdvAJltQ==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.2.tgz", + "integrity": "sha512-e4ERvRV2wb+rRZ/IQeb3jm2VxBsirQLpQhdxplZ2MEzGvDkkMmPglecnNDfSUBivMjP93vRbngYYDQqQ/78bcQ==", "dev": true }, "unique-string": { @@ -6946,9 +7024,9 @@ "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" }, "uuid": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz", - "integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.0.tgz", + "integrity": "sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ==", "optional": true }, "vary": { @@ -6966,12 +7044,33 @@ "extsprintf": "^1.2.0" } }, + "w3c-hr-time": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", + "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", + "requires": { + "browser-process-hrtime": "^1.0.0" + } + }, + "w3c-xmlserializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", + "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", + "requires": { + "xml-name-validator": "^3.0.0" + } + }, "walkdir": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.4.1.tgz", "integrity": "sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ==", "optional": true }, + "webidl-conversions": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", + "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==" + }, "websocket-driver": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", @@ -6983,9 +7082,32 @@ } }, "websocket-extensions": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz", - "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==" + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==" + }, + "whatwg-encoding": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", + "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "requires": { + "iconv-lite": "0.4.24" + } + }, + "whatwg-mimetype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==" + }, + "whatwg-url": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.2.1.tgz", + "integrity": "sha512-ZmVCr6nfBeaMxEHALLEGy0LszYjpJqf6PVNQUQ1qd9Et+q7Jpygd4rGGDXgHjD8e99yLFseD69msHDM4YwPZ4A==", + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^2.0.2", + "webidl-conversions": "^6.1.0" + } }, "which": { "version": "2.0.2", @@ -6996,30 +7118,11 @@ "isexe": "^2.0.0" } }, - "which-boxed-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.1.tgz", - "integrity": "sha512-7BT4TwISdDGBgaemWU0N0OU7FeAEJ9Oo2P1PHRm/FCWoEi2VLWC9b6xvxAA3C/NMpxg3HXVgi0sMmGbNUbNepQ==", - "optional": true, - "requires": { - "is-bigint": "^1.0.0", - "is-boolean-object": "^1.0.0", - "is-number-object": "^1.0.3", - "is-string": "^1.0.4", - "is-symbol": "^1.0.2" - } - }, - "which-collection": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", - "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", - "optional": true, - "requires": { - "is-map": "^2.0.1", - "is-set": "^2.0.1", - "is-weakmap": "^2.0.1", - "is-weakset": "^2.0.1" - } + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "optional": true }, "which-pm-runs": { "version": "1.0.0", @@ -7027,18 +7130,47 @@ "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=", "dev": true }, - "which-typed-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.2.tgz", - "integrity": "sha512-KT6okrd1tE6JdZAy3o2VhMoYPh3+J6EMZLyrxBQsZflI1QCZIxMrIYLkosd8Twf+YfknVIHmYQPgJt238p8dnQ==", + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==" + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "optional": true, "requires": { - "available-typed-arrays": "^1.0.2", - "es-abstract": "^1.17.5", - "foreach": "^2.0.5", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.1", - "is-typed-array": "^1.1.3" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "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==", + "optional": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "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==", + "optional": 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==", + "optional": true + } } }, "wrappy": { @@ -7072,10 +7204,21 @@ "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", "optional": true }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + "xml-name-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==" + }, + "xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "optional": true }, "yallist": { "version": "3.1.1", @@ -7088,6 +7231,35 @@ "integrity": "sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==", "dev": true }, + "yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "optional": true, + "requires": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + } + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "optional": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, "yauzl": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", diff --git a/cloud/functions/package.json b/cloud/functions/package.json index 1ec9b955f..f9709cb7b 100644 --- a/cloud/functions/package.json +++ b/cloud/functions/package.json @@ -12,30 +12,34 @@ }, "main": "lib/index.js", "dependencies": { - "firebase-admin": "^8.12.1", - "firebase-functions": "^3.7.0", + "cors": "^2.8.5", + "firebase-admin": "^9.1.1", + "firebase-functions": "^3.11.0", "install": "^0.13.0", "js-beautify": "^1.13.0", + "jsdom": "^16.4.0", "mailchimp-api-v3": "^1.14.0", "node-fetch": "^2.6.0", - "nodemailer": "^6.4.8", + "nodemailer": "^6.4.11", "npm": "^6.14.8", "puppeteer": "^2.1.1", "rimraf": "^3.0.2", - "simple-git": "^2.19.0" + "simple-git": "^2.20.1" }, "devDependencies": { + "@types/cors": "^2.8.7", "@types/js-beautify": "^1.11.0", + "@types/jsdom": "^16.2.4", "@types/node": "^10.17.28", "@types/node-fetch": "^2.5.7", "@types/nodemailer": "^6.4.0", "@types/puppeteer": "^2.0.1", "@types/rimraf": "^3.0.0", "husky": "^4.2.5", - "prettier": "^2.1.0", - "pretty-quick": "^2.0.1", - "tslint": "^6.1.2", - "typescript": "^3.9.5" + "prettier": "^2.1.1", + "pretty-quick": "^3.0.0", + "tslint": "^6.1.3", + "typescript": "^4.0.2" }, "private": true, "engines": { diff --git a/cloud/functions/src/index.ts b/cloud/functions/src/index.ts index 04f7be044..216ce332b 100644 --- a/cloud/functions/src/index.ts +++ b/cloud/functions/src/index.ts @@ -8,6 +8,9 @@ app.firestore().settings({timestampsInSnapshots: true}); import {applyWatchDeckCreate, applyWatchDeckDelete, applyWatchDeckUpdate} from './watch/watch-deck'; import {applyWatchUserCreate, applyWatchUserDelete, applyWatchUserUpdate} from './watch/watch-user'; +import {applyWatchTaskCreate} from './watch/watch-task'; + +import {publishTask} from './request/publish'; const runtimeOpts = { timeoutSeconds: 120, @@ -22,6 +25,10 @@ export const watchDeckCreate = functions.firestore.document('decks/{deckId}').on export const watchUserUpdate = functions.firestore.document('users/{userId}').onUpdate(applyWatchUserUpdate); +export const watchTaskCreate = functions.firestore.document('tasks/{taskId}').onCreate(applyWatchTaskCreate); + export const watchUserDelete = functions.auth.user().onDelete(applyWatchUserDelete); export const watchUserCreate = functions.auth.user().onCreate(applyWatchUserCreate); + +export const publish = functions.https.onRequest(publishTask); diff --git a/studio/src/app/models/api/api.deck.tsx b/cloud/functions/src/model/api/api.deck.ts similarity index 100% rename from studio/src/app/models/api/api.deck.tsx rename to cloud/functions/src/model/api/api.deck.ts diff --git a/studio/src/app/models/api/api.presentation.tsx b/cloud/functions/src/model/api/api.presentation.ts similarity index 100% rename from studio/src/app/models/api/api.presentation.tsx rename to cloud/functions/src/model/api/api.presentation.ts diff --git a/studio/src/app/models/api/api.slide.tsx b/cloud/functions/src/model/api/api.slide.ts similarity index 100% rename from studio/src/app/models/api/api.slide.tsx rename to cloud/functions/src/model/api/api.slide.ts diff --git a/cloud/functions/src/model/deck.ts b/cloud/functions/src/model/data/deck.ts similarity index 65% rename from cloud/functions/src/model/deck.ts rename to cloud/functions/src/model/data/deck.ts index 7768f7c48..77626364f 100644 --- a/cloud/functions/src/model/deck.ts +++ b/cloud/functions/src/model/data/deck.ts @@ -1,6 +1,29 @@ import {firestore} from 'firebase-admin'; + import {UserSocial} from './user'; +export interface DeckDeployData { + status: 'scheduled' | 'failure' | 'successful'; + updated_at: firestore.Timestamp; +} + +export interface DeckDeploy { + github?: DeckDeployData; + api?: DeckDeployData; +} + +export interface DeckGitHubRepo { + id: string; + url: string; + name: string; + nameWithOwner: string; +} + +export interface DeckGitHub { + repo?: DeckGitHubRepo; + publish: boolean; +} + export interface DeckMetaAuthor { name: string; photo_url?: string | firestore.FieldValue; @@ -20,13 +43,14 @@ export interface DeckMeta { published: boolean; published_at: firestore.Timestamp; - updated_at: firestore.Timestamp; + feed: boolean; - github?: boolean; + updated_at: firestore.Timestamp; } export interface DeckAttributes { style?: string; + transition?: 'slide' | 'fade' | 'none'; } export interface DeckClone { @@ -39,6 +63,8 @@ export interface DeckData { attributes?: DeckAttributes; background?: string; + header?: string; + footer?: string; owner_id: string; @@ -48,6 +74,10 @@ export interface DeckData { meta?: DeckMeta; + deploy?: DeckDeploy; + + github?: DeckGitHub; + clone?: DeckClone; created_at?: firestore.Timestamp; diff --git a/cloud/functions/src/model/data/slide.ts b/cloud/functions/src/model/data/slide.ts new file mode 100644 index 000000000..b5e642a05 --- /dev/null +++ b/cloud/functions/src/model/data/slide.ts @@ -0,0 +1,75 @@ +import {firestore} from 'firebase-admin'; + +export enum SlideTemplate { + TITLE = 'title', + CONTENT = 'content', + SPLIT = 'split', + GIF = 'gif', + AUTHOR = 'author', + YOUTUBE = 'youtube', + QRCODE = 'qrcode', + CHART = 'chart', + POLL = 'poll', + 'ASPECT-RATIO' = 'aspect-ratio', + PLAYGROUND = 'playground', +} + +export enum SlideChartType { + LINE = 'line', + PIE = 'pie', + BAR = 'bar', +} + +export enum SlideSplitType { + DEFAULT = 'default', + DEMO = 'demo', +} + +export type SlideAttributesYAxisDomain = 'max' | 'extent'; + +export interface SlideAttributes { + style?: string; + src?: string; + customBackground?: string; + imgSrc?: string; + imgAlt?: string; + + content?: string; + customQRCode?: boolean; + + type?: SlideChartType | SlideSplitType; + innerRadius?: number; + animation?: boolean; + datePattern?: string; + yAxisDomain?: SlideAttributesYAxisDomain; + smooth?: boolean; + area?: boolean; + ticks?: number; + grid?: boolean; + separator?: string; + + vertical?: boolean; + + imgMode?: string; + + customLoader?: boolean; + + theme?: string; +} + +export interface SlideData { + content?: string; + template: SlideTemplate; + attributes?: SlideAttributes; + + api_id?: string; + + created_at?: firestore.Timestamp; + updated_at?: firestore.Timestamp; +} + +export interface Slide { + id: string; + ref: firestore.DocumentReference; + data: SlideData; +} diff --git a/cloud/functions/src/model/data/task.ts b/cloud/functions/src/model/data/task.ts new file mode 100644 index 000000000..5ae7b10c2 --- /dev/null +++ b/cloud/functions/src/model/data/task.ts @@ -0,0 +1,19 @@ +import {firestore} from 'firebase-admin'; + +export interface TaskData { + deckId: string; + token: string | firestore.FieldValue; + + type: 'publish-deck' | 'push-github'; + + status: 'scheduled' | 'failure' | 'successful'; + + created_at: firestore.Timestamp; + updated_at: firestore.Timestamp; +} + +export interface Task { + id: string; + ref: firestore.DocumentReference; + data: TaskData; +} diff --git a/cloud/functions/src/model/token.ts b/cloud/functions/src/model/data/token.ts similarity index 100% rename from cloud/functions/src/model/token.ts rename to cloud/functions/src/model/data/token.ts diff --git a/cloud/functions/src/model/user.ts b/cloud/functions/src/model/data/user.ts similarity index 100% rename from cloud/functions/src/model/user.ts rename to cloud/functions/src/model/data/user.ts diff --git a/cloud/functions/src/model/deploy.ts b/cloud/functions/src/model/deploy.ts deleted file mode 100644 index 966860805..000000000 --- a/cloud/functions/src/model/deploy.ts +++ /dev/null @@ -1,27 +0,0 @@ -import {firestore} from 'firebase-admin'; - -export interface DeployGitHubRepo { - id: string; - url: string; - name: string; - nameWithOwner: string; -} - -export interface DeployGitHub { - repo: DeployGitHubRepo; -} - -export interface DeployData { - owner_id: string; - - github: DeployGitHub; - - created_at?: firestore.Timestamp; - updated_at?: firestore.Timestamp; -} - -export interface Deploy { - id: string; - ref: firestore.DocumentReference; - data: DeployData; -} diff --git a/cloud/functions/src/model/slide.ts b/cloud/functions/src/model/slide.ts deleted file mode 100644 index f391c89ed..000000000 --- a/cloud/functions/src/model/slide.ts +++ /dev/null @@ -1,8 +0,0 @@ -import {firestore} from 'firebase-admin'; - -export interface Slide { - id: string; - ref: firestore.DocumentReference; - // For simplicity reason cast to any as we don't want to redefine them as in studio - data: any; -} diff --git a/cloud/functions/src/request/publish.ts b/cloud/functions/src/request/publish.ts new file mode 100644 index 000000000..9d4a963e7 --- /dev/null +++ b/cloud/functions/src/request/publish.ts @@ -0,0 +1,32 @@ +import * as functions from 'firebase-functions'; + +import * as cors from 'cors'; + +import {verifyToken} from './utils/request-utils'; + +import {ScheduledPublishTask, schedulePublish} from './publish/schedule-publish-task'; + +export async function publishTask(request: functions.Request, response: functions.Response) { + const corsHandler = cors({origin: true}); + + corsHandler(request, response, async () => { + const validToken: boolean = await verifyToken(request); + + if (!validToken) { + response.status(400).json({ + error: 'Not Authorized.', + }); + return; + } + + try { + const scheduledTask: ScheduledPublishTask = await schedulePublish(request); + + response.json(scheduledTask); + } catch (err) { + response.status(500).json({ + error: err, + }); + } + }); +} diff --git a/cloud/functions/src/request/publish/schedule-publish-task.ts b/cloud/functions/src/request/publish/schedule-publish-task.ts new file mode 100644 index 000000000..e9465338b --- /dev/null +++ b/cloud/functions/src/request/publish/schedule-publish-task.ts @@ -0,0 +1,117 @@ +import * as admin from 'firebase-admin'; +import * as functions from 'firebase-functions'; + +import {scheduleTask} from '../../utils/data/task-utils'; +import {geToken} from '../utils/request-utils'; + +import {DeckData, DeckDeployData} from '../../model/data/deck'; + +export interface ScheduledPublishTask { + deckId: string; + status: 'scheduled'; + publish: boolean; + github: boolean; +} + +export function schedulePublish(request: functions.Request): Promise { + return new Promise(async (resolve, reject) => { + try { + const token: string | undefined = await geToken(request); + const deckId: string | undefined = request.body.deckId; + const ownerId: string | undefined = request.body.ownerId; + + if (!deckId) { + reject('No deck information provided.'); + return; + } + + if (!token) { + reject('No token provided.'); + return; + } + + if (!ownerId) { + reject('No owner ID provided.'); + return; + } + + const publish: boolean = request.body.publish !== undefined && request.body.publish; + const github: boolean = request.body.github !== undefined && request.body.github; + + if (!github && !publish) { + reject('Nothing to publish'); + return; + } + + // We tell the frontend to wait + await updateDeckDeploy(deckId, ownerId, publish, github); + + // We schedule internally / cloud the job so we keep secret the token + + if (publish) { + await scheduleTask({ + deckId, + token, + type: 'publish-deck', + }); + } + + if (github) { + await scheduleTask({ + deckId, + token, + type: 'push-github', + }); + } + + resolve({ + deckId, + status: 'scheduled', + publish: publish, + github: github, + }); + } catch (err) { + reject(err); + } + }); +} + +function updateDeckDeploy(deckId: string, ownerId: string, publish: boolean, github: boolean): Promise { + return new Promise(async (resolve, reject) => { + try { + if (!deckId || deckId === undefined || !deckId) { + resolve(); + return; + } + + const documentReference: admin.firestore.DocumentReference = admin.firestore().doc(`/decks/${deckId}/`); + + const deployData: DeckDeployData = { + status: 'scheduled', + updated_at: admin.firestore.Timestamp.now(), + }; + + const updateData: Partial = publish + ? { + deploy: { + api: { + ...deployData, + }, + }, + } + : { + deploy: { + github: { + ...deployData, + }, + }, + }; + + await documentReference.set(updateData, {merge: true}); + + resolve(); + } catch (err) { + reject(err); + } + }); +} diff --git a/cloud/functions/src/request/utils/api-utils.ts b/cloud/functions/src/request/utils/api-utils.ts new file mode 100644 index 000000000..78656f495 --- /dev/null +++ b/cloud/functions/src/request/utils/api-utils.ts @@ -0,0 +1,80 @@ +import * as functions from 'firebase-functions'; + +import fetch, {Response} from 'node-fetch'; + +import {Deck} from '../../model/data/deck'; +import {ApiDeck} from '../../model/api/api.deck'; +import {ApiPresentation} from '../../model/api/api.presentation'; + +export function publishDeckApi(deck: Deck, apiDeck: ApiDeck, token: string): Promise { + return new Promise(async (resolve, reject) => { + try { + const apiDeckPublish: ApiPresentation = await createOrUpdatePublish(deck, apiDeck, token); + + resolve(apiDeckPublish); + } catch (err) { + reject(err); + } + }); +} + +function createOrUpdatePublish(deck: Deck, apiDeck: ApiDeck, token: string): Promise { + if (deck.data.api_id) { + return put(deck.data.api_id, apiDeck, token); + } else { + return post(apiDeck, token); + } +} + +function post(deck: ApiDeck, token: string): Promise { + return query(deck, '/presentations', 'POST', token); +} + +function put(apiDeckId: string, deck: ApiDeck, token: string): Promise { + return query(deck, `/presentations/${apiDeckId}`, 'PUT', token); +} + +function query(deck: ApiDeck, context: string, method: string, token: string): Promise { + return new Promise(async (resolve, reject) => { + try { + const apiSkip: string = functions.config().deckdeckgo.api.skip; + + if (apiSkip === 'true') { + resolve(mockApiPresentationData()); + return; + } + + const apiUrl: string = functions.config().deckdeckgo.api.url; + + const rawResponse: Response = await fetch(apiUrl + context, { + method: method, + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify(deck), + }); + + if (!rawResponse || !rawResponse.ok) { + reject('Something went wrong while creating or updating the deck'); + return; + } + + const publishedPresentation: ApiPresentation = await rawResponse.json(); + + resolve(publishedPresentation); + } catch (err) { + reject(err); + } + }); +} + +function mockApiPresentationData(): ApiPresentation { + const presentationUrl: string = functions.config().deckdeckgo.presentation.url; + + return { + id: Math.random().toString(36).substring(2) + Date.now().toString(36), + url: `${presentationUrl}/daviddalbusco/introducing-deckdeckgo/`, + }; +} diff --git a/cloud/functions/src/request/utils/convert-deck-utils.ts b/cloud/functions/src/request/utils/convert-deck-utils.ts new file mode 100644 index 000000000..3addacf4c --- /dev/null +++ b/cloud/functions/src/request/utils/convert-deck-utils.ts @@ -0,0 +1,165 @@ +import * as functions from 'firebase-functions'; + +import {Deck} from '../../model/data/deck'; +import {ApiDeck} from '../../model/api/api.deck'; +import {ApiSlide} from '../../model/api/api.slide'; +import {Slide, SlideAttributes, SlideTemplate} from '../../model/data/slide'; + +import {findSlide} from '../../utils/data/slide-utils'; +import {getGoogleFontScript} from './google-fonts-utils'; + +export function convertDeck(deck: Deck): Promise { + return new Promise(async (resolve, reject) => { + try { + const apiSlides: ApiSlide[] = await convertSlides(deck); + + const apiDeck: ApiDeck = { + name: deck.data.name ? deck.data.name.trim() : deck.data.name, + description: + deck.data.meta && deck.data.meta.description !== undefined && deck.data.meta.description !== '' + ? (deck.data.meta.description as string) + : deck.data.name, + owner_id: deck.data.owner_id, + attributes: deck.data.attributes, + background: deck.data.background, + header: deck.data.header, + footer: deck.data.footer, + slides: apiSlides, + }; + + const googleFontScript: string | undefined = await getGoogleFontScript(deck); + if (googleFontScript !== undefined) { + apiDeck.head_extra = googleFontScript; + } + + resolve(apiDeck); + } catch (err) { + reject(err); + } + }); +} + +function convertSlides(deck: Deck): Promise { + return new Promise(async (resolve, reject) => { + if (!deck.data.slides || deck.data.slides.length <= 0) { + resolve([]); + return; + } + + try { + const promises: Promise[] = []; + + for (let i: number = 0; i < deck.data.slides.length; i++) { + const slideId: string = deck.data.slides[i]; + + promises.push(convertSlide(deck, slideId)); + } + + if (!promises || promises.length <= 0) { + resolve([]); + return; + } + + const slides: ApiSlide[] = await Promise.all(promises); + + resolve(slides); + } catch (err) { + reject(err); + } + }); +} + +function convertSlide(deck: Deck, slideId: string): Promise { + return new Promise(async (resolve, reject) => { + const slide: Slide = await findSlide(deck.id, slideId); + + if (!slide || !slide.data) { + reject('Missing slide for publishing'); + return; + } + + const attributes: SlideAttributes = await convertAttributesToString(slide.data.attributes); + + const apiSlide: ApiSlide = { + template: slide.data.template, + content: slide.data.content, + attributes: attributes, + }; + + const cleanApiSlide: ApiSlide = await convertSlideQRCode(apiSlide); + cleanApiSlide.content = await cleanNotes(apiSlide.content); + + resolve(cleanApiSlide); + }); +} + +function convertAttributesToString(attributes: SlideAttributes | undefined): Promise { + return new Promise((resolve) => { + if (!attributes) { + resolve(undefined); + return; + } + + // We loose the type but doing so we ensure that all attributes are converted to string in order to parse them to HTML in the API + const result: SlideAttributes = {}; + Object.keys(attributes).forEach((key: string) => { + // @ts-ignore + result[key] = `${attributes[key]}`; + }); + + if (!result) { + resolve(undefined); + return; + } + + resolve(result); + }); +} + +function cleanNotes(content: string | undefined): Promise { + return new Promise((resolve) => { + if (!content || content === undefined || content === '') { + resolve(content); + return; + } + + const result: string = content.replace(/
(.|[\s\S])*?<\/div>/gi, ''); + + resolve(result); + }); +} + +function convertSlideQRCode(apiSlide: ApiSlide): Promise { + return new Promise(async (resolve) => { + if (!apiSlide) { + resolve(apiSlide); + return; + } + + if (apiSlide.template !== SlideTemplate.QRCODE) { + resolve(apiSlide); + return; + } + + const presentationUrl: string = functions.config().deckdeckgo.presentation.url; + + // If no attributes at all, we create an attribute "content" of the QR code with it's upcoming published url + if (!apiSlide.attributes) { + apiSlide.attributes = { + content: `${presentationUrl}{{DECKDECKGO_BASE_HREF}}`, + }; + } + + // If not custom content, we replace the attribute "content" of the QR code with it's upcoming published url + if (!apiSlide.attributes.hasOwnProperty('customQRCode') || !apiSlide.attributes.customQRCode) { + apiSlide.attributes.content = `${presentationUrl}{{DECKDECKGO_BASE_HREF}}`; + } + + // In any case, we don't need customQRCode attribute in our presentations, this is an attribute used by the editor + if (apiSlide.attributes.hasOwnProperty('customQRCode')) { + delete apiSlide.attributes['customQRCode']; + } + + resolve(apiSlide); + }); +} diff --git a/cloud/functions/src/request/utils/google-fonts-utils.ts b/cloud/functions/src/request/utils/google-fonts-utils.ts new file mode 100644 index 000000000..16e724631 --- /dev/null +++ b/cloud/functions/src/request/utils/google-fonts-utils.ts @@ -0,0 +1,143 @@ +import {Deck} from '../../model/data/deck'; + +import {JSDOM} from 'jsdom'; + +// TODO: Extract to utilities because it is also use in studio + +interface GoogleFont { + id: string; + name: string; + family: string; +} + +// TODO fetch(`https://deckdeckgo.com/assests/assets.json`) +const fonts = [ + { + id: 'google-fonts-lora', + name: 'Lora', + family: "'Lora', serif", + }, + { + id: 'google-fonts-roboto', + name: 'Roboto', + family: "'Roboto', sans-serif", + }, + { + id: 'google-fonts-open-sans', + name: 'Open Sans', + family: "'Open Sans', sans-serif", + }, + { + id: 'google-fonts-montserrat', + name: 'Montserrat', + family: "'Montserrat', sans-serif", + }, + { + id: 'google-fonts-cabin', + name: 'Cabin', + family: "'Cabin', sans-serif", + }, + { + id: 'google-fonts-lato', + name: 'Lato', + family: "'Lato', sans-serif", + }, + { + id: 'google-fonts-muli', + name: 'Muli', + family: "'Muli', sans-serif", + }, + { + id: 'google-fonts-source-sans-pro', + name: 'Source Sans Pro', + family: "'Source Sans Pro', sans-serif", + }, + { + id: 'google-fonts-libre-baskerville', + name: 'Libre Baskerville', + family: "'Libre Baskerville', serif", + }, + { + id: 'google-fonts-oswald', + name: 'Oswald', + family: "'Oswald', sans-serif", + }, + { + id: 'google-fonts-jura', + name: 'Jura', + family: "'Jura', sans-serif", + }, + { + id: 'google-fonts-fjord-one', + name: 'Fjord One', + family: "'Fjord One', serif", + }, + { + id: 'google-fonts-josefin-slab', + name: 'Josefin Slab', + family: "'Josefin Slab', serif", + }, +]; + +const googleFontsUrl: string = 'https://fonts.googleapis.com/css?display=swap&family='; + +export function getGoogleFontScript(deck: Deck): Promise { + return new Promise(async (resolve) => { + if (!deck || !deck.data || !deck.data.attributes) { + resolve(undefined); + return; + } + + if (!deck.data.attributes.style || deck.data.attributes.style === undefined || deck.data.attributes.style === '') { + resolve(undefined); + return; + } + + const dom = new JSDOM(`
`); + const div = dom.window.document.querySelector('div'); + + if (!div) { + return; + } + + div.setAttribute('style', deck.data.attributes.style); + + const fontFamily: string | undefined = div.style.getPropertyValue('font-family'); + + if (!fontFamily || fontFamily === undefined || fontFamily === '') { + resolve(undefined); + return; + } + + const font: GoogleFont | undefined = await extractGoogleFont(fontFamily); + + if (!font || font === undefined) { + resolve(undefined); + return; + } + + const url: string = getGoogleFontUrl(googleFontsUrl, font); + + const link: string = ``; + + resolve(link); + }); +} + +async function extractGoogleFont(fontFamilyStyle: string): Promise { + if (!fontFamilyStyle || fontFamilyStyle === undefined) { + return undefined; + } + + const fontFamily: string = fontFamilyStyle.replace(/\'/g, '').replace(/"/g, ''); + + const font: GoogleFont | undefined = fonts.find((filteredFont: GoogleFont) => { + return fontFamily === filteredFont.family.replace(/\'/g, ''); + }); + + return font; +} + +function getGoogleFontUrl(fontsUrl: string, font: GoogleFont): string { + return fontsUrl + font.name.replace(' ', '+'); +} diff --git a/cloud/functions/src/request/utils/request-utils.ts b/cloud/functions/src/request/utils/request-utils.ts new file mode 100644 index 000000000..7c5ee9399 --- /dev/null +++ b/cloud/functions/src/request/utils/request-utils.ts @@ -0,0 +1,28 @@ +import * as admin from 'firebase-admin'; +import * as functions from 'firebase-functions'; + +export async function verifyToken(request: functions.Request, acceptAnonymous: boolean = false): Promise { + try { + const token: string | undefined = await geToken(request); + + if (!token) { + return false; + } + + const payload: admin.auth.DecodedIdToken = await admin.auth().verifyIdToken(token); + + return payload !== null && (acceptAnonymous || payload.firebase.sign_in_provider !== 'anonymous'); + } catch (err) { + return false; + } +} + +export async function geToken(request: functions.Request): Promise { + if (!request.headers.authorization) { + return undefined; + } + + const token: string = request.headers.authorization.replace(/^Bearer\s/, ''); + + return token; +} diff --git a/cloud/functions/src/utils/data/deck-deploy-utils.ts b/cloud/functions/src/utils/data/deck-deploy-utils.ts new file mode 100644 index 000000000..1c7def91a --- /dev/null +++ b/cloud/functions/src/utils/data/deck-deploy-utils.ts @@ -0,0 +1,74 @@ +import * as admin from 'firebase-admin'; + +import {DeckData} from '../../model/data/deck'; + +export function successfulDeploy(deckId: string, type: 'github' | 'api'): Promise { + return new Promise(async (resolve, reject) => { + try { + await updateStatus(deckId, type, 'successful'); + + resolve(); + } catch (err) { + reject(err); + } + }); +} + +export function failureDeploy(deckId: string, type: 'github' | 'api'): Promise { + return new Promise(async (resolve, reject) => { + try { + await updateStatus(deckId, type, 'failure'); + + resolve(); + } catch (err) { + reject(err); + } + }); +} + +function updateStatus(deckId: string, type: 'github' | 'api', status: 'scheduled' | 'failure' | 'successful'): Promise { + return new Promise(async (resolve, reject) => { + try { + if (!deckId || deckId === undefined || !deckId) { + resolve(); + return; + } + + const documentReference: admin.firestore.DocumentReference = admin.firestore().doc(`/decks/${deckId}/`); + + const updateData: Partial = { + updated_at: admin.firestore.Timestamp.now(), + }; + + if (type === 'github') { + updateData.deploy = { + github: { + status, + updated_at: admin.firestore.Timestamp.now(), + }, + }; + } else if (type === 'api') { + updateData.deploy = { + api: { + status, + updated_at: admin.firestore.Timestamp.now(), + }, + }; + } + + await admin.firestore().runTransaction((transaction) => { + return transaction.get(documentReference).then((sfDoc) => { + if (!sfDoc.exists) { + throw new Error('Document does not exist!'); + } + + transaction.set(documentReference, updateData, {merge: true}); + }); + }); + + resolve(); + } catch (err) { + reject(err); + } + }); +} diff --git a/cloud/functions/src/utils/data/deck-utils.ts b/cloud/functions/src/utils/data/deck-utils.ts new file mode 100644 index 000000000..5f9a88e0d --- /dev/null +++ b/cloud/functions/src/utils/data/deck-utils.ts @@ -0,0 +1,48 @@ +import * as admin from 'firebase-admin'; + +import {Deck, DeckData} from '../../model/data/deck'; + +export function findDeck(deckId: string): Promise { + return new Promise(async (resolve, reject) => { + try { + const snapshot: admin.firestore.DocumentSnapshot = await admin.firestore().doc(`/decks/${deckId}/`).get(); + + if (!snapshot.exists) { + reject('Deck not found'); + return; + } + + const deckData: DeckData = snapshot.data() as DeckData; + + resolve({ + id: snapshot.id, + ref: snapshot.ref, + data: deckData, + }); + } catch (err) { + reject(err); + } + }); +} + +export function updateDeck(deckId: string, deckData: Partial): Promise { + return new Promise(async (resolve, reject) => { + try { + if (!deckId || deckId === undefined || deckId === '') { + reject('No deck ID provided to update.'); + return; + } + + const data: Partial = {...deckData}; + data.updated_at = admin.firestore.Timestamp.now(); + + const documentReference: admin.firestore.DocumentReference = admin.firestore().doc(`/decks/${deckId}`); + + await documentReference.set(data, {merge: true}); + + resolve(); + } catch (err) { + reject(err); + } + }); +} diff --git a/cloud/functions/src/utils/data/slide-utils.ts b/cloud/functions/src/utils/data/slide-utils.ts new file mode 100644 index 000000000..ba468808d --- /dev/null +++ b/cloud/functions/src/utils/data/slide-utils.ts @@ -0,0 +1,24 @@ +import * as admin from 'firebase-admin'; + +import {Slide, SlideData} from '../../model/data/slide'; + +export function findSlide(deckId: string, slideId: string): Promise { + return new Promise(async (resolve, reject) => { + try { + const snapshot: admin.firestore.DocumentSnapshot = await admin.firestore().doc(`/decks/${deckId}/slides/${slideId}`).get(); + + if (!snapshot.exists) { + reject('Deck not found'); + return; + } + + resolve({ + id: snapshot.id, + ref: snapshot.ref, + data: snapshot.data() as SlideData, + }); + } catch (err) { + reject(err); + } + }); +} diff --git a/cloud/functions/src/utils/data/task-utils.ts b/cloud/functions/src/utils/data/task-utils.ts new file mode 100644 index 000000000..beeb82eaf --- /dev/null +++ b/cloud/functions/src/utils/data/task-utils.ts @@ -0,0 +1,75 @@ +import * as admin from 'firebase-admin'; + +import {TaskData} from '../../model/data/task'; + +export function scheduleTask(data: Partial): Promise { + return new Promise(async (resolve, reject) => { + try { + const now: admin.firestore.Timestamp = admin.firestore.Timestamp.now(); + + const scheduledData: TaskData = { + ...(data as TaskData), + status: 'scheduled', + created_at: now, + updated_at: now, + }; + + const collectionRef: admin.firestore.CollectionReference = admin.firestore().collection('/tasks/'); + + await collectionRef.add(scheduledData); + + resolve(); + } catch (err) { + reject(err); + } + }); +} + +export function successfulTask(taskId: string): Promise { + return new Promise(async (resolve, reject) => { + try { + await updateStatus(taskId, 'successful'); + + resolve(); + } catch (err) { + reject(err); + } + }); +} + +export function failureTask(taskId: string): Promise { + return new Promise(async (resolve, reject) => { + try { + await updateStatus(taskId, 'failure'); + + resolve(); + } catch (err) { + reject(err); + } + }); +} + +function updateStatus(taskId: string, status: 'scheduled' | 'failure' | 'successful'): Promise { + return new Promise(async (resolve, reject) => { + try { + if (!taskId || taskId === undefined || !taskId) { + resolve(); + return; + } + + const documentReference: admin.firestore.DocumentReference = admin.firestore().doc(`/tasks/${taskId}/`); + + const updateTaskData: Partial = { + status: status, + token: admin.firestore.FieldValue.delete(), + updated_at: admin.firestore.Timestamp.now(), + }; + + await documentReference.set(updateTaskData, {merge: true}); + + resolve(); + } catch (err) { + reject(err); + } + }); +} diff --git a/cloud/functions/src/utils/resources.ts b/cloud/functions/src/utils/resources.ts deleted file mode 100644 index f6403cfc2..000000000 --- a/cloud/functions/src/utils/resources.ts +++ /dev/null @@ -1,13 +0,0 @@ -export class Resources { - - static get Constants(): any { - return { - PRESENTATION: { - URL: 'https://beta.deckdeckgo.io', - FOLDER: 'presentations', - IMAGE: 'deckdeckgo.png' - } - }; - } - -} diff --git a/cloud/functions/src/utils/resources/resources.ts b/cloud/functions/src/utils/resources/resources.ts new file mode 100644 index 000000000..a98dee95b --- /dev/null +++ b/cloud/functions/src/utils/resources/resources.ts @@ -0,0 +1,13 @@ +export class Resources { + static get Constants(): any { + return { + PRESENTATION: { + FOLDER: 'presentations', + IMAGE: 'deckdeckgo.png', + }, + FEED: { + MIN_SLIDES: 3, + }, + }; + } +} diff --git a/cloud/functions/src/watch/clone/clone-deck-slides.ts b/cloud/functions/src/watch/clone/clone-deck-slides.ts index 6b1cf6c3b..1022c8931 100644 --- a/cloud/functions/src/watch/clone/clone-deck-slides.ts +++ b/cloud/functions/src/watch/clone/clone-deck-slides.ts @@ -3,33 +3,33 @@ import {DocumentSnapshot} from 'firebase-functions/lib/providers/firestore'; import {cloneSlides, updateCloneData} from './utils/clone-slides-utils'; -import {DeckData} from '../../model/deck'; +import {DeckData} from '../../model/data/deck'; export async function cloneDeckSlides(snap: DocumentSnapshot, context: EventContext) { - const deck: DeckData = snap.data() as DeckData; + const deck: DeckData = snap.data() as DeckData; - if (!deck || deck === undefined) { - return; - } + if (!deck || deck === undefined) { + return; + } - if (!deck.clone || deck.clone === undefined || !deck.clone.deck_id_from || deck.clone.deck_id_from === undefined || deck.clone.deck_id_from === '') { - return; - } + if (!deck.clone || deck.clone === undefined || !deck.clone.deck_id_from || deck.clone.deck_id_from === undefined || deck.clone.deck_id_from === '') { + return; + } - const deckIdTo: string = context.params.deckId; + const deckIdTo: string = context.params.deckId; - if (!deckIdTo || deckIdTo === undefined || deckIdTo === '') { - return; - } + if (!deckIdTo || deckIdTo === undefined || deckIdTo === '') { + return; + } - try { - const deckIdFrom: string = deck.clone.deck_id_from; + try { + const deckIdFrom: string = deck.clone.deck_id_from; - const slideIds: string[] | undefined = await cloneSlides(deckIdTo, deckIdFrom); + const slideIds: string[] | undefined = await cloneSlides(deckIdTo, deckIdFrom); - await updateCloneData(deckIdTo, slideIds); - await updateCloneData(deckIdFrom); - } catch (err) { - console.error(err); - } + await updateCloneData(deckIdTo, slideIds); + await updateCloneData(deckIdFrom); + } catch (err) { + console.error(err); + } } diff --git a/cloud/functions/src/watch/clone/utils/clone-slides-utils.ts b/cloud/functions/src/watch/clone/utils/clone-slides-utils.ts index 972dcdf8c..67d561da0 100644 --- a/cloud/functions/src/watch/clone/utils/clone-slides-utils.ts +++ b/cloud/functions/src/watch/clone/utils/clone-slides-utils.ts @@ -1,132 +1,94 @@ import * as admin from 'firebase-admin'; -import {Slide} from '../../../model/slide'; -import {Deck, DeckData} from '../../../model/deck'; +import {Slide} from '../../../model/data/slide'; +import {Deck} from '../../../model/data/deck'; + +import {findSlide} from '../../../utils/data/slide-utils'; +import {findDeck} from '../../../utils/data/deck-utils'; export function cloneSlides(deckIdTo: string, deckIdFrom: string): Promise { - return new Promise(async (resolve, reject) => { - try { - // We have to iterate on the deck.data.slides because it contains the order the slides - const deckFrom: Deck = await findDeck(deckIdFrom); - - if (!deckFrom || !deckFrom.data || !deckFrom.data.slides || deckFrom.data.slides.length <= 0) { - resolve(undefined); - return; - } - - const promises: Promise[] = []; - deckFrom.data.slides.forEach((slideId: string) => { - promises.push(cloneSlide(deckIdTo, deckIdFrom, slideId)); - }); - - if (promises && promises.length > 0) { - const newSlideIds: string[] = await Promise.all(promises); - resolve(newSlideIds); - return; - } - - resolve(undefined); - } catch (err) { - reject(err); - } - }); + return new Promise(async (resolve, reject) => { + try { + // We have to iterate on the deck.data.slides because it contains the order the slides + const deckFrom: Deck = await findDeck(deckIdFrom); + + if (!deckFrom || !deckFrom.data || !deckFrom.data.slides || deckFrom.data.slides.length <= 0) { + resolve(undefined); + return; + } + + const promises: Promise[] = []; + deckFrom.data.slides.forEach((slideId: string) => { + promises.push(cloneSlide(deckIdTo, deckIdFrom, slideId)); + }); + + if (promises && promises.length > 0) { + const newSlideIds: string[] = await Promise.all(promises); + resolve(newSlideIds); + return; + } + + resolve(undefined); + } catch (err) { + reject(err); + } + }); } export function updateCloneData(deckId: string, slidesIds?: string[] | undefined): Promise { - return new Promise(async (resolve, reject) => { - try { - if (!deckId || deckId === undefined || deckId === '') { - resolve(); - return; - } - - const documentReference: admin.firestore.DocumentReference = admin.firestore().doc(`/decks/${deckId}/`); - - const updateData: any = { - clone: admin.firestore.FieldValue.delete(), - updated_at: admin.firestore.Timestamp.now() - }; - - if (slidesIds !== undefined && slidesIds.length > 0) { - updateData['slides'] = slidesIds; - } - - await documentReference.set(updateData, {merge: true}); - - resolve(); - } catch (err) { - reject(err); - } - }); + return new Promise(async (resolve, reject) => { + try { + if (!deckId || deckId === undefined || deckId === '') { + resolve(); + return; + } + + const documentReference: admin.firestore.DocumentReference = admin.firestore().doc(`/decks/${deckId}/`); + + const updateData: any = { + clone: admin.firestore.FieldValue.delete(), + updated_at: admin.firestore.Timestamp.now(), + }; + + if (slidesIds !== undefined && slidesIds.length > 0) { + updateData['slides'] = slidesIds; + } + + await documentReference.set(updateData, {merge: true}); + + resolve(); + } catch (err) { + reject(err); + } + }); } function cloneSlide(deckIdTo: string, deckIdFrom: string, slideId: string): Promise { - return new Promise(async (resolve, reject) => { - try { - // Find original slide - const slide: Slide = await findSlide(deckIdFrom, slideId); - - // Clone data - const slideData = {...slide.data}; - - const now: admin.firestore.Timestamp = admin.firestore.Timestamp.now(); - slideData.created_at = now; - slideData.updated_at = now; - - // Create cloned data - const collectionRef: admin.firestore.CollectionReference = admin.firestore().collection(`/decks/${deckIdTo}/slides/`); - - collectionRef.add(slideData).then(async (doc: admin.firestore.DocumentReference) => { - resolve(doc.id); - }, (err) => { - reject(err); - }); - } catch (err) { - reject(err); - } - }); -} - -function findDeck(deckId: string): Promise { - return new Promise(async (resolve, reject) => { - try { - const snapshot: admin.firestore.DocumentSnapshot = await admin.firestore().doc(`/decks/${deckId}/`).get(); - - if (!snapshot.exists) { - reject('Deck not found'); - return; - } - - const deckData: DeckData = snapshot.data() as DeckData; - - resolve({ - id: snapshot.id, - ref: snapshot.ref, - data: deckData - }); - } catch (err) { - reject(err); - } - }) -} - -function findSlide(deckId: string, slideId: string): Promise { - return new Promise(async (resolve, reject) => { - try { - const snapshot: admin.firestore.DocumentSnapshot = await admin.firestore().doc(`/decks/${deckId}/slides/${slideId}`).get(); - - if (!snapshot.exists) { - reject('Deck not found'); - return; - } - - resolve({ - id: snapshot.id, - ref: snapshot.ref, - data: snapshot.data() - }); - } catch (err) { - reject(err); + return new Promise(async (resolve, reject) => { + try { + // Find original slide + const slide: Slide = await findSlide(deckIdFrom, slideId); + + // Clone data + const slideData = {...slide.data}; + + const now: admin.firestore.Timestamp = admin.firestore.Timestamp.now(); + slideData.created_at = now; + slideData.updated_at = now; + + // Create cloned data + const collectionRef: admin.firestore.CollectionReference = admin.firestore().collection(`/decks/${deckIdTo}/slides/`); + + collectionRef.add(slideData).then( + async (doc: admin.firestore.DocumentReference) => { + resolve(doc.id); + }, + (err) => { + reject(err); } - }) + ); + } catch (err) { + reject(err); + } + }); } diff --git a/cloud/functions/src/watch/delete/delete-deck-deploy.ts b/cloud/functions/src/watch/delete/delete-deck-deploy.ts index 2d4718da1..298d717bf 100644 --- a/cloud/functions/src/watch/delete/delete-deck-deploy.ts +++ b/cloud/functions/src/watch/delete/delete-deck-deploy.ts @@ -1,7 +1,7 @@ import {EventContext} from 'firebase-functions'; import {DocumentSnapshot} from 'firebase-functions/lib/providers/firestore'; -import {DeckData} from '../../model/deck'; +import {DeckData} from '../../model/data/deck'; import {deleteDeployForId} from './utils/delete-platform-utils'; diff --git a/cloud/functions/src/watch/delete/delete-decks-slides.ts b/cloud/functions/src/watch/delete/delete-decks-slides.ts index 333b28eb0..39be70f3f 100644 --- a/cloud/functions/src/watch/delete/delete-decks-slides.ts +++ b/cloud/functions/src/watch/delete/delete-decks-slides.ts @@ -1,158 +1,155 @@ import * as admin from 'firebase-admin'; -import {Deck, DeckData} from '../../model/deck'; +import {Deck, DeckData} from '../../model/data/deck'; import {DeckSlides, deleteSlides, findSlides} from './utils/delete-slides-utils'; export async function deleteDecksSlides(userRecord: admin.auth.UserRecord) { - if (!userRecord || !userRecord.uid || userRecord.uid === undefined || userRecord.uid === '') { - return; - } + if (!userRecord || !userRecord.uid || userRecord.uid === undefined || userRecord.uid === '') { + return; + } - try { - const userId: string = userRecord.uid; + try { + const userId: string = userRecord.uid; - const decks: Deck[] | null = await findDecks(userId); + const decks: Deck[] | null = await findDecks(userId); - if (!decks || decks.length <= 0) { - return; - } + if (!decks || decks.length <= 0) { + return; + } - const decksSlides: (DeckSlides | null)[] = await findAllSlides(decks); + const decksSlides: (DeckSlides | null)[] = await findAllSlides(decks); - if (!decksSlides || decksSlides.length <= 0) { - await deleteDecks(decks); - return; - } - - await deleteAllSlides(decksSlides); + if (!decksSlides || decksSlides.length <= 0) { + await deleteDecks(decks); + return; + } - await deleteDecks(decks); + await deleteAllSlides(decksSlides); - } catch (err) { - console.error(err); - } + await deleteDecks(decks); + } catch (err) { + console.error(err); + } } function findDecks(userId: string): Promise { - return new Promise(async (resolve, reject) => { - try { - const collectionRef: admin.firestore.CollectionReference = admin.firestore().collection('/decks/'); - - const snapShot: admin.firestore.QuerySnapshot = await collectionRef - .where('owner_id', '==', userId) - .get(); - - if (snapShot && snapShot.docs && snapShot.docs.length > 0) { - const decks: Deck[] = snapShot.docs.map((doc) => { - const data: Object = doc.data() as DeckData; - const id = doc.id; - const ref = doc.ref; - - return { - id: id, - ref: ref, - data: data - } as Deck; - }); - - resolve(decks); - } else { - resolve(null); - } - } catch (err) { - reject(err); - } - }) + return new Promise(async (resolve, reject) => { + try { + const collectionRef: admin.firestore.CollectionReference = admin.firestore().collection('/decks/'); + + const snapShot: admin.firestore.QuerySnapshot = await collectionRef.where('owner_id', '==', userId).get(); + + if (snapShot && snapShot.docs && snapShot.docs.length > 0) { + const decks: Deck[] = snapShot.docs.map((doc) => { + const data: Object = doc.data() as DeckData; + const id = doc.id; + const ref = doc.ref; + + return { + id: id, + ref: ref, + data: data, + } as Deck; + }); + + resolve(decks); + } else { + resolve(null); + } + } catch (err) { + reject(err); + } + }); } function findAllSlides(decks: Deck[]): Promise<(DeckSlides | null)[]> { - return new Promise<(DeckSlides | null)[]>(async (resolve, reject) => { - try { - const promises: Promise[] = []; - decks.forEach((deck: Deck) => { - promises.push(findSlides(deck.id)); - }); - - if (!promises || promises.length <= 0) { - resolve([]); - return; - } - - const slides: (DeckSlides | null)[] = await Promise.all(promises); - resolve(slides); - } catch (err) { - reject(err); - } - }); + return new Promise<(DeckSlides | null)[]>(async (resolve, reject) => { + try { + const promises: Promise[] = []; + decks.forEach((deck: Deck) => { + promises.push(findSlides(deck.id)); + }); + + if (!promises || promises.length <= 0) { + resolve([]); + return; + } + + const slides: (DeckSlides | null)[] = await Promise.all(promises); + resolve(slides); + } catch (err) { + reject(err); + } + }); } function deleteAllSlides(decksSlides: (DeckSlides | null)[]): Promise { - return new Promise(async (resolve, reject) => { - try { - if (!decksSlides || decksSlides.length <= 0) { - resolve(); - return; - } - - const promises: Promise[] = []; - decksSlides.forEach((deckSlides: DeckSlides | null) => { - if (deckSlides) { - promises.push(deleteSlides(deckSlides.deckId, deckSlides.slides)); - } - }); - - if (promises && promises.length > 0) { - await Promise.all(promises); - } - - resolve(); - } catch (err) { - reject(err); + return new Promise(async (resolve, reject) => { + try { + if (!decksSlides || decksSlides.length <= 0) { + resolve(); + return; + } + + const promises: Promise[] = []; + decksSlides.forEach((deckSlides: DeckSlides | null) => { + if (deckSlides) { + promises.push(deleteSlides(deckSlides.deckId, deckSlides.slides)); } - }); + }); + + if (promises && promises.length > 0) { + await Promise.all(promises); + } + + resolve(); + } catch (err) { + reject(err); + } + }); } function deleteDecks(decks: Deck[]): Promise { - return new Promise(async (resolve, reject) => { - try { - if (!decks || decks.length <= 0) { - resolve(); - return; - } - - const promises: Promise[] = []; - decks.forEach((deck: Deck) => { - promises.push(deleteDeck(deck)); - }); - - if (promises && promises.length > 0) { - await Promise.all(promises); - } - - resolve(); - } catch (err) { - reject(err); - } - }); + return new Promise(async (resolve, reject) => { + try { + if (!decks || decks.length <= 0) { + resolve(); + return; + } + + const promises: Promise[] = []; + decks.forEach((deck: Deck) => { + promises.push(deleteDeck(deck)); + }); + + if (promises && promises.length > 0) { + await Promise.all(promises); + } + + resolve(); + } catch (err) { + reject(err); + } + }); } function deleteDeck(deck: Deck): Promise { - return new Promise(async (resolve, reject) => { - try { - if (!deck || !deck.id || deck.id === undefined || deck.id === '') { - resolve(); - return; - } + return new Promise(async (resolve, reject) => { + try { + if (!deck || !deck.id || deck.id === undefined || deck.id === '') { + resolve(); + return; + } - const collectionRef: admin.firestore.CollectionReference = admin.firestore().collection(`/decks/`); - const doc: admin.firestore.DocumentReference = collectionRef.doc(deck.id); + const collectionRef: admin.firestore.CollectionReference = admin.firestore().collection(`/decks/`); + const doc: admin.firestore.DocumentReference = collectionRef.doc(deck.id); - await doc.delete(); + await doc.delete(); - resolve(); - } catch (err) { - reject(err); - } - }); + resolve(); + } catch (err) { + reject(err); + } + }); } diff --git a/cloud/functions/src/watch/delete/delete-deploy.ts b/cloud/functions/src/watch/delete/delete-deploy.ts deleted file mode 100644 index 025a99b95..000000000 --- a/cloud/functions/src/watch/delete/delete-deploy.ts +++ /dev/null @@ -1,60 +0,0 @@ -import * as admin from 'firebase-admin'; - -import {Deploy} from '../../model/deploy'; - -import {deleteDeployForId} from './utils/delete-platform-utils'; - -export async function deleteDeploy(userRecord: admin.auth.UserRecord) { - if (!userRecord || !userRecord.uid || userRecord.uid === undefined || userRecord.uid === '') { - return; - } - - try { - const userId: string = userRecord.uid; - - const deploys: Deploy[] | null = await findDeploys(userId); - - if (!deploys || deploys.length <= 0) { - return; - } - - const promises: Promise[] = deploys.map((deploy: Deploy) => deleteDeployForId(deploy.id)); - await Promise.all(promises); - } catch (err) { - console.error(err); - } -} - -function findDeploys(userId: string): Promise { - return new Promise(async (resolve, reject) => { - try { - if (!userId || userId === undefined || userId === '') { - resolve(null); - return; - } - - const collectionRef: admin.firestore.CollectionReference = admin.firestore().collection(`/deploys/`); - - const snapShot: admin.firestore.QuerySnapshot = await collectionRef.where('owner_id', '==', userId).get(); - - if (snapShot && snapShot.docs && snapShot.docs.length > 0) { - const decks: Deploy[] = snapShot.docs.map((doc) => { - const id = doc.id; - const ref = doc.ref; - - return { - id: id, - ref: ref, - data: doc.data(), - } as Deploy; - }); - - resolve(decks); - } else { - resolve(null); - } - } catch (err) { - reject(err); - } - }); -} diff --git a/cloud/functions/src/watch/delete/utils/delete-slides-utils.ts b/cloud/functions/src/watch/delete/utils/delete-slides-utils.ts index 95abadd9f..372313864 100644 --- a/cloud/functions/src/watch/delete/utils/delete-slides-utils.ts +++ b/cloud/functions/src/watch/delete/utils/delete-slides-utils.ts @@ -1,6 +1,6 @@ import * as admin from 'firebase-admin'; -import {Slide} from '../../../model/slide'; +import {Slide} from '../../../model/data/slide'; export interface DeckSlides { deckId: string; diff --git a/cloud/functions/src/watch/github/publish-github.ts b/cloud/functions/src/watch/github/publish-github.ts deleted file mode 100644 index f1adbeaab..000000000 --- a/cloud/functions/src/watch/github/publish-github.ts +++ /dev/null @@ -1,79 +0,0 @@ -import * as functions from 'firebase-functions'; -import {DocumentSnapshot} from 'firebase-functions/lib/providers/firestore'; - -import {DeckData} from '../../model/deck'; -import {Token} from '../../model/token'; -import {DeployGitHubRepo} from '../../model/deploy'; - -import {isDeckPublished} from '../screenshot/utils/update-deck'; - -import {getUser, GitHubUser} from './utils/github-api'; -import {clone} from './utils/github-cmd'; -import {findToken} from './utils/github-db'; -import {getRepo, updateDeck, updateProject} from './utils/github-utils'; - -export async function publishToGitHub(change: functions.Change, context: functions.EventContext) { - const newValue: DeckData = change.after.data() as DeckData; - - const previousValue: DeckData = change.before.data() as DeckData; - - if (!newValue || !newValue.meta || !newValue.meta.published || !newValue.meta.pathname) { - return; - } - - if (!newValue.owner_id || newValue.owner_id === undefined || newValue.owner_id === '') { - return; - } - - if (!newValue.meta.title || newValue.meta.title === '') { - return; - } - - if (newValue.meta.github !== true) { - return; - } - - const update: boolean = await isDeckPublished(previousValue, newValue); - - if (!update) { - return; - } - - try { - // Has the user a GitHub token? - - const platform: Token = await findToken(newValue.owner_id); - - if (!platform || !platform.data || !platform.data.github || !platform.data.github.token) { - return; - } - - // Get GitHub user information such as id and username (login) - - const user: GitHubUser = await getUser(platform.data.github.token); - - if (!user) { - return; - } - - // Get or create GitHub repo / project - - const deckId: string = context.params.deckId; - - const repo: DeployGitHubRepo | undefined = await getRepo(platform.data.github.token, user, newValue.owner_id, deckId, newValue.meta); - - if (!repo || repo === undefined || !repo.url || !repo.name) { - return; - } - - //TODO: In the future, if the repo is an existing one, sync dependencies within the PR aka compare these with source repo and provide change to upgrade repo. - - await clone(repo.url, user.login, repo.name); - - await updateProject(platform.data.github.token, user.login, repo.name, repo.url, newValue.meta); - - await updateDeck(platform.data.github.token, user, repo, newValue.meta); - } catch (err) { - console.error(err); - } -} diff --git a/cloud/functions/src/watch/github/utils/github-db.ts b/cloud/functions/src/watch/github/utils/github-db.ts deleted file mode 100644 index 2e4f08b27..000000000 --- a/cloud/functions/src/watch/github/utils/github-db.ts +++ /dev/null @@ -1,83 +0,0 @@ -import * as admin from 'firebase-admin'; - -import {Token, TokenData} from '../../../model/token'; -import {DeployGitHubRepo, Deploy, DeployData} from '../../../model/deploy'; - -export function findToken(userId: string): Promise { - return new Promise(async (resolve, reject) => { - try { - const snapshot: admin.firestore.DocumentSnapshot = await admin.firestore().doc(`/tokens/${userId}/`).get(); - - if (!snapshot.exists) { - reject('Platform not found'); - return; - } - - const data: TokenData = snapshot.data() as TokenData; - - resolve({ - id: snapshot.id, - ref: snapshot.ref, - data, - }); - } catch (err) { - reject(err); - } - }); -} - -export function findDeploy(deckId: string): Promise { - return new Promise(async (resolve, reject) => { - try { - const snapshot: admin.firestore.DocumentSnapshot = await admin.firestore().doc(`/deploys/${deckId}`).get(); - - if (!snapshot.exists) { - resolve(undefined); - return; - } - - const data: DeployData = snapshot.data() as DeployData; - - resolve({ - id: snapshot.id, - ref: snapshot.ref, - data, - }); - } catch (err) { - reject(err); - } - }); -} - -export function updateDeploy(deckId: string, deckData: DeployData, repo: DeployGitHubRepo | undefined): Promise { - return new Promise(async (resolve, reject) => { - try { - if (!deckId || deckId === undefined || deckId === '') { - resolve(); - return; - } - - if (!repo) { - resolve(); - return; - } - - const data: DeployData = {...deckData}; - data.github.repo = {...repo}; - - data.updated_at = admin.firestore.Timestamp.now(); - - if (!data.created_at) { - data.created_at = admin.firestore.Timestamp.now(); - } - - const documentReference: admin.firestore.DocumentReference = admin.firestore().doc(`/deploys/${deckId}`); - - await documentReference.set(data, {merge: true}); - - resolve(); - } catch (err) { - reject(err); - } - }); -} diff --git a/cloud/functions/src/watch/info/info-deck-publish.ts b/cloud/functions/src/watch/info/info-deck-publish.ts index 0219e5ea7..3cca5c2db 100644 --- a/cloud/functions/src/watch/info/info-deck-publish.ts +++ b/cloud/functions/src/watch/info/info-deck-publish.ts @@ -3,89 +3,96 @@ import {DocumentSnapshot} from 'firebase-functions/lib/providers/firestore'; import * as nodemailer from 'nodemailer'; -import {DeckData} from '../../model/deck'; +import {DeckData} from '../../model/data/deck'; import * as Mail from 'nodemailer/lib/mailer'; -import {Resources} from '../../utils/resources'; export async function infoDeckPublish(change: functions.Change) { - const newValue: DeckData = change.after.data() as DeckData; + const newValue: DeckData = change.after.data() as DeckData; - const previousValue: DeckData = change.before.data() as DeckData; + const previousValue: DeckData = change.before.data() as DeckData; - if (!newValue || !newValue.meta || !newValue.meta.published || !newValue.meta.pathname) { - return; - } + if (!newValue || !newValue.meta || !newValue.meta.published || !newValue.meta.pathname) { + return; + } - if (!newValue.owner_id || newValue.owner_id === undefined || newValue.owner_id === '') { - return; - } + if (!newValue.owner_id || newValue.owner_id === undefined || newValue.owner_id === '') { + return; + } - const publish: boolean = await isFirstTimePublished(previousValue, newValue); + const publish: boolean = await isFirstTimePublished(previousValue, newValue); - if (!publish) { - return; - } + if (!publish) { + return; + } - try { - await sendInfo(change.after.id, newValue); - } catch (err) { - console.error(err); - } + try { + await sendInfo(change.after.id, newValue); + } catch (err) { + console.error(err); + } } function isFirstTimePublished(previousValue: DeckData, newValue: DeckData): Promise { - return new Promise((resolve) => { - if (!previousValue || !newValue) { - resolve(false); - return; - } - - resolve(!previousValue.meta && newValue.meta && newValue.meta.published); - }); + return new Promise((resolve) => { + if (!previousValue || !newValue) { + resolve(false); + return; + } + + resolve(!previousValue.meta && newValue.meta && newValue.meta.published); + }); } function sendInfo(deckId: string, deckData: DeckData): Promise { - return new Promise(async (resolve, reject) => { - try { - const mailFrom: string = functions.config().info.mail.from; - const mailPwd: string = functions.config().info.mail.pwd; - const mailTo: string = functions.config().info.mail.to; - const mailHost: string = functions.config().info.mail.host; - - let deckUrl: string = 'Unknown'; - if (deckData && deckData.meta && deckData.meta.pathname) { - let pathname: string = deckData.meta.pathname; - pathname += pathname.endsWith('/') ? '' : '/'; - - deckUrl = Resources.Constants.PRESENTATION.URL + pathname; - } - - const mailOptions = { - from: mailFrom, - to: mailTo, - subject: 'DeckDeckGo: New published deck', - html: `

${deckData.meta ? deckData.meta.title : 'Unknown title'}

+ return new Promise(async (resolve, reject) => { + try { + const infoMailSkip: string = functions.config().info.mail.skip; + + if (infoMailSkip === 'true') { + return; + } + + const mailFrom: string = functions.config().info.mail.from; + const mailPwd: string = functions.config().info.mail.pwd; + const mailTo: string = functions.config().info.mail.to; + const mailHost: string = functions.config().info.mail.host; + + let deckUrl: string = 'Unknown'; + if (deckData && deckData.meta && deckData.meta.pathname) { + let pathname: string = deckData.meta.pathname; + pathname += pathname.endsWith('/') ? '' : '/'; + + const presentationUrl: string = functions.config().deckdeckgo.presentation.url; + + deckUrl = presentationUrl + pathname; + } + + const mailOptions = { + from: mailFrom, + to: mailTo, + subject: 'DeckDeckGo: New published deck', + html: `

${deckData.meta ? deckData.meta.title : 'Unknown title'}

Deck: ${deckId}

Url: ${deckUrl}

- ` - }; - - const transporter: Mail = nodemailer.createTransport({ - host: mailHost, - port: 587, - secure: false, // STARTTLS - auth: { - type: 'LOGIN', - user: mailFrom, - pass: mailPwd - } - }); - - await transporter.sendMail(mailOptions); - - resolve(); - } catch (err) { - reject(err); - } - }); + `, + }; + + const transporter: Mail = nodemailer.createTransport({ + host: mailHost, + port: 587, + secure: false, // STARTTLS + auth: { + type: 'LOGIN', + user: mailFrom, + pass: mailPwd, + }, + }); + + await transporter.sendMail(mailOptions); + + resolve(); + } catch (err) { + reject(err); + } + }); } diff --git a/cloud/functions/src/watch/mailchimp/mailchimp-member.ts b/cloud/functions/src/watch/mailchimp/mailchimp-member.ts index 889d47330..621dd374f 100644 --- a/cloud/functions/src/watch/mailchimp/mailchimp-member.ts +++ b/cloud/functions/src/watch/mailchimp/mailchimp-member.ts @@ -11,7 +11,7 @@ import * as crypto from 'crypto'; // @ts-ignore incorrect typescript typings import * as Mailchimp from 'mailchimp-api-v3'; -import {UserData} from '../../model/user'; +import {UserData} from '../../model/data/user'; export async function createMailchimpMember(userRecord: admin.auth.UserRecord) { if (!userRecord || !userRecord.email || userRecord.email === undefined || userRecord.email === '') { diff --git a/cloud/functions/src/watch/publish/api/publish-api.ts b/cloud/functions/src/watch/publish/api/publish-api.ts new file mode 100644 index 000000000..72926c874 --- /dev/null +++ b/cloud/functions/src/watch/publish/api/publish-api.ts @@ -0,0 +1,85 @@ +import * as admin from 'firebase-admin'; + +import {Deck, DeckData, DeckMeta} from '../../../model/data/deck'; +import {ApiDeck} from '../../../model/api/api.deck'; +import {ApiPresentation} from '../../../model/api/api.presentation'; + +import {convertDeck} from '../../../request/utils/convert-deck-utils'; +import {publishDeckApi} from '../../../request/utils/api-utils'; +import {updateDeck} from '../../../utils/data/deck-utils'; +import {failureDeploy, successfulDeploy} from '../../../utils/data/deck-deploy-utils'; + +import {Resources} from '../../../utils/resources/resources'; + +export function publishToApi(deck: Deck, token: string): Promise { + return new Promise(async (resolve, reject) => { + try { + if (!deck || !deck.data) { + reject('No deck data.'); + return; + } + + if (!deck.data.meta) { + reject('No deck meta data.'); + return; + } + + const apiDeck: ApiDeck = await convertDeck(deck); + + if (!apiDeck) { + reject('Cannot convert deck.'); + return; + } + + const apiDeckPublish: ApiPresentation = await publishDeckApi(deck, apiDeck, token); + + if (!apiDeckPublish || !apiDeckPublish.id || !apiDeckPublish.url) { + reject(`Publish for deck ${deck.id} failed.`); + return; + } + + await updateDeckPublished(deck, apiDeckPublish); + + await successfulDeploy(deck.id, 'api'); + + resolve(); + } catch (err) { + await failureDeploy(deck.id, 'api'); + + reject(err); + } + }); +} + +function updateDeckPublished(deck: Deck, apiDeckPublish: ApiPresentation): Promise { + return new Promise(async (resolve, reject) => { + try { + const url: URL = new URL(apiDeckPublish.url); + + const now: admin.firestore.Timestamp = admin.firestore.Timestamp.now(); + + const deckData: Partial = { + meta: { + feed: deck.data.slides && deck.data.slides.length > Resources.Constants.FEED.MIN_SLIDES, + pathname: url.pathname, + updated_at: now, + } as DeckMeta, + api_id: apiDeckPublish.id, + }; + + const newApiId: boolean = deck.data.api_id !== apiDeckPublish.id; + + // First time published or updated + if (newApiId) { + (deckData.meta as DeckMeta).published = true; + (deckData.meta as DeckMeta).published_at = now; + } + + await updateDeck(deck.id, deckData); + + resolve(); + } catch (err) { + reject(err); + } + }); +} diff --git a/cloud/functions/src/watch/publish/github/publish-github.ts b/cloud/functions/src/watch/publish/github/publish-github.ts new file mode 100644 index 000000000..f889dee6c --- /dev/null +++ b/cloud/functions/src/watch/publish/github/publish-github.ts @@ -0,0 +1,83 @@ +import * as functions from 'firebase-functions'; + +import {DeckData, DeckGitHubRepo} from '../../../model/data/deck'; +import {Token} from '../../../model/data/token'; + +import {getUser, GitHubUser} from './utils/github-api'; +import {clone} from './utils/github-cmd'; +import {findToken} from './utils/github-db'; +import {getRepo, updateGitHubDeck, updateProject} from './utils/github-utils'; +import {failureDeploy, successfulDeploy} from '../../../utils/data/deck-deploy-utils'; + +export async function publishToGitHub(deckId: string, deckData: DeckData): Promise { + return new Promise(async (resolve, reject) => { + try { + if (!deckData || !deckData.meta || !deckData.meta.published || !deckData.meta.pathname) { + reject('No deck meta data.'); + return; + } + + if (!deckData.owner_id || deckData.owner_id === undefined || deckData.owner_id === '') { + reject('No deck owner.'); + return; + } + + if (!deckData.meta.title || deckData.meta.title === '') { + reject('No deck title.'); + return; + } + + const gitHubSkip: string = functions.config().github.skip; + + if (gitHubSkip === 'true') { + await successfulDeploy(deckId, 'github'); + + resolve(); + return; + } + + // Has the user a GitHub token? + + const platform: Token = await findToken(deckData.owner_id); + + if (!platform || !platform.data || !platform.data.github || !platform.data.github.token) { + reject('No token to access GitHub.'); + return; + } + + // Get GitHub user information such as id and username (login) + + const user: GitHubUser = await getUser(platform.data.github.token); + + if (!user) { + reject('No GitHub user resolved.'); + return; + } + + // Get or create GitHub repo / project + + const repo: DeckGitHubRepo | undefined = await getRepo(platform.data.github.token, user, deckData.owner_id, deckId, deckData); + + if (!repo || repo === undefined || !repo.url || !repo.name) { + reject('No GitHub repo found or created.'); + return; + } + + //TODO: In the future, if the repo is an existing one, sync dependencies within the PR aka compare these with source repo and provide change to upgrade repo. + + await clone(repo.url, user.login, repo.name); + + await updateProject(platform.data.github.token, user.login, repo.name, repo.url, deckData.meta); + + await updateGitHubDeck(platform.data.github.token, user, repo, deckData.meta); + + await successfulDeploy(deckId, 'github'); + + resolve(); + } catch (err) { + await failureDeploy(deckId, 'github'); + + reject(err); + } + }); +} diff --git a/cloud/functions/src/watch/github/utils/github-api.ts b/cloud/functions/src/watch/publish/github/utils/github-api.ts similarity index 86% rename from cloud/functions/src/watch/github/utils/github-api.ts rename to cloud/functions/src/watch/publish/github/utils/github-api.ts index d7ec46a3b..bbd9cf538 100644 --- a/cloud/functions/src/watch/github/utils/github-api.ts +++ b/cloud/functions/src/watch/publish/github/utils/github-api.ts @@ -6,7 +6,7 @@ import * as functions from 'firebase-functions'; import fetch, {Response} from 'node-fetch'; -import {DeployGitHubRepo} from '../../../model/deploy'; +import {DeckGitHubRepo} from '../../../../model/data/deck'; export interface GitHubUser { id: string; @@ -37,10 +37,10 @@ export function getUser(githubToken: string): Promise { }); } -export function findOrCreateRepo(githubToken: string, user: GitHubUser, project: string, description: string): Promise { - return new Promise(async (resolve, reject) => { +export function findOrCreateRepo(githubToken: string, user: GitHubUser, project: string, description: string): Promise { + return new Promise(async (resolve, reject) => { try { - const repo: DeployGitHubRepo | undefined = await findRepo(githubToken, user, project); + const repo: DeckGitHubRepo | undefined = await findRepo(githubToken, user, project); // Repo already exists if (repo) { @@ -49,7 +49,7 @@ export function findOrCreateRepo(githubToken: string, user: GitHubUser, project: } // Create a new repo otherwise - const newRepo: DeployGitHubRepo | undefined = await createRepo(githubToken, user, project, description); + const newRepo: DeckGitHubRepo | undefined = await createRepo(githubToken, user, project, description); resolve(newRepo); } catch (err) { @@ -59,8 +59,8 @@ export function findOrCreateRepo(githubToken: string, user: GitHubUser, project: }); } -export function findRepo(githubToken: string, user: GitHubUser, project: string): Promise { - return new Promise(async (resolve, reject) => { +export function findRepo(githubToken: string, user: GitHubUser, project: string): Promise { + return new Promise(async (resolve, reject) => { try { if (!user) { resolve(undefined); @@ -95,8 +95,8 @@ export function findRepo(githubToken: string, user: GitHubUser, project: string) }); } -export function createRepo(githubToken: string, user: GitHubUser, project: string, description: string): Promise { - return new Promise(async (resolve, reject) => { +export function createRepo(githubToken: string, user: GitHubUser, project: string, description: string): Promise { + return new Promise(async (resolve, reject) => { try { if (!user) { resolve(undefined); diff --git a/cloud/functions/src/watch/github/utils/github-cmd.ts b/cloud/functions/src/watch/publish/github/utils/github-cmd.ts similarity index 100% rename from cloud/functions/src/watch/github/utils/github-cmd.ts rename to cloud/functions/src/watch/publish/github/utils/github-cmd.ts diff --git a/cloud/functions/src/watch/publish/github/utils/github-db.ts b/cloud/functions/src/watch/publish/github/utils/github-db.ts new file mode 100644 index 000000000..05b3406d5 --- /dev/null +++ b/cloud/functions/src/watch/publish/github/utils/github-db.ts @@ -0,0 +1,58 @@ +import * as admin from 'firebase-admin'; + +import {Token, TokenData} from '../../../../model/data/token'; +import {DeckData, DeckGitHub, DeckGitHubRepo} from '../../../../model/data/deck'; + +export function findToken(userId: string): Promise { + return new Promise(async (resolve, reject) => { + try { + const snapshot: admin.firestore.DocumentSnapshot = await admin.firestore().doc(`/tokens/${userId}/`).get(); + + if (!snapshot.exists) { + reject('Platform not found'); + return; + } + + const data: TokenData = snapshot.data() as TokenData; + + resolve({ + id: snapshot.id, + ref: snapshot.ref, + data, + }); + } catch (err) { + reject(err); + } + }); +} + +export function updateDeckGitHub(deckId: string, repo: DeckGitHubRepo | undefined): Promise { + return new Promise(async (resolve, reject) => { + try { + if (!deckId || deckId === undefined || deckId === '') { + resolve(); + return; + } + + if (!repo) { + resolve(); + return; + } + + const data: Partial = { + updated_at: admin.firestore.Timestamp.now(), + github: { + repo: {...repo}, + } as DeckGitHub, + }; + + const documentReference: admin.firestore.DocumentReference = admin.firestore().doc(`/decks/${deckId}`); + + await documentReference.set(data, {merge: true}); + + resolve(); + } catch (err) { + reject(err); + } + }); +} diff --git a/cloud/functions/src/watch/github/utils/github-fs.ts b/cloud/functions/src/watch/publish/github/utils/github-fs.ts similarity index 98% rename from cloud/functions/src/watch/github/utils/github-fs.ts rename to cloud/functions/src/watch/publish/github/utils/github-fs.ts index c024de8fb..2db0aaec2 100644 --- a/cloud/functions/src/watch/github/utils/github-fs.ts +++ b/cloud/functions/src/watch/publish/github/utils/github-fs.ts @@ -10,7 +10,7 @@ import {promises as fs} from 'fs'; import * as os from 'os'; import * as path from 'path'; -import {DeckMeta} from '../../../model/deck'; +import {DeckMeta} from '../../../../model/data/deck'; export function deleteDir(localPath: string): Promise { return new Promise((resolve) => { diff --git a/cloud/functions/src/watch/github/utils/github-utils.ts b/cloud/functions/src/watch/publish/github/utils/github-utils.ts similarity index 63% rename from cloud/functions/src/watch/github/utils/github-utils.ts rename to cloud/functions/src/watch/publish/github/utils/github-utils.ts index 8d276c0a0..cfbb1e109 100644 --- a/cloud/functions/src/watch/github/utils/github-utils.ts +++ b/cloud/functions/src/watch/publish/github/utils/github-utils.ts @@ -1,56 +1,45 @@ -import {DeckMeta} from '../../../model/deck'; -import {DeployGitHubRepo, Deploy, DeployData} from '../../../model/deploy'; +import * as functions from 'firebase-functions'; + +import {DeckData, DeckGitHubRepo, DeckMeta} from '../../../../model/data/deck'; import {createPR, createRepo, findOrCreateRepo, findRepo, GitHubUser} from './github-api'; -import {findDeploy, updateDeploy} from './github-db'; +import {updateDeckGitHub} from './github-db'; import {parseDeck, parseInfo, shouldUpdate} from './github-fs'; import {checkoutBranch, commitDeck, commit, pull, push} from './github-cmd'; -import * as functions from 'firebase-functions'; -export async function getRepo( - githubToken: string, - user: GitHubUser, - userId: string, - deckId: string, - deckMeta: DeckMeta -): Promise { - const project: string = deckMeta.title.replace(' ', '-').toLowerCase(); - const description: string = deckMeta.description ? (deckMeta.description as string) : ''; +export async function getRepo(githubToken: string, user: GitHubUser, userId: string, deckId: string, deckData: DeckData): Promise { + if (!deckData || !deckData.meta) { + return undefined; + } - const deploy: Deploy | undefined = await findDeploy(deckId); + const project: string = deckData.meta.title.replace(' ', '-').toLowerCase(); + const description: string = deckData.meta.description ? (deckData.meta.description as string) : ''; - if (deploy) { - const existingRepo: DeployGitHubRepo | undefined = await findRepo(githubToken, user, deploy.data.github.repo.name); + if (deckData.github && deckData.github.repo) { + const existingRepo: DeckGitHubRepo | undefined = await findRepo(githubToken, user, deckData.github.repo.name); if (existingRepo) { // We update our information because the user may have renamed its repo. For example, the new repo name ("hello world world") is returned when looking with the old repo name ("hello world") - await updateDeploy(deckId, deploy.data, existingRepo); + await updateDeckGitHub(deckId, existingRepo); return existingRepo; } // The user may have delete its repo - const createdRepo: DeployGitHubRepo | undefined = await createRepo(githubToken, user, project, description); - await updateDeploy(deckId, deploy.data, createdRepo); + const createdRepo: DeckGitHubRepo | undefined = await createRepo(githubToken, user, project, description); + await updateDeckGitHub(deckId, createdRepo); return createdRepo; } - const repo: DeployGitHubRepo | undefined = await findOrCreateRepo(githubToken, user, project, description); + const repo: DeckGitHubRepo | undefined = await findOrCreateRepo(githubToken, user, project, description); if (!repo) { return undefined; } - const data: DeployData = { - owner_id: userId, - github: { - repo, - }, - }; - - await updateDeploy(deckId, data, repo); + await updateDeckGitHub(deckId, repo); return repo; } @@ -81,7 +70,7 @@ async function updateInfo(githubToken: string, login: string, project: string, u await commit(login, project, msg, ...files); } -export async function updateDeck(githubToken: string, user: GitHubUser, repo: DeployGitHubRepo, meta: DeckMeta) { +export async function updateGitHubDeck(githubToken: string, user: GitHubUser, repo: DeckGitHubRepo, meta: DeckMeta) { // Working branch name const branch: string = functions.config().github.branch; diff --git a/cloud/functions/src/watch/publish/publish.ts b/cloud/functions/src/watch/publish/publish.ts new file mode 100644 index 000000000..677e1fb39 --- /dev/null +++ b/cloud/functions/src/watch/publish/publish.ts @@ -0,0 +1,69 @@ +import {EventContext} from 'firebase-functions'; +import {DocumentSnapshot} from 'firebase-functions/lib/providers/firestore'; + +import {Deck} from '../../model/data/deck'; +import {TaskData} from '../../model/data/task'; + +import {findDeck} from '../../utils/data/deck-utils'; + +import {failureTask, successfulTask} from '../../utils/data/task-utils'; +import {publishToApi} from './api/publish-api'; +import {publishToGitHub} from './github/publish-github'; + +export async function publish(snap: DocumentSnapshot, context: EventContext) { + const taskId: string = context.params.taskId; + + if (!taskId || taskId === undefined || taskId === '') { + return; + } + + try { + await publishJob(snap); + + await successfulTask(taskId); + } catch (err) { + console.error(err); + + await failureTask(taskId); + } +} + +function publishJob(snap: DocumentSnapshot): Promise { + return new Promise(async (resolve, reject) => { + const task: TaskData = snap.data() as TaskData; + + if (!task || task === undefined) { + reject('No task data.'); + return; + } + + if (!task.deckId || !task.token) { + reject('No task token.'); + return; + } + + if (task.status !== 'scheduled') { + reject('Task not scheduled.'); + return; + } + + try { + const deck: Deck = await findDeck(task.deckId); + + if (!deck || !deck.data) { + reject('Task found no deck data.'); + return; + } + + if (task.type === 'publish-deck') { + await publishToApi(deck, task.token as string); + } else if (task.type === 'push-github') { + await publishToGitHub(deck.id, deck.data); + } + + resolve(); + } catch (err) { + reject(err); + } + }); +} diff --git a/cloud/functions/src/watch/screenshot/generate-deck-screenshot.ts b/cloud/functions/src/watch/screenshot/generate-deck-screenshot.ts index dc780a91a..13addefca 100644 --- a/cloud/functions/src/watch/screenshot/generate-deck-screenshot.ts +++ b/cloud/functions/src/watch/screenshot/generate-deck-screenshot.ts @@ -1,17 +1,17 @@ -import {Change} from 'firebase-functions'; +import * as functions from 'firebase-functions'; import {DocumentSnapshot} from 'firebase-functions/lib/providers/firestore'; import * as admin from 'firebase-admin'; import * as puppeteer from 'puppeteer'; -import {Resources} from '../../utils/resources'; +import {Resources} from '../../utils/resources/resources'; -import {DeckData} from '../../model/deck'; +import {DeckData} from '../../model/data/deck'; import {isDeckPublished} from './utils/update-deck'; -export async function generateDeckScreenshot(change: Change) { +export async function generateDeckScreenshot(change: functions.Change) { const newValue: DeckData = change.after.data() as DeckData; const previousValue: DeckData = change.before.data() as DeckData; @@ -57,7 +57,8 @@ function generateScreenshot(deckData: DeckData): Promise { pathname += pathname.endsWith('/') ? '' : '/'; // ?screenshot = no navigation and action displayed in the presentation - await page.goto(Resources.Constants.PRESENTATION.URL + pathname + '?screenshot', {waitUntil: 'networkidle0', timeout: 30000}); + const presentationUrl: string = functions.config().deckdeckgo.presentation.url; + await page.goto(presentationUrl + pathname + '?screenshot', {waitUntil: 'networkidle0', timeout: 30000}); await (page as any)._client.send('ServiceWorker.enable'); await (page as any)._client.send('ServiceWorker.stopAllWorkers'); diff --git a/cloud/functions/src/watch/screenshot/utils/update-deck.ts b/cloud/functions/src/watch/screenshot/utils/update-deck.ts index 66dc57d88..10ee0f687 100644 --- a/cloud/functions/src/watch/screenshot/utils/update-deck.ts +++ b/cloud/functions/src/watch/screenshot/utils/update-deck.ts @@ -1,4 +1,4 @@ -import {DeckData} from '../../../model/deck'; +import {DeckData} from '../../../model/data/deck'; export function isDeckPublished(previousValue: DeckData, newValue: DeckData): Promise { return new Promise((resolve) => { diff --git a/cloud/functions/src/watch/update/update-deck-meta.ts b/cloud/functions/src/watch/update/update-deck-meta.ts index 6aebf48ca..18618b413 100644 --- a/cloud/functions/src/watch/update/update-deck-meta.ts +++ b/cloud/functions/src/watch/update/update-deck-meta.ts @@ -3,8 +3,8 @@ import {DocumentSnapshot} from 'firebase-functions/lib/providers/firestore'; import * as admin from 'firebase-admin'; -import {UserData, UserSocial} from '../../model/user'; -import {Deck, DeckData, DeckMetaAuthor} from '../../model/deck'; +import {UserData, UserSocial} from '../../model/data/user'; +import {Deck, DeckData, DeckMetaAuthor} from '../../model/data/deck'; export async function updateDeckMeta(change: Change, context: EventContext) { const userId: string = context.params.userId; diff --git a/cloud/functions/src/watch/watch-deck.ts b/cloud/functions/src/watch/watch-deck.ts index 7689f7e17..8a8291c62 100644 --- a/cloud/functions/src/watch/watch-deck.ts +++ b/cloud/functions/src/watch/watch-deck.ts @@ -9,10 +9,7 @@ import {deleteDeckDeploy} from './delete/delete-deck-deploy'; import {cloneDeckSlides} from './clone/clone-deck-slides'; -import {publishToGitHub} from './github/publish-github'; - export async function applyWatchDeckUpdate(change: Change, context: EventContext) { - await publishToGitHub(change, context); await generateDeckScreenshot(change); await infoDeckPublish(change); } diff --git a/cloud/functions/src/watch/watch-task.ts b/cloud/functions/src/watch/watch-task.ts new file mode 100644 index 000000000..0314b2a6f --- /dev/null +++ b/cloud/functions/src/watch/watch-task.ts @@ -0,0 +1,8 @@ +import {EventContext} from 'firebase-functions'; +import {DocumentSnapshot} from 'firebase-functions/lib/providers/firestore'; + +import {publish} from './publish/publish'; + +export async function applyWatchTaskCreate(snapshot: DocumentSnapshot, context: EventContext) { + await publish(snapshot, context); +} diff --git a/cloud/functions/src/watch/watch-user.ts b/cloud/functions/src/watch/watch-user.ts index 8dbbbf6b3..3bccf1222 100644 --- a/cloud/functions/src/watch/watch-user.ts +++ b/cloud/functions/src/watch/watch-user.ts @@ -6,7 +6,6 @@ import * as admin from 'firebase-admin'; import {deleteDecksSlides} from './delete/delete-decks-slides'; import {deleteUserStorage} from './delete/delete-user-storage'; import {deleteToken} from './delete/delete-token'; -import {deleteDeploy} from './delete/delete-deploy'; import {createMailchimpMember, deleteMailchimpMember, updateMailchimpMember} from './mailchimp/mailchimp-member'; @@ -17,7 +16,6 @@ export async function applyWatchUserDelete(userRecord: admin.auth.UserRecord, _c await deleteUserStorage(userRecord); await deleteMailchimpMember(userRecord); await deleteToken(userRecord); - await deleteDeploy(userRecord); } export async function applyWatchUserCreate(userRecord: admin.auth.UserRecord, _context: EventContext) { diff --git a/studio/config.dev.json b/studio/config.dev.json index b7e5cec10..f12a2b007 100644 --- a/studio/config.dev.json +++ b/studio/config.dev.json @@ -2,7 +2,7 @@ "API_URL": "https://localhost/beta/api", "PRESENTATION_URL": "https://beta.deckdeckgo.io", "UNSPLASH_URL": "https://localhost/beta/unsplash/", - "FIREBASE_API_KEY" : "AIzaSyDOC1HFzDfuFRH-WdUAJCzDDLA3c88KRw4", + "FIREBASE_API_KEY": "AIzaSyDOC1HFzDfuFRH-WdUAJCzDDLA3c88KRw4", "FIREBASE_AUTH_DOMAIN": "deckdeckgo-studio-beta.firebaseapp.com", "FIREBASE_DATABASE_URL": "https://deckdeckgo-studio-beta.firebaseio.com", "FIREBASE_PROJECT_ID": "deckdeckgo-studio-beta", @@ -10,6 +10,7 @@ "FIREBASE_MESSAGING_SENDER_ID": "448110541714", "FIREBASE_APP_ID": "1:448110541714:web:3d8fd2b806465304", "FIREBASE_STORAGE_URL": "https://firebasestorage.googleapis.com/v0/b/deckdeckgo-studio-beta.appspot.com/o/", + "FIREBASE_FUNCTIONS_URL": "https://us-central1-deckdeckgo-studio-beta.cloudfunctions.net", "TENOR_KEY": "SECRET_KEY", "SOCKET_URL": "https://api.deckdeckgo.com" } diff --git a/studio/src/app/components/editor/publish/app-publish-done/app-publish-done.tsx b/studio/src/app/components/editor/publish/app-publish-done/app-publish-done.tsx index 8cf559e19..593b9f229 100644 --- a/studio/src/app/components/editor/publish/app-publish-done/app-publish-done.tsx +++ b/studio/src/app/components/editor/publish/app-publish-done/app-publish-done.tsx @@ -4,7 +4,6 @@ import deckStore from '../../../../stores/deck.store'; import userStore from '../../../../stores/user.store'; import shareStore from '../../../../stores/share.store'; import authStore from '../../../../stores/auth.store'; -import deployStore from '../../../../stores/deploy.store'; @Component({ tag: 'app-publish-done', @@ -55,11 +54,11 @@ export class AppPublishDone { return undefined; } - if (!deckStore.state.deck || !deckStore.state.deck.data || !deckStore.state.deck.data.meta || !deckStore.state.deck.data.meta.github) { + if (!deckStore.state.deck || !deckStore.state.deck.data || !deckStore.state.deck.data.github || !deckStore.state.deck.data.github.publish) { return undefined; } - if (!deployStore.state.deploy || !deployStore.state.deploy.data) { + if (!deckStore.state.deck.data.github || !deckStore.state.deck.data.github.repo) { return ( The source code of the presentation is processing @@ -70,7 +69,7 @@ export class AppPublishDone { return ( The source code of the presentation has been submitted to a{' '} - + repository {' '} on GitHub . diff --git a/studio/src/app/components/editor/publish/app-publish-edit/app-publish-edit.tsx b/studio/src/app/components/editor/publish/app-publish-edit/app-publish-edit.tsx index c313cd156..e49b4e99a 100644 --- a/studio/src/app/components/editor/publish/app-publish-edit/app-publish-edit.tsx +++ b/studio/src/app/components/editor/publish/app-publish-edit/app-publish-edit.tsx @@ -5,10 +5,8 @@ import {debounce} from '@deckdeckgo/utils'; import deckStore from '../../../../stores/deck.store'; import errorStore from '../../../../stores/error.store'; import feedStore from '../../../../stores/feed.store'; -import publishStore from '../../../../stores/publish.store'; import apiUserStore from '../../../../stores/api.user.store'; import authStore from '../../../../stores/auth.store'; -import deployStore from '../../../../stores/deploy.store'; import {Deck} from '../../../../models/data/deck'; @@ -17,6 +15,8 @@ import {Resources} from '../../../../utils/core/resources'; import {DeckService} from '../../../../services/data/deck/deck.service'; import {PublishService} from '../../../../services/editor/publish/publish.service'; +import {getPublishedUrl} from '../../../../utils/core/share.utils'; + interface CustomInputEvent extends KeyboardEvent { data: string | null; } @@ -41,6 +41,9 @@ export class AppPublishEdit { @State() private publishing: boolean = false; + @State() + private progress: number | undefined = undefined; + @State() private tag: string; @@ -58,6 +61,8 @@ export class AppPublishEdit { private publishService: PublishService; + private destroyDeckListener; + constructor() { this.deckService = DeckService.getInstance(); @@ -70,12 +75,25 @@ export class AppPublishEdit { async componentWillLoad() { await this.init(); + + this.destroyDeckListener = deckStore.onChange('deck', async (deck: Deck | undefined) => { + // Deck is maybe updating while we have set it to true manually + this.publishing = + this.publishing || + (deck.data.deploy && deck.data.deploy.api && (deck.data.deploy.api.status === 'scheduled' || deck.data.deploy.api.status === 'failure')); + }); } componentDidLoad() { this.validateCaptionInput(); } + disconnectedCallback() { + if (this.destroyDeckListener) { + this.destroyDeckListener(); + } + } + private async init() { if (!deckStore.state.deck || !deckStore.state.deck.data) { return; @@ -87,7 +105,7 @@ export class AppPublishEdit { ? (deckStore.state.deck.data.meta.description as string) : await this.getFirstSlideContent(); this.tags = deckStore.state.deck.data.meta && deckStore.state.deck.data.meta.tags ? (deckStore.state.deck.data.meta.tags as string[]) : []; - this.pushToGitHub = deckStore.state.deck.data.meta && deckStore.state.deck.data.meta.github !== undefined ? deckStore.state.deck.data.meta.github : true; + this.pushToGitHub = deckStore.state.deck.data.github ? deckStore.state.deck.data.github.publish : true; } private getFirstSlideContent(): Promise { @@ -155,14 +173,9 @@ export class AppPublishEdit { try { this.publishing = true; - const publishedUrl: string = await this.publishService.publish(this.description, this.tags, this.pushToGitHub); + this.onSuccessfulPublish(); - this.published.emit(publishedUrl); - - this.publishing = false; - - // In case the user would have browse the feed before, reset it to fetch is updated or new presentation - feedStore.reset(); + await this.publishService.publish(this.description, this.tags, this.pushToGitHub); resolve(); } catch (err) { @@ -173,6 +186,51 @@ export class AppPublishEdit { }); } + private onSuccessfulPublish() { + const currentDeck: Deck = {...deckStore.state.deck}; + + const destroyDeckDeployListener = deckStore.onChange('deck', async (deck: Deck | undefined) => { + if (deck && deck.data && deck.data.deploy && deck.data.deploy.api && deck.data.deploy.api.status === 'successful') { + destroyDeckDeployListener(); + + // In case the user would have browse the feed before, reset it to fetch is updated or new presentation + feedStore.reset(); + + await this.delayNavigation(currentDeck.data.api_id !== deckStore.state.deck.data.api_id); + } + }); + } + + // Even if we fixed the delay to publish to Cloudfare CDN (#195), sometimes if too quick, the presentation will not be correctly published + // Therefore, to avoid such problem, we add a bit of delay in the process but only for the first publish + private async delayNavigation(newApiId: boolean) { + this.progress = 0; + + const interval = setInterval( + () => { + this.progress += 0.1; + }, + newApiId ? 7000 / 10 : 700 / 10 + ); + + setTimeout( + async () => { + if (interval) { + clearInterval(interval); + } + + this.progress = 1; + + // Just for display so the progress bar reaches 100% for the eyes + setTimeout(async () => { + const publishedUrl: string = await getPublishedUrl(deckStore.state.deck); + this.published.emit(publishedUrl); + }, 200); + }, + newApiId ? 7000 : 700 + ); + } + private onCaptionInput($event: CustomEvent): Promise { return new Promise((resolve) => { let title: string = ($event.target as InputTargetEvent).value; @@ -306,6 +364,8 @@ export class AppPublishEdit { } render() { + const disable: boolean = this.publishing || this.progress !== undefined; + return (

Share your presentation online

@@ -333,7 +393,7 @@ export class AppPublishEdit { value={this.caption} debounce={500} minlength={3} - disabled={this.publishing} + disabled={disable} maxlength={Resources.Constants.DECK.TITLE_MAX_LENGTH} required={true} input-mode="text" @@ -355,7 +415,7 @@ export class AppPublishEdit { rows={5} value={this.description} debounce={500} - disabled={this.publishing} + disabled={disable} maxlength={Resources.Constants.DECK.DESCRIPTION_MAX_LENGTH} onIonInput={(e: CustomEvent) => this.onDescriptionInput(e)} onIonChange={() => this.validateDescriptionInput()}> @@ -371,7 +431,7 @@ export class AppPublishEdit { input-mode="text" value={this.tag} placeholder="Add a tag..." - disabled={!this.tags || this.tags.length >= 5 || this.publishing} + disabled={!this.tags || this.tags.length >= 5 || disable} onKeyUp={($event: KeyboardEvent) => this.onTagInputKeyUp($event)} onIonInput={(e: CustomEvent) => this.onTagInput(e)}> @@ -379,13 +439,13 @@ export class AppPublishEdit { this.removeTag($event)}> - {this.renderGitHub()} + {this.renderGitHub(disable)} -
{this.renderPublish()}
+
{this.renderPublish(disable)}

DeckDeckGo will automatically generate the social card for your presentation based on the first slide of your deck.

@@ -403,8 +463,8 @@ export class AppPublishEdit { ); } - private renderPublish() { - if (!this.publishing) { + private renderPublish(disable: boolean) { + if (!disable) { return ( Publish now @@ -413,14 +473,22 @@ export class AppPublishEdit { } else { return (
- + {this.renderProgressBar()} Hang on, we are publishing your presentation
); } } - private renderGitHub() { + private renderProgressBar() { + if (this.progress === undefined) { + return ; + } + + return ; + } + + private renderGitHub(disable: boolean) { if (!authStore.state.gitHub) { return undefined; } @@ -433,12 +501,12 @@ export class AppPublishEdit { this.onGitHubChange($event)}> - + Yes - + No @@ -447,7 +515,7 @@ export class AppPublishEdit { } private renderGitHubText() { - if (!deployStore.state.deploy || !deployStore.state.deploy.data) { + if (!deckStore.state.deck || !deckStore.state.deck.data || !deckStore.state.deck.data.github) { return

Push the source code of the presentation to a new public repository of your GitHub account?

; } diff --git a/studio/src/app/modals/editor/app-publish/app-publish.tsx b/studio/src/app/modals/editor/app-publish/app-publish.tsx index 4abf2be7e..91944ff58 100644 --- a/studio/src/app/modals/editor/app-publish/app-publish.tsx +++ b/studio/src/app/modals/editor/app-publish/app-publish.tsx @@ -1,6 +1,6 @@ import {Component, Element, Listen, h, State} from '@stencil/core'; -import {DeployService} from '../../../services/data/deploy/deploy.service'; +import {PublishService} from '../../../services/editor/publish/publish.service'; @Component({ tag: 'app-publish', @@ -12,16 +12,16 @@ export class AppPublish { @State() private publishedUrl: string; - private deployService: DeployService; + private publishService: PublishService; private unsubscribeSnapshot: () => void | undefined; constructor() { - this.deployService = DeployService.getInstance(); + this.publishService = PublishService.getInstance(); } async componentWillLoad() { - this.unsubscribeSnapshot = await this.deployService.snapshot(); + this.unsubscribeSnapshot = await this.publishService.snapshot(); } async componentDidLoad() { diff --git a/studio/src/app/models/auth/auth.user.tsx b/studio/src/app/models/auth/auth.user.tsx index 3b8924823..c6f9d8ead 100644 --- a/studio/src/app/models/auth/auth.user.tsx +++ b/studio/src/app/models/auth/auth.user.tsx @@ -1,6 +1,5 @@ export interface AuthUser { uid: string; - token: string; anonymous: boolean; diff --git a/studio/src/app/models/data/deck.tsx b/studio/src/app/models/data/deck.tsx index 4619f4dff..526dea6c1 100644 --- a/studio/src/app/models/data/deck.tsx +++ b/studio/src/app/models/data/deck.tsx @@ -1,5 +1,27 @@ import {UserSocial} from './user'; +export interface DeckDeployData { + status: 'scheduled' | 'failure' | 'successful'; + updated_at: firebase.firestore.Timestamp; +} + +export interface DeckDeploy { + github?: DeckDeployData; + api?: DeckDeployData; +} + +export interface DeckGitHubRepo { + id: string; + url: string; + name: string; + nameWithOwner: string; +} + +export interface DeckGitHub { + repo?: DeckGitHubRepo; + publish: boolean; +} + export interface DeckMetaAuthor { name: string; photo_url?: string; @@ -12,15 +34,14 @@ export interface DeckMeta { description?: string | firebase.firestore.FieldValue; tags?: string[] | firebase.firestore.FieldValue; - pathname: string; + pathname?: string; author?: DeckMetaAuthor | firebase.firestore.FieldValue; - published: boolean; - published_at: firebase.firestore.Timestamp; + published?: boolean; + published_at?: firebase.firestore.Timestamp; - feed: boolean; - github?: boolean; + feed?: boolean; updated_at: firebase.firestore.Timestamp; } @@ -51,6 +72,10 @@ export interface DeckData { meta?: DeckMeta; + deploy?: DeckDeploy; + + github?: DeckGitHub; + clone?: DeckClone; created_at?: firebase.firestore.Timestamp; diff --git a/studio/src/app/models/data/deploy.tsx b/studio/src/app/models/data/deploy.tsx deleted file mode 100644 index e615288ac..000000000 --- a/studio/src/app/models/data/deploy.tsx +++ /dev/null @@ -1,24 +0,0 @@ -export interface DeployGitHubRepo { - id: string; - url: string; - name: string; - nameWithOwner: string; -} - -export interface DeployGitHub { - repo: DeployGitHubRepo; -} - -export interface DeployData { - owner_id: string; - - github: DeployGitHub; - - updated_at?: firebase.firestore.Timestamp; - created_at?: firebase.firestore.Timestamp; -} - -export interface Deploy { - id: string; - data: DeployData | undefined; -} diff --git a/studio/src/app/pages/core/app-settings/app-settings.tsx b/studio/src/app/pages/core/app-settings/app-settings.tsx index 850bbdb40..a1b4b933d 100644 --- a/studio/src/app/pages/core/app-settings/app-settings.tsx +++ b/studio/src/app/pages/core/app-settings/app-settings.tsx @@ -303,7 +303,9 @@ export class AppHome { this.apiUser.username = this.apiUsername; try { - await this.apiUserService.put(this.apiUser, authStore.state.authUser.token, this.apiUser.id); + const token: string = await firebase.auth().currentUser.getIdToken(); + + await this.apiUserService.put(this.apiUser, token, this.apiUser.id); resolve(); } catch (err) { @@ -397,7 +399,8 @@ export class AppHome { if (firebaseUser) { // We need the user token to access the API, therefore delete it here first - await this.apiUserService.delete(this.apiUser.id, authStore.state.authUser.token); + const token: string = await firebase.auth().currentUser.getIdToken(); + await this.apiUserService.delete(this.apiUser.id, token); // Then delete the user await this.userService.delete(authStore.state.authUser.uid); diff --git a/studio/src/app/pages/core/app-signin/app-signin.tsx b/studio/src/app/pages/core/app-signin/app-signin.tsx index fb3e08fab..93f6c7ab1 100644 --- a/studio/src/app/pages/core/app-signin/app-signin.tsx +++ b/studio/src/app/pages/core/app-signin/app-signin.tsx @@ -212,10 +212,15 @@ export class AppSignIn { await set('deckdeckgo_redirect', this.redirect ? this.redirect : '/'); + let token: string | null = null; + if (authStore.state.authUser) { + token = await firebase.auth().currentUser.getIdToken(); + } + await set('deckdeckgo_redirect_info', { deckId: deckStore.state.deck ? deckStore.state.deck.id : null, userId: authStore.state.authUser ? authStore.state.authUser.uid : null, - userToken: authStore.state.authUser ? authStore.state.authUser.token : null, + userToken: token, anonymous: authStore.state.authUser ? authStore.state.authUser.anonymous : true, }); diff --git a/studio/src/app/services/api/presentation/api.presentation.factory.service.tsx b/studio/src/app/services/api/presentation/api.presentation.factory.service.tsx deleted file mode 100644 index c9fa03d81..000000000 --- a/studio/src/app/services/api/presentation/api.presentation.factory.service.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import {EnvironmentDeckDeckGoConfig} from '../../core/environment/environment-config'; -import {EnvironmentConfigService} from '../../core/environment/environment-config.service'; - -import {ApiPresentationService} from './api.presentation.service'; -import {ApiPresentationMockService} from './api.presentation.mock.service'; -import {ApiPresentationProdService} from './api.presentation.prod.service'; - -export class ApiPresentationFactoryService { - private static instance: ApiPresentationService; - - static getInstance() { - if (!ApiPresentationFactoryService.instance) { - const deckDeckGoConfig: EnvironmentDeckDeckGoConfig = EnvironmentConfigService.getInstance().get('deckdeckgo'); - ApiPresentationFactoryService.instance = !deckDeckGoConfig.prod ? new ApiPresentationMockService() : new ApiPresentationProdService(); - } - return ApiPresentationFactoryService.instance; - } -} diff --git a/studio/src/app/services/api/presentation/api.presentation.mock.service.tsx b/studio/src/app/services/api/presentation/api.presentation.mock.service.tsx deleted file mode 100644 index af4a2e1bb..000000000 --- a/studio/src/app/services/api/presentation/api.presentation.mock.service.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import {ApiPresentationService} from './api.presentation.service'; -import {ApiDeck} from '../../../models/api/api.deck'; -import {ApiPresentation} from '../../../models/api/api.presentation'; - -import {EnvironmentConfigService} from '../../core/environment/environment-config.service'; - -export class ApiPresentationMockService extends ApiPresentationService { - // @Override - protected query(_deck: ApiDeck, _context: string, _method: string, _bearer?: string): Promise { - return new Promise(async (resolve) => { - const presentationUrl: string = EnvironmentConfigService.getInstance().get('deckdeckgo').presentationUrl; - - resolve({ - id: - Math.random() - .toString(36) - .substring(2) + Date.now().toString(36), - url: `${presentationUrl}/daviddalbusco/introducing-deckdeckgo/` - }); - }); - } -} diff --git a/studio/src/app/services/api/presentation/api.presentation.prod.service.tsx b/studio/src/app/services/api/presentation/api.presentation.prod.service.tsx deleted file mode 100644 index a50cfa697..000000000 --- a/studio/src/app/services/api/presentation/api.presentation.prod.service.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import authStore from '../../../stores/auth.store'; - -import {ApiPresentationService} from './api.presentation.service'; -import {ApiDeck} from '../../../models/api/api.deck'; -import {ApiPresentation} from '../../../models/api/api.presentation'; - -import {EnvironmentDeckDeckGoConfig} from '../../core/environment/environment-config'; -import {EnvironmentConfigService} from '../../core/environment/environment-config.service'; - -export class ApiPresentationProdService extends ApiPresentationService { - protected query(deck: ApiDeck, context: string, method: string, bearer?: string): Promise { - return new Promise(async (resolve, reject) => { - try { - const config: EnvironmentDeckDeckGoConfig = EnvironmentConfigService.getInstance().get('deckdeckgo'); - - if (!bearer) { - bearer = authStore.state.bearer; - } - - const rawResponse: Response = await fetch(config.apiUrl + context, { - method: method, - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - Authorization: bearer, - }, - body: JSON.stringify(deck), - }); - - if (!rawResponse || !rawResponse.ok) { - reject('Something went wrong while creating or updating the deck'); - return; - } - - const publishedPresentation: ApiPresentation = await rawResponse.json(); - - resolve(publishedPresentation); - } catch (err) { - reject(err); - } - }); - } -} diff --git a/studio/src/app/services/api/presentation/api.presentation.service.tsx b/studio/src/app/services/api/presentation/api.presentation.service.tsx deleted file mode 100644 index 8093facad..000000000 --- a/studio/src/app/services/api/presentation/api.presentation.service.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import {ApiDeck} from '../../../models/api/api.deck'; - -import {ApiPresentation} from '../../../models/api/api.presentation'; - -import {AuthService} from '../../auth/auth.service'; - -export abstract class ApiPresentationService { - protected authService: AuthService; - - public constructor() { - // Private constructor, singleton - this.authService = AuthService.getInstance(); - } - - protected abstract query(deck: ApiDeck, context: string, method: string, bearer?: string): Promise; - - post(deck: ApiDeck): Promise { - return this.query(deck, '/presentations', 'POST'); - } - - put(apiDeckId: string, deck: ApiDeck, bearer?: string): Promise { - return this.query(deck, `/presentations/${apiDeckId}`, 'PUT', bearer); - } -} diff --git a/studio/src/app/services/api/user/api.user.service.tsx b/studio/src/app/services/api/user/api.user.service.tsx index fdfcc3adc..7529b03d4 100644 --- a/studio/src/app/services/api/user/api.user.service.tsx +++ b/studio/src/app/services/api/user/api.user.service.tsx @@ -1,3 +1,6 @@ +import firebase from '@firebase/app'; +import '@firebase/auth'; + import apiUserStore from '../../../stores/api.user.store'; import {ApiUser, ApiUserInfo} from '../../../models/api/api.user'; @@ -27,7 +30,9 @@ export abstract class ApiUserService { if (!user) { const apiUser: ApiUserInfo = await this.createUserInfo(authUser); - await this.post(apiUser, authUser.token); + const token: string = await firebase.auth().currentUser.getIdToken(); + + await this.post(apiUser, token); } } catch (err) { // We don't display the error. The user could continue to work and edit his/her presentations. diff --git a/studio/src/app/services/auth/auth.service.tsx b/studio/src/app/services/auth/auth.service.tsx index e9142dff7..a5b757c1a 100644 --- a/studio/src/app/services/auth/auth.service.tsx +++ b/studio/src/app/services/auth/auth.service.tsx @@ -51,11 +51,8 @@ export class AuthService { await this.apiUserService.signOut(); } else { - const tokenId: string = await firebaseUser.getIdToken(); - const authUser: AuthUser = { uid: firebaseUser.uid, - token: tokenId, anonymous: firebaseUser.isAnonymous, name: firebaseUser.displayName, email: firebaseUser.email, diff --git a/studio/src/app/services/core/environment/environment-config.tsx b/studio/src/app/services/core/environment/environment-config.tsx index 1c62a41fa..982298b8b 100644 --- a/studio/src/app/services/core/environment/environment-config.tsx +++ b/studio/src/app/services/core/environment/environment-config.tsx @@ -6,6 +6,7 @@ export interface EnvironmentFirebaseConfig { projectId: string; messagingSenderId: string; storageUrl: string; + functionsUrl: string; appId: string; } @@ -56,7 +57,7 @@ export function setupConfig(config: EnvironmentConfig) { win.DeckGo = win.DeckGo || {}; win.DeckGo.config = { ...win.DeckGo.config, - ...config + ...config, }; return win.DeckGo.config; diff --git a/studio/src/app/services/data/deploy/deploy.service.ts b/studio/src/app/services/data/deploy/deploy.service.ts deleted file mode 100644 index 14b8e781e..000000000 --- a/studio/src/app/services/data/deploy/deploy.service.ts +++ /dev/null @@ -1,63 +0,0 @@ -import * as firebase from 'firebase/app'; -import 'firebase/firestore'; - -import {Deck} from '../../../models/data/deck'; -import {DeployData} from '../../../models/data/deploy'; - -import deckStore from '../../../stores/deck.store'; -import deployStore from '../../../stores/deploy.store'; -import authStore from '../../../stores/auth.store'; -import errorStore from '../../../stores/error.store'; - -export class DeployService { - private static instance: DeployService; - - private constructor() { - // Private constructor, singleton - } - - static getInstance() { - if (!DeployService.instance) { - DeployService.instance = new DeployService(); - } - return DeployService.instance; - } - - snapshot(): Promise<() => void | undefined> { - return new Promise<() => void | undefined>((resolve) => { - const deck: Deck = deckStore.state.deck; - - if (!deck || !deck.id) { - deployStore.reset(); - - resolve(undefined); - return; - } - - if (!authStore.state.gitHub) { - deployStore.reset(); - - resolve(undefined); - return; - } - - const firestore: firebase.firestore.Firestore = firebase.firestore(); - const unsubscribe = firestore - .collection(`deploys`) - .doc(deck.id) - .onSnapshot( - (deploySnapshot: firebase.firestore.DocumentSnapshot) => { - deployStore.state.deploy = { - id: deploySnapshot.id, - data: deploySnapshot.data(), - }; - }, - (_err) => { - errorStore.state.error = 'GitHub deploy information cannot be retrieved.'; - } - ); - - resolve(unsubscribe); - }); - } -} diff --git a/studio/src/app/services/editor/publish/publish.service.tsx b/studio/src/app/services/editor/publish/publish.service.tsx index 4f9776146..a30df0cd8 100644 --- a/studio/src/app/services/editor/publish/publish.service.tsx +++ b/studio/src/app/services/editor/publish/publish.service.tsx @@ -1,47 +1,28 @@ import * as firebase from 'firebase/app'; import 'firebase/firestore'; +import 'firebase/auth'; import deckStore from '../../../stores/deck.store'; -import publishStore from '../../../stores/publish.store'; import userStore from '../../../stores/user.store'; +import errorStore from '../../../stores/error.store'; +import authStore from '../../../stores/auth.store'; -import {Deck, DeckMetaAuthor} from '../../../models/data/deck'; -import {ApiDeck} from '../../../models/api/api.deck'; -import {Slide, SlideAttributes, SlideTemplate} from '../../../models/data/slide'; +import {Deck, DeckData, DeckMetaAuthor} from '../../../models/data/deck'; -import {Resources} from '../../../utils/core/resources'; - -import {ApiPresentation} from '../../../models/api/api.presentation'; -import {ApiSlide} from '../../../models/api/api.slide'; import {UserSocial} from '../../../models/data/user'; import {DeckService} from '../../data/deck/deck.service'; -import {SlideService} from '../../data/slide/slide.service'; - -import {ApiPresentationService} from '../../api/presentation/api.presentation.service'; -import {ApiPresentationFactoryService} from '../../api/presentation/api.presentation.factory.service'; import {EnvironmentConfigService} from '../../core/environment/environment-config.service'; -import {EnvironmentGoogleConfig} from '../../core/environment/environment-config'; -import {FontsService} from '../fonts/fonts.service'; +import {EnvironmentFirebaseConfig} from '../../core/environment/environment-config'; export class PublishService { private static instance: PublishService; - private apiPresentationService: ApiPresentationService; - private deckService: DeckService; - private slideService: SlideService; - - private fontsService: FontsService; private constructor() { - this.apiPresentationService = ApiPresentationFactoryService.getInstance(); - this.deckService = DeckService.getInstance(); - this.slideService = SlideService.getInstance(); - - this.fontsService = FontsService.getInstance(); } static getInstance() { @@ -51,317 +32,52 @@ export class PublishService { return PublishService.instance; } - private progress(progress: number) { - publishStore.state.progress = progress; - } - - private progressComplete() { - publishStore.state.progress = 1; - } - - // TODO: Move in a cloud functions? - publish(description: string, tags: string[], github: boolean): Promise { - return new Promise(async (resolve, reject) => { - this.progress(0); - + publish(description: string, tags: string[], github: boolean): Promise { + return new Promise(async (resolve, reject) => { try { if (!deckStore.state.deck || !deckStore.state.deck.id || !deckStore.state.deck.data) { - this.progressComplete(); reject('No deck found'); return; } - const apiDeck: ApiDeck = await this.convertDeck(deckStore.state.deck, description); - - this.progress(0.25); - - const apiDeckPublish: ApiPresentation = await this.publishDeck(deckStore.state.deck, apiDeck); - - this.progress(0.5); - - if (!apiDeckPublish || !apiDeckPublish.id || !apiDeckPublish.url) { - this.progressComplete(); - reject('Publish failed'); - return; - } - - this.progress(0.75); - - const newApiId: boolean = deckStore.state.deck.data.api_id !== apiDeckPublish.id; - if (newApiId) { - deckStore.state.deck.data.api_id = apiDeckPublish.id; - - const updatedDeck: Deck = await this.deckService.update(deckStore.state.deck); - deckStore.state.deck = {...updatedDeck}; - } - - this.progress(0.8); - - const publishedUrl: string = apiDeckPublish.url; - - await this.delayUpdateMeta(deckStore.state.deck, publishedUrl, description, tags, github, newApiId); - - resolve(publishedUrl); - } catch (err) { - this.progressComplete(); - reject(err); - } - }); - } - - private publishDeck(deck: Deck, apiDeck: ApiDeck): Promise { - return new Promise(async (resolve, reject) => { - try { - const apiDeckPublish: ApiPresentation = await this.createOrUpdatePublish(deck, apiDeck); + await this.updateDeckMeta(description, tags, github); - resolve(apiDeckPublish); - } catch (err) { - reject(err); - } - }); - } + await this.publishDeck(deckStore.state.deck); - private createOrUpdatePublish(deck: Deck, apiDeck: ApiDeck): Promise { - if (deck.data.api_id) { - return this.apiPresentationService.put(deck.data.api_id, apiDeck); - } else { - return this.apiPresentationService.post(apiDeck); - } - } - - private convertDeck(deck: Deck, description: string): Promise { - return new Promise(async (resolve, reject) => { - try { - const apiSlides: ApiSlide[] = await this.convertSlides(deck); - - const apiDeck: ApiDeck = { - name: deck.data.name ? deck.data.name.trim() : deck.data.name, - description: description !== undefined && description !== '' ? description : deck.data.name, - owner_id: deck.data.owner_id, - attributes: deck.data.attributes, - background: deck.data.background, - header: deck.data.header, - footer: deck.data.footer, - slides: apiSlides, - }; - - const googleFontScript: string | undefined = await this.getGoogleFontScript(deck); - if (googleFontScript !== undefined) { - apiDeck.head_extra = googleFontScript; - } - - resolve(apiDeck); + resolve(); } catch (err) { reject(err); } }); } - private getGoogleFontScript(deck: Deck): Promise { - return new Promise(async (resolve) => { - if (!document) { - resolve(undefined); - return; - } - if (!deck || !deck.data || !deck.data.attributes) { - resolve(undefined); - return; - } - - if (!deck.data.attributes.style || deck.data.attributes.style === undefined || deck.data.attributes.style === '') { - resolve(undefined); - return; - } - - const div: HTMLDivElement = document.createElement('div'); - div.setAttribute('style', deck.data.attributes.style); - - const fontFamily: string | undefined = div.style.getPropertyValue('font-family'); - - if (!fontFamily || fontFamily === undefined || fontFamily === '') { - resolve(undefined); - return; - } - - const font: GoogleFont | undefined = await this.fontsService.extractGoogleFont(fontFamily); - - if (!font || font === undefined) { - resolve(undefined); - return; - } - - const google: EnvironmentGoogleConfig = EnvironmentConfigService.getInstance().get('google'); - const url: string = this.fontsService.getGoogleFontUrl(google.fontsUrl, font); - - const link = document.createElement('link'); - link.setAttribute('rel', 'stylesheet'); - link.setAttribute('href', url); - - resolve(link.outerHTML); - }); - } - - private convertSlides(deck: Deck): Promise { - return new Promise(async (resolve, reject) => { - if (!deck.data.slides || deck.data.slides.length <= 0) { - resolve([]); - return; - } - + private publishDeck(deck: Deck): Promise { + return new Promise(async (resolve, reject) => { try { - const promises: Promise[] = []; - - for (let i: number = 0; i < deck.data.slides.length; i++) { - const slideId: string = deck.data.slides[i]; - - promises.push(this.convertSlide(deck, slideId)); - } - - if (!promises || promises.length <= 0) { - resolve([]); + const config: EnvironmentFirebaseConfig = EnvironmentConfigService.getInstance().get('firebase'); + + const token: string = await firebase.auth().currentUser.getIdToken(); + + const rawResponse: Response = await fetch(`${config.functionsUrl}/publish`, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify({ + deckId: deck.id, + ownerId: deck.data.owner_id, + publish: true, + github: deck.data.github ? deck.data.github.publish : false, + }), + }); + + if (!rawResponse || !rawResponse.ok) { + reject('Something went wrong while publishing the deck'); return; } - const slides: ApiSlide[] = await Promise.all(promises); - - resolve(slides); - } catch (err) { - reject(err); - } - }); - } - - private convertSlide(deck: Deck, slideId: string): Promise { - return new Promise(async (resolve, reject) => { - const slide: Slide = await this.slideService.get(deck.id, slideId); - - if (!slide || !slide.data) { - reject('Missing slide for publishing'); - return; - } - - const attributes: SlideAttributes = await this.convertAttributesToString(slide.data.attributes); - - const apiSlide: ApiSlide = { - template: slide.data.template, - content: slide.data.content, - attributes: attributes, - }; - - const cleanApiSlide: ApiSlide = await this.convertSlideQRCode(apiSlide); - cleanApiSlide.content = await this.cleanNotes(apiSlide.content); - - resolve(cleanApiSlide); - }); - } - - private convertAttributesToString(attributes: SlideAttributes): Promise { - return new Promise((resolve) => { - if (!attributes) { - resolve(undefined); - return; - } - - // We loose the type but doing so we ensure that all attributes are converted to string in order to parse them to HTML in the API - const result: SlideAttributes = {}; - Object.keys(attributes).forEach((key: string) => { - result[key] = `${attributes[key]}`; - }); - - if (!result) { - resolve(undefined); - return; - } - - resolve(result); - }); - } - - private cleanNotes(content: string): Promise { - return new Promise((resolve) => { - if (!content || content === undefined || content === '') { - resolve(content); - return; - } - - const result: string = content.replace(/
(.|[\s\S])*?<\/div>/gi, ''); - - resolve(result); - }); - } - - private convertSlideQRCode(apiSlide: ApiSlide): Promise { - return new Promise(async (resolve) => { - if (!apiSlide) { - resolve(apiSlide); - return; - } - - if (apiSlide.template !== SlideTemplate.QRCODE) { - resolve(apiSlide); - return; - } - - const presentationUrl: string = EnvironmentConfigService.getInstance().get('deckdeckgo').presentationUrl; - - // If no attributes at all, we create an attribute "content" of the QR code with it's upcoming published url - if (!apiSlide.attributes) { - apiSlide.attributes = { - content: `${presentationUrl}{{DECKDECKGO_BASE_HREF}}`, - }; - } - - // If not custom content, we replace the attribute "content" of the QR code with it's upcoming published url - if (!apiSlide.attributes.hasOwnProperty('customQRCode') || !apiSlide.attributes.customQRCode) { - apiSlide.attributes.content = `${presentationUrl}{{DECKDECKGO_BASE_HREF}}`; - } - - // In any case, we don't need customQRCode attribute in our presentations, this is an attribute used by the editor - if (apiSlide.attributes.hasOwnProperty('customQRCode')) { - delete apiSlide.attributes['customQRCode']; - } - - resolve(apiSlide); - }); - } - - // Even if we fixed the delay to publish to Cloudfare CDN (#195), sometimes if too quick, the presentation will not be correctly published - // Therefore, to avoid such problem, we add a bit of delay in the process but only for the first publish - private delayUpdateMeta(deck: Deck, publishedUrl: string, description: string, tags: string[], github: boolean, delay: boolean): Promise { - return new Promise((resolve) => { - setTimeout( - () => { - this.progress(0.9); - - setTimeout( - async () => { - await this.updateDeckMeta(deck, publishedUrl, description, tags, github); - - this.progress(0.95); - - await this.refreshDeck(deck.id); - - this.progressComplete(); - - setTimeout(() => { - resolve(); - }, 500); - }, - delay ? 3500 : 0 - ); - }, - delay ? 3500 : 0 - ); - }); - } - - // Otherwise we gonna kept in memory references like firebase.firestore.FieldValue.delete instead of null values - private refreshDeck(deckId: string): Promise { - return new Promise(async (resolve, reject) => { - try { - const freshDeck: Deck = await this.deckService.get(deckId); - deckStore.state.deck = {...freshDeck}; - resolve(); } catch (err) { reject(err); @@ -369,40 +85,26 @@ export class PublishService { }); } - private updateDeckMeta(deck: Deck, publishedUrl: string, description: string, tags: string[], github: boolean): Promise { + private updateDeckMeta(description: string, tags: string[], github: boolean): Promise { return new Promise(async (resolve, reject) => { try { - if (!publishedUrl || publishedUrl === undefined || publishedUrl === '') { - resolve(); - return; - } - if (!userStore.state.user || !userStore.state.user.data) { reject('No user'); return; } - const url: URL = new URL(publishedUrl); const now: firebase.firestore.Timestamp = firebase.firestore.Timestamp.now(); - const feed: boolean = deck.data.slides && deck.data.slides.length > Resources.Constants.DECK.MIN_SLIDES; + const deck: Deck = {...deckStore.state.deck}; if (!deck.data.meta) { deck.data.meta = { title: deck.data.name, - pathname: url.pathname, - published: true, - published_at: now, - feed: feed, - github: github, updated_at: now, }; } else { deck.data.meta.title = deck.data.name; - deck.data.meta.pathname = url.pathname; deck.data.meta.updated_at = now; - deck.data.meta.feed = feed; - deck.data.meta.github = github; } if (description && description !== undefined && description !== '') { @@ -446,7 +148,19 @@ export class PublishService { deck.data.meta.author = firebase.firestore.FieldValue.delete(); } - await this.deckService.update(deck); + // Update GitHub info (push or not) for GitHub users so next time user publish, the choice is kept + if (authStore.state.gitHub) { + if (deck.data.github) { + deck.data.github.publish = github; + } else { + deck.data.github = { + publish: github, + }; + } + } + + const updatedDeck: Deck = await this.deckService.update(deckStore.state.deck); + deckStore.state.deck = {...updatedDeck}; resolve(); } catch (err) { @@ -454,4 +168,33 @@ export class PublishService { } }); } + + snapshot(): Promise<() => void | undefined> { + return new Promise<() => void | undefined>((resolve) => { + const deck: Deck = deckStore.state.deck; + + if (!deck || !deck.id) { + resolve(undefined); + return; + } + + const firestore: firebase.firestore.Firestore = firebase.firestore(); + const unsubscribe = firestore + .collection(`decks`) + .doc(deck.id) + .onSnapshot( + (deploySnapshot: firebase.firestore.DocumentSnapshot) => { + deckStore.state.deck = { + id: deploySnapshot.id, + data: deploySnapshot.data(), + }; + }, + (_err) => { + errorStore.state.error = 'Cannont retrieve the deck information.'; + } + ); + + resolve(unsubscribe); + }); + } } diff --git a/studio/src/app/stores/auth.store.ts b/studio/src/app/stores/auth.store.ts index 89ee33441..d2109eeae 100644 --- a/studio/src/app/stores/auth.store.ts +++ b/studio/src/app/stores/auth.store.ts @@ -5,7 +5,6 @@ import {AuthUser} from '../models/auth/auth.user'; interface AuthStore { authUser: AuthUser | null; anonymous: boolean; - bearer: string; loggedIn: boolean; gitHub: boolean; } @@ -13,14 +12,12 @@ interface AuthStore { const {state, onChange, reset} = createStore({ authUser: null, anonymous: true, - bearer: 'Bearer ', loggedIn: false, gitHub: false, } as AuthStore); onChange('authUser', (authUser: AuthUser) => { state.anonymous = authUser ? authUser.anonymous : true; - state.bearer = `Bearer ${authUser ? authUser.token : ''}`; state.loggedIn = authUser && !authUser.anonymous; state.gitHub = authUser ? authUser.gitHub : false; }); diff --git a/studio/src/app/stores/deck.store.ts b/studio/src/app/stores/deck.store.ts index d306a0a5e..cfbd529c1 100644 --- a/studio/src/app/stores/deck.store.ts +++ b/studio/src/app/stores/deck.store.ts @@ -19,4 +19,4 @@ onChange('deck', (deck: Deck | null) => { state.published = deck && deck.data && deck.data.meta && deck.data.meta.published ? deck.data.meta.published : false; }); -export default {state, reset}; +export default {state, onChange, reset}; diff --git a/studio/src/app/stores/deploy.store.ts b/studio/src/app/stores/deploy.store.ts deleted file mode 100644 index e8412b709..000000000 --- a/studio/src/app/stores/deploy.store.ts +++ /dev/null @@ -1,13 +0,0 @@ -import {createStore} from '@stencil/store'; - -import {Deploy} from '../models/data/deploy'; - -interface DeployStore { - deploy: Deploy | undefined; -} - -const {state, onChange, reset} = createStore({ - deploy: undefined, -} as DeployStore); - -export default {state, onChange, reset}; diff --git a/studio/src/app/stores/publish.store.ts b/studio/src/app/stores/publish.store.ts deleted file mode 100644 index 47e5fcea2..000000000 --- a/studio/src/app/stores/publish.store.ts +++ /dev/null @@ -1,11 +0,0 @@ -import {createStore} from '@stencil/store'; - -interface PublishStore { - progress: number | undefined; -} - -const {state} = createStore({ - progress: undefined, -} as PublishStore); - -export default {state}; diff --git a/studio/src/app/utils/core/resources.tsx b/studio/src/app/utils/core/resources.tsx index 77005d2b3..4f1334242 100644 --- a/studio/src/app/utils/core/resources.tsx +++ b/studio/src/app/utils/core/resources.tsx @@ -4,7 +4,6 @@ export class Resources { DECK: { TITLE_MAX_LENGTH: 45, DESCRIPTION_MAX_LENGTH: 500, - MIN_SLIDES: 3, }, STORAGE: { MAX_QUERY_RESULTS: 20, diff --git a/studio/src/global/app-dev.ts b/studio/src/global/app-dev.ts index 6d9f3b5e7..42a1d66cf 100644 --- a/studio/src/global/app-dev.ts +++ b/studio/src/global/app-dev.ts @@ -52,6 +52,7 @@ setupDeckGoConfig({ storageBucket: '<@FIREBASE_STORAGE_BUCKET@>', messagingSenderId: '<@FIREBASE_MESSAGING_SENDER_ID@>', storageUrl: '<@FIREBASE_STORAGE_URL@>', + functionsUrl: '<@FIREBASE_FUNCTIONS_URL@>', appId: '<@FIREBASE_APP_ID@>', }, tenor: { diff --git a/studio/src/global/app-staging.ts b/studio/src/global/app-staging.ts index 8da78e48f..ebc4ab1d6 100644 --- a/studio/src/global/app-staging.ts +++ b/studio/src/global/app-staging.ts @@ -50,6 +50,7 @@ setupDeckGoConfig({ storageBucket: '<@FIREBASE_STORAGE_BUCKET@>', messagingSenderId: '<@FIREBASE_MESSAGING_SENDER_ID@>', storageUrl: '<@FIREBASE_STORAGE_URL@>', + functionsUrl: '<@FIREBASE_FUNCTIONS_URL@>', appId: '<@FIREBASE_APP_ID@>', }, tenor: { diff --git a/studio/src/global/app.ts b/studio/src/global/app.ts index af0e9e8eb..9c597f3e7 100644 --- a/studio/src/global/app.ts +++ b/studio/src/global/app.ts @@ -52,6 +52,7 @@ setupDeckGoConfig({ storageBucket: '<@FIREBASE_STORAGE_BUCKET@>', messagingSenderId: '<@FIREBASE_MESSAGING_SENDER_ID@>', storageUrl: '<@FIREBASE_STORAGE_URL@>', + functionsUrl: '<@FIREBASE_FUNCTIONS_URL@>', appId: '<@FIREBASE_APP_ID@>', }, tenor: { diff --git a/studio/src/index.html b/studio/src/index.html index 2d73cf449..004050cf4 100644 --- a/studio/src/index.html +++ b/studio/src/index.html @@ -11,7 +11,7 @@ style-src 'self' 'unsafe-inline' https://www.gstatic.com https://fonts.googleapis.com; font-src 'self' https://fonts.googleapis.com https://fonts.gstatic.com; script-src 'self' blob: <@PRELOADMODULE_LINKS@> <@SW_LOADER@> 'sha256-vay/aAFxtYsaISRoBsVDHCbAzow9u6P2gHHTewRPaJY=' 'sha256-Q4WsoEk996jr5AUtYVXE8AMljYZgUVD3cxqC2YHsvyQ=' https://www.gstatic.com https://apis.google.com https://unpkg.com/prismjs@latest/; - connect-src 'self' <@API_URLS@> https://deckdeckgo.com/ http://deckdeckgo-studio-beta.web.app/ http://deckdeckgo-studio-staging.web.app/ wss://api.deckdeckgo.com/ https://firebasestorage.googleapis.com/v0/b/deckdeckgo-studio-prod.appspot.com/ https://firebasestorage.googleapis.com/v0/b/deckdeckgo-studio-beta.appspot.com/ https://firebasestorage.googleapis.com/v0/b/deckdeckgo-studio-staging.appspot.com/ https://www.googleapis.com https://securetoken.googleapis.com https://firestore.googleapis.com ws://localhost:3333/ https://raw.githubusercontent.com/PrismJS/ https://raw.githubusercontent.com/deckgo/ https://api.tenor.com/ https://media.tenor.com/ https://formspree.io/xjvaebzk; + connect-src 'self' <@API_URLS@> https://deckdeckgo.com/ http://deckdeckgo-studio-beta.web.app/ http://deckdeckgo-studio-staging.web.app/ wss://api.deckdeckgo.com/ https://firebasestorage.googleapis.com/v0/b/deckdeckgo-studio-prod.appspot.com/ https://firebasestorage.googleapis.com/v0/b/deckdeckgo-studio-beta.appspot.com/ https://firebasestorage.googleapis.com/v0/b/deckdeckgo-studio-staging.appspot.com/ https://us-central1-deckdeckgo-studio-staging.cloudfunctions.net/ https://us-central1-deckdeckgo-studio-beta.cloudfunctions.net/ https://us-central1-deckdeckgo-studio.cloudfunctions.net/ https://www.googleapis.com https://securetoken.googleapis.com https://firestore.googleapis.com ws://localhost:3333/ https://raw.githubusercontent.com/PrismJS/ https://raw.githubusercontent.com/deckgo/ https://api.tenor.com/ https://media.tenor.com/ https://formspree.io/xjvaebzk; frame-src https: http://localhost:3333/~dev-server" /> @@ -124,8 +124,6 @@ - +