From c0667c0a5fa230a99dbcf0ce51e2cf3b013b0be2 Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Thu, 3 Sep 2020 15:18:43 +0200 Subject: [PATCH 01/30] chore: update functions dependencies --- cloud/functions/package-lock.json | 1294 ++++++++++++----------------- cloud/functions/package.json | 16 +- 2 files changed, 553 insertions(+), 757 deletions(-) diff --git a/cloud/functions/package-lock.json b/cloud/functions/package-lock.json index 3cc3be2f3..dcbca1f1f 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", @@ -322,24 +357,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", @@ -363,9 +389,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", @@ -431,9 +457,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,9 +477,9 @@ } }, "@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": "*" @@ -483,9 +509,9 @@ } }, "agent-base": { - "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==", + "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 +528,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 +558,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 +597,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 +627,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": { @@ -693,6 +710,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 +743,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 +812,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": { @@ -886,9 +933,9 @@ } }, "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 +946,11 @@ "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 }, "delayed-stream": { "version": "1.0.0", @@ -977,35 +1000,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 +1056,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 +1090,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", @@ -1162,18 +1119,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" } @@ -1330,9 +1287,9 @@ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, "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 +1341,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 +1356,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 +1381,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 +1419,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 +1426,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 +1439,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 +1500,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 +1562,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,72 +1587,17 @@ "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, - "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" - } - } - } + "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 }, "http-errors": { "version": "1.7.2", @@ -1831,6 +1654,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", @@ -1960,50 +1789,16 @@ "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,20 +1807,6 @@ "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-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", @@ -2037,53 +1818,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": { @@ -2138,12 +1881,12 @@ "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, "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 +1911,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 +1923,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 +1945,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==" } } }, @@ -2247,7 +1995,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" } @@ -2317,12 +2064,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 +2127,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 +2191,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 +2225,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,9 +5759,9 @@ } }, "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" @@ -6022,37 +5777,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 +5794,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,12 +5807,6 @@ "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 - }, "p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -6101,7 +5819,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" } @@ -6140,8 +5857,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", @@ -6200,23 +5916,75 @@ } }, "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 +6003,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 +6024,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 +6086,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 +6181,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==", - "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==", + "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": { - "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 +6242,18 @@ } } }, + "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 +6270,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": { @@ -6580,6 +6372,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 +6398,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 +6409,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", @@ -6689,42 +6477,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 +6503,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", @@ -6778,25 +6548,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": { @@ -6814,14 +6575,14 @@ } }, "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 +6596,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": { @@ -6908,9 +6669,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 +6707,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": { @@ -6983,9 +6744,9 @@ } }, "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==" }, "which": { "version": "2.0.2", @@ -6996,30 +6757,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 +6769,42 @@ "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==", + "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 +6838,11 @@ "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==" + "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 +6855,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..1d888c6d5 100644 --- a/cloud/functions/package.json +++ b/cloud/functions/package.json @@ -12,17 +12,17 @@ }, "main": "lib/index.js", "dependencies": { - "firebase-admin": "^8.12.1", - "firebase-functions": "^3.7.0", + "firebase-admin": "^9.1.1", + "firebase-functions": "^3.11.0", "install": "^0.13.0", "js-beautify": "^1.13.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/js-beautify": "^1.11.0", @@ -32,10 +32,10 @@ "@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": { From fc2d56500ac9f8211f9bb123c359daa7aff75fcd Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Thu, 3 Sep 2020 17:50:11 +0200 Subject: [PATCH 02/30] feat: add a publish action and validate user token --- cloud/functions/src/index.ts | 4 ++++ .../src/request/publish/publish-deck.ts | 24 +++++++++++++++++++ .../src/request/utils/request-utils.ts | 18 ++++++++++++++ 3 files changed, 46 insertions(+) create mode 100644 cloud/functions/src/request/publish/publish-deck.ts create mode 100644 cloud/functions/src/request/utils/request-utils.ts diff --git a/cloud/functions/src/index.ts b/cloud/functions/src/index.ts index 04f7be044..cf3b31c31 100644 --- a/cloud/functions/src/index.ts +++ b/cloud/functions/src/index.ts @@ -9,6 +9,8 @@ app.firestore().settings({timestampsInSnapshots: true}); import {applyWatchDeckCreate, applyWatchDeckDelete, applyWatchDeckUpdate} from './watch/watch-deck'; import {applyWatchUserCreate, applyWatchUserDelete, applyWatchUserUpdate} from './watch/watch-user'; +import {publishDeck} from './request/publish/publish-deck'; + const runtimeOpts = { timeoutSeconds: 120, memory: '1GB', @@ -25,3 +27,5 @@ export const watchUserUpdate = functions.firestore.document('users/{userId}').on export const watchUserDelete = functions.auth.user().onDelete(applyWatchUserDelete); export const watchUserCreate = functions.auth.user().onCreate(applyWatchUserCreate); + +export const publish = functions.runWith(runtimeOpts).https.onRequest(publishDeck); diff --git a/cloud/functions/src/request/publish/publish-deck.ts b/cloud/functions/src/request/publish/publish-deck.ts new file mode 100644 index 000000000..4d5f04d97 --- /dev/null +++ b/cloud/functions/src/request/publish/publish-deck.ts @@ -0,0 +1,24 @@ +import * as functions from 'firebase-functions'; + +import {verifyToken} from '../utils/request-utils'; + +export async function publishDeck(request: functions.Request, response: functions.Response) { + const validToken: boolean = await verifyToken(request); + + if (!validToken) { + response.status(400).json({ + error: 'Not Authorized', + }); + return; + } + + try { + response.json({ + result: 'success', + }); + } catch (err) { + response.status(500).json({ + error: err, + }); + } +} 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..cb696af22 --- /dev/null +++ b/cloud/functions/src/request/utils/request-utils.ts @@ -0,0 +1,18 @@ +import * as admin from 'firebase-admin'; +import * as functions from 'firebase-functions'; + +export async function verifyToken(request: functions.Request): Promise { + if (!request.headers.authorization) { + return false; + } + + try { + const token: string = request.headers.authorization.replace(/^Bearer\s/, ''); + + const payload: admin.auth.DecodedIdToken = await admin.auth().verifyIdToken(token); + + return payload !== null; + } catch (err) { + return false; + } +} From dcd6a653e57f1c3980f880c8ef25fb1eaa49bfb8 Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Thu, 3 Sep 2020 17:58:31 +0200 Subject: [PATCH 03/30] feat: accept only not anonymous users --- cloud/functions/src/request/utils/request-utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cloud/functions/src/request/utils/request-utils.ts b/cloud/functions/src/request/utils/request-utils.ts index cb696af22..89a42adbb 100644 --- a/cloud/functions/src/request/utils/request-utils.ts +++ b/cloud/functions/src/request/utils/request-utils.ts @@ -1,7 +1,7 @@ import * as admin from 'firebase-admin'; import * as functions from 'firebase-functions'; -export async function verifyToken(request: functions.Request): Promise { +export async function verifyToken(request: functions.Request, acceptAnonymous: boolean = false): Promise { if (!request.headers.authorization) { return false; } @@ -11,7 +11,7 @@ export async function verifyToken(request: functions.Request): Promise const payload: admin.auth.DecodedIdToken = await admin.auth().verifyIdToken(token); - return payload !== null; + return payload !== null && (acceptAnonymous || payload.firebase.sign_in_provider !== 'anonymous'); } catch (err) { return false; } From 0fc36b2c4e65a54f0b55ff974c7ed56f690c22a0 Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Fri, 4 Sep 2020 10:33:36 +0200 Subject: [PATCH 04/30] feat: publish first step, convert deck in cloud --- cloud/config/cors-studio-staging.json | 16 + cloud/config/set-cors-gcp.sh | 5 + cloud/functions/package-lock.json | 380 +++++++++++++++++- cloud/functions/package.json | 4 + cloud/functions/src/index.ts | 10 +- cloud/functions/src/model/api/api.deck.ts | 15 + cloud/functions/src/model/api/api.slide.ts | 7 + cloud/functions/src/model/deck.ts | 9 +- cloud/functions/src/model/slide.ts | 75 +++- .../src/request/publish/publish-deck.ts | 27 ++ .../src/request/utils/convert-deck-utils.ts | 165 ++++++++ .../src/request/utils/google-fonts.utils.ts | 143 +++++++ cloud/functions/src/utils/deck-utils.ts | 26 ++ cloud/functions/src/utils/slide-utils.ts | 24 ++ .../watch/clone/utils/clone-slides-utils.ts | 200 ++++----- studio/config.dev.json | 3 +- .../core/environment/environment-config.tsx | 3 +- .../editor/publish/publish.service.tsx | 303 +++----------- studio/src/app/stores/auth.store.ts | 3 + studio/src/global/app-dev.ts | 1 + studio/src/global/app-staging.ts | 1 + studio/src/global/app.ts | 1 + studio/src/index.html | 6 +- 23 files changed, 1057 insertions(+), 370 deletions(-) create mode 100644 cloud/config/cors-studio-staging.json create mode 100755 cloud/config/set-cors-gcp.sh create mode 100644 cloud/functions/src/model/api/api.deck.ts create mode 100644 cloud/functions/src/model/api/api.slide.ts create mode 100644 cloud/functions/src/request/utils/convert-deck-utils.ts create mode 100644 cloud/functions/src/request/utils/google-fonts.utils.ts create mode 100644 cloud/functions/src/utils/deck-utils.ts create mode 100644 cloud/functions/src/utils/slide-utils.ts 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-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 dcbca1f1f..c3287e67f 100644 --- a/cloud/functions/package-lock.json +++ b/cloud/functions/package-lock.json @@ -346,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", @@ -382,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", @@ -447,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", @@ -485,6 +511,17 @@ "@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", @@ -508,6 +545,25 @@ "negotiator": "0.6.2" } }, + "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/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", @@ -678,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", @@ -924,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", @@ -932,6 +1013,16 @@ "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.14.1", "resolved": "https://registry.npmjs.org/date-and-time/-/date-and-time-0.14.1.tgz", @@ -952,6 +1043,16 @@ "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", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -981,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", @@ -1101,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", @@ -1286,6 +1423,11 @@ "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.3", "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz", @@ -1599,6 +1741,14 @@ "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": { + "whatwg-encoding": "^1.0.5" + } + }, "http-errors": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", @@ -1784,6 +1934,11 @@ "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", @@ -1807,6 +1962,11 @@ "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", "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", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", @@ -1880,6 +2040,56 @@ "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": "1.0.0", "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", @@ -1985,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", @@ -2057,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", @@ -5767,6 +5991,11 @@ "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", @@ -5807,6 +6036,19 @@ "integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==", "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", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -5849,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", @@ -5915,6 +6162,11 @@ "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.1", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.1.1.tgz", @@ -6242,6 +6494,31 @@ } } }, + "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", @@ -6296,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", @@ -6430,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", @@ -6457,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", @@ -6533,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", @@ -6574,6 +6875,14 @@ "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.13.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", @@ -6645,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", @@ -6727,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", @@ -6748,6 +7086,29 @@ "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", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -6769,6 +7130,11 @@ "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=", "dev": true }, + "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", @@ -6838,6 +7204,16 @@ "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", "optional": true }, + "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", diff --git a/cloud/functions/package.json b/cloud/functions/package.json index 1d888c6d5..f9709cb7b 100644 --- a/cloud/functions/package.json +++ b/cloud/functions/package.json @@ -12,10 +12,12 @@ }, "main": "lib/index.js", "dependencies": { + "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.11", @@ -25,7 +27,9 @@ "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", diff --git a/cloud/functions/src/index.ts b/cloud/functions/src/index.ts index cf3b31c31..c6b7d3958 100644 --- a/cloud/functions/src/index.ts +++ b/cloud/functions/src/index.ts @@ -2,6 +2,8 @@ import * as functions from 'firebase-functions'; import 'firebase-functions/lib/logger/compat'; +import * as cors from 'cors'; + import * as admin from 'firebase-admin'; const app: admin.app.App = admin.initializeApp(); app.firestore().settings({timestampsInSnapshots: true}); @@ -16,6 +18,8 @@ const runtimeOpts = { memory: '1GB', }; +const corsHandler = cors({origin: true}); + export const watchDeckUpdate = functions.runWith(runtimeOpts).firestore.document('decks/{deckId}').onUpdate(applyWatchDeckUpdate); export const watchDeckDelete = functions.firestore.document('decks/{deckId}').onDelete(applyWatchDeckDelete); @@ -28,4 +32,8 @@ export const watchUserDelete = functions.auth.user().onDelete(applyWatchUserDele export const watchUserCreate = functions.auth.user().onCreate(applyWatchUserCreate); -export const publish = functions.runWith(runtimeOpts).https.onRequest(publishDeck); +export const publish = functions.runWith(runtimeOpts).https.onRequest((request: functions.Request, response: functions.Response) => { + corsHandler(request, response, async () => { + await publishDeck(request, response); + }); +}); diff --git a/cloud/functions/src/model/api/api.deck.ts b/cloud/functions/src/model/api/api.deck.ts new file mode 100644 index 000000000..4eaaf2e5b --- /dev/null +++ b/cloud/functions/src/model/api/api.deck.ts @@ -0,0 +1,15 @@ +import {DeckAttributes} from '../deck'; +import {ApiSlide} from './api.slide'; + +export interface ApiDeck { + id?: string; + slides: ApiSlide[]; + name: string; + description: string; + owner_id: string; + attributes?: DeckAttributes; + background?: string; + header?: string; + footer?: string; + head_extra?: string; +} diff --git a/cloud/functions/src/model/api/api.slide.ts b/cloud/functions/src/model/api/api.slide.ts new file mode 100644 index 000000000..d72dd1d25 --- /dev/null +++ b/cloud/functions/src/model/api/api.slide.ts @@ -0,0 +1,7 @@ +import {SlideAttributes, SlideTemplate} from '../slide'; + +export interface ApiSlide { + content?: string; + template: SlideTemplate; + attributes?: SlideAttributes; +} diff --git a/cloud/functions/src/model/deck.ts b/cloud/functions/src/model/deck.ts index 7768f7c48..2c78dcc45 100644 --- a/cloud/functions/src/model/deck.ts +++ b/cloud/functions/src/model/deck.ts @@ -1,4 +1,5 @@ import {firestore} from 'firebase-admin'; + import {UserSocial} from './user'; export interface DeckMetaAuthor { @@ -20,13 +21,15 @@ 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 +42,8 @@ export interface DeckData { attributes?: DeckAttributes; background?: string; + header?: string; + footer?: string; owner_id: string; diff --git a/cloud/functions/src/model/slide.ts b/cloud/functions/src/model/slide.ts index f391c89ed..b5e642a05 100644 --- a/cloud/functions/src/model/slide.ts +++ b/cloud/functions/src/model/slide.ts @@ -1,8 +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; - // For simplicity reason cast to any as we don't want to redefine them as in studio - data: any; + id: string; + ref: firestore.DocumentReference; + data: SlideData; } diff --git a/cloud/functions/src/request/publish/publish-deck.ts b/cloud/functions/src/request/publish/publish-deck.ts index 4d5f04d97..e3fb3404f 100644 --- a/cloud/functions/src/request/publish/publish-deck.ts +++ b/cloud/functions/src/request/publish/publish-deck.ts @@ -1,5 +1,10 @@ import * as functions from 'firebase-functions'; +import {Deck} from '../../model/deck'; +import {ApiDeck} from '../../model/api/api.deck'; + +import {findDeck} from '../../utils/deck-utils'; +import {convertDeck} from '../utils/convert-deck-utils'; import {verifyToken} from '../utils/request-utils'; export async function publishDeck(request: functions.Request, response: functions.Response) { @@ -13,6 +18,28 @@ export async function publishDeck(request: functions.Request, response: function } try { + const deckId: string = request.body.deckId; + + if (!deckId) { + response.status(500).json({ + error: 'No deck information provided', + }); + return; + } + + const deck: Deck = await findDeck(deckId); + + if (!deck) { + response.status(500).json({ + error: 'No matching deck', + }); + return; + } + + const apiDeck: ApiDeck = await convertDeck(deck); + + console.log('API DECK', apiDeck); + response.json({ result: 'success', }); 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..c11357e90 --- /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/deck'; +import {ApiDeck} from '../../model/api/api.deck'; +import {ApiSlide} from '../../model/api/api.slide'; +import {Slide, SlideAttributes, SlideTemplate} from '../../model/slide'; + +import {findSlide} from '../../utils/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..6ebb615db --- /dev/null +++ b/cloud/functions/src/request/utils/google-fonts.utils.ts @@ -0,0 +1,143 @@ +import {Deck} from '../../model/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/utils/deck-utils.ts b/cloud/functions/src/utils/deck-utils.ts new file mode 100644 index 000000000..836361393 --- /dev/null +++ b/cloud/functions/src/utils/deck-utils.ts @@ -0,0 +1,26 @@ +import * as admin from 'firebase-admin'; + +import {Deck, DeckData} from '../model/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); + } + }); +} diff --git a/cloud/functions/src/utils/slide-utils.ts b/cloud/functions/src/utils/slide-utils.ts new file mode 100644 index 000000000..d2bb179cf --- /dev/null +++ b/cloud/functions/src/utils/slide-utils.ts @@ -0,0 +1,24 @@ +import * as admin from 'firebase-admin'; + +import {Slide, SlideData} from '../model/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/watch/clone/utils/clone-slides-utils.ts b/cloud/functions/src/watch/clone/utils/clone-slides-utils.ts index 972dcdf8c..a1ed04c0d 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 {Deck} from '../../../model/deck'; + +import {findSlide} from '../../../utils/slide-utils'; +import {findDeck} from '../../../utils/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/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/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/editor/publish/publish.service.tsx b/studio/src/app/services/editor/publish/publish.service.tsx index 4f9776146..a6d367096 100644 --- a/studio/src/app/services/editor/publish/publish.service.tsx +++ b/studio/src/app/services/editor/publish/publish.service.tsx @@ -1,5 +1,6 @@ import * as firebase from 'firebase/app'; import 'firebase/firestore'; +import 'firebase/auth'; import deckStore from '../../../stores/deck.store'; import publishStore from '../../../stores/publish.store'; @@ -7,23 +8,19 @@ import userStore from '../../../stores/user.store'; import {Deck, DeckMetaAuthor} from '../../../models/data/deck'; import {ApiDeck} from '../../../models/api/api.deck'; -import {Slide, SlideAttributes, SlideTemplate} from '../../../models/data/slide'; 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; @@ -31,17 +28,11 @@ export class 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() { @@ -71,45 +62,81 @@ export class PublishService { 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); + // TODO + // 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); + + await this.delayUpdateMeta(deckStore.state.deck, 'https://deckdeckgo.com/todo', description, tags, github, true); + + await this.publishDeck(deckStore.state.deck); + + // TODO + + resolve('https://deckdeckgo.com/todo'); + } catch (err) { + this.progressComplete(); + reject(err); + } + }); + } - if (!apiDeckPublish || !apiDeckPublish.id || !apiDeckPublish.url) { - this.progressComplete(); - reject('Publish failed'); + private async publishDeck(deck: Deck): Promise { + return new Promise(async (resolve, reject) => { + try { + 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, + }), + }); + + if (!rawResponse || !rawResponse.ok) { + reject('Something went wrong while publishing the deck'); 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); + resolve(); } catch (err) { - this.progressComplete(); reject(err); } }); } - private publishDeck(deck: Deck, apiDeck: ApiDeck): Promise { + publishDeckApi(deck: Deck, apiDeck: ApiDeck): Promise { return new Promise(async (resolve, reject) => { try { const apiDeckPublish: ApiPresentation = await this.createOrUpdatePublish(deck, apiDeck); @@ -129,202 +156,6 @@ export class PublishService { } } - 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); - } 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; - } - - 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([]); - 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 { diff --git a/studio/src/app/stores/auth.store.ts b/studio/src/app/stores/auth.store.ts index 89ee33441..6b7c6b886 100644 --- a/studio/src/app/stores/auth.store.ts +++ b/studio/src/app/stores/auth.store.ts @@ -20,7 +20,10 @@ const {state, onChange, reset} = createStore({ onChange('authUser', (authUser: AuthUser) => { state.anonymous = authUser ? authUser.anonymous : true; + + // TODO: Remove bearer from store state.bearer = `Bearer ${authUser ? authUser.token : ''}`; + state.loggedIn = authUser && !authUser.anonymous; state.gitHub = authUser ? authUser.gitHub : false; }); 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 @@ - + From 15319b0ca99ea35044d9df3f18fbe02ef215dc84 Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Fri, 4 Sep 2020 10:37:56 +0200 Subject: [PATCH 05/30] refactor: move split publish function --- cloud/functions/src/index.ts | 12 +---- cloud/functions/src/request/publish.ts | 33 +++++++++++++ .../src/request/publish/publish-deck.ts | 46 ++++--------------- 3 files changed, 45 insertions(+), 46 deletions(-) create mode 100644 cloud/functions/src/request/publish.ts diff --git a/cloud/functions/src/index.ts b/cloud/functions/src/index.ts index c6b7d3958..0e1cbbf44 100644 --- a/cloud/functions/src/index.ts +++ b/cloud/functions/src/index.ts @@ -2,8 +2,6 @@ import * as functions from 'firebase-functions'; import 'firebase-functions/lib/logger/compat'; -import * as cors from 'cors'; - import * as admin from 'firebase-admin'; const app: admin.app.App = admin.initializeApp(); app.firestore().settings({timestampsInSnapshots: true}); @@ -11,15 +9,13 @@ app.firestore().settings({timestampsInSnapshots: true}); import {applyWatchDeckCreate, applyWatchDeckDelete, applyWatchDeckUpdate} from './watch/watch-deck'; import {applyWatchUserCreate, applyWatchUserDelete, applyWatchUserUpdate} from './watch/watch-user'; -import {publishDeck} from './request/publish/publish-deck'; +import {publishJob} from './request/publish'; const runtimeOpts = { timeoutSeconds: 120, memory: '1GB', }; -const corsHandler = cors({origin: true}); - export const watchDeckUpdate = functions.runWith(runtimeOpts).firestore.document('decks/{deckId}').onUpdate(applyWatchDeckUpdate); export const watchDeckDelete = functions.firestore.document('decks/{deckId}').onDelete(applyWatchDeckDelete); @@ -32,8 +28,4 @@ export const watchUserDelete = functions.auth.user().onDelete(applyWatchUserDele export const watchUserCreate = functions.auth.user().onCreate(applyWatchUserCreate); -export const publish = functions.runWith(runtimeOpts).https.onRequest((request: functions.Request, response: functions.Response) => { - corsHandler(request, response, async () => { - await publishDeck(request, response); - }); -}); +export const publish = functions.runWith(runtimeOpts).https.onRequest(publishJob); diff --git a/cloud/functions/src/request/publish.ts b/cloud/functions/src/request/publish.ts new file mode 100644 index 000000000..103c55bab --- /dev/null +++ b/cloud/functions/src/request/publish.ts @@ -0,0 +1,33 @@ +import * as functions from 'firebase-functions'; + +import * as cors from 'cors'; + +import {verifyToken} from './utils/request-utils'; +import {publishDeck} from './publish/publish-deck'; + +export async function publishJob(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 { + await publishDeck(request); + + response.json({ + result: 'success', + }); + } catch (err) { + response.status(500).json({ + error: err, + }); + } + }); +} diff --git a/cloud/functions/src/request/publish/publish-deck.ts b/cloud/functions/src/request/publish/publish-deck.ts index e3fb3404f..350c20ac0 100644 --- a/cloud/functions/src/request/publish/publish-deck.ts +++ b/cloud/functions/src/request/publish/publish-deck.ts @@ -5,47 +5,21 @@ import {ApiDeck} from '../../model/api/api.deck'; import {findDeck} from '../../utils/deck-utils'; import {convertDeck} from '../utils/convert-deck-utils'; -import {verifyToken} from '../utils/request-utils'; -export async function publishDeck(request: functions.Request, response: functions.Response) { - const validToken: boolean = await verifyToken(request); +export async function publishDeck(request: functions.Request) { + const deckId: string = request.body.deckId; - if (!validToken) { - response.status(400).json({ - error: 'Not Authorized', - }); - return; + if (!deckId) { + throw new Error('No deck information provided'); } - try { - const deckId: string = request.body.deckId; + const deck: Deck = await findDeck(deckId); - if (!deckId) { - response.status(500).json({ - error: 'No deck information provided', - }); - return; - } - - const deck: Deck = await findDeck(deckId); - - if (!deck) { - response.status(500).json({ - error: 'No matching deck', - }); - return; - } - - const apiDeck: ApiDeck = await convertDeck(deck); + if (!deck) { + throw new Error('No matching deck'); + } - console.log('API DECK', apiDeck); + const apiDeck: ApiDeck = await convertDeck(deck); - response.json({ - result: 'success', - }); - } catch (err) { - response.status(500).json({ - error: err, - }); - } + console.log('API DECK', apiDeck); } From 30162c75b1bc2e1a0ec772f049deb54441fed2dd Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Fri, 4 Sep 2020 10:39:15 +0200 Subject: [PATCH 06/30] refactor: add try catch --- .../src/request/publish/publish-deck.ts | 40 +++++++++++-------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/cloud/functions/src/request/publish/publish-deck.ts b/cloud/functions/src/request/publish/publish-deck.ts index 350c20ac0..e951543ce 100644 --- a/cloud/functions/src/request/publish/publish-deck.ts +++ b/cloud/functions/src/request/publish/publish-deck.ts @@ -6,20 +6,28 @@ import {ApiDeck} from '../../model/api/api.deck'; import {findDeck} from '../../utils/deck-utils'; import {convertDeck} from '../utils/convert-deck-utils'; -export async function publishDeck(request: functions.Request) { - const deckId: string = request.body.deckId; - - if (!deckId) { - throw new Error('No deck information provided'); - } - - const deck: Deck = await findDeck(deckId); - - if (!deck) { - throw new Error('No matching deck'); - } - - const apiDeck: ApiDeck = await convertDeck(deck); - - console.log('API DECK', apiDeck); +export function publishDeck(request: functions.Request): Promise { + return new Promise(async (resolve, reject) => { + try { + const deckId: string = request.body.deckId; + + if (!deckId) { + reject('No deck information provided'); + return; + } + + const deck: Deck = await findDeck(deckId); + + if (!deck) { + reject('No matching deck'); + return; + } + + const apiDeck: ApiDeck = await convertDeck(deck); + + console.log('API DECK', apiDeck); + } catch (err) { + reject(err); + } + }); } From dca63823adf83f45e3b9c850051abb84e20d934c Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Fri, 4 Sep 2020 10:59:40 +0200 Subject: [PATCH 07/30] feat: publish to API --- .../src/model/api/api.presentation.ts | 4 + cloud/functions/src/request/publish.ts | 7 +- .../src/request/publish/publish-deck.ts | 28 ++++--- .../functions/src/request/utils/api-utils.ts | 80 +++++++++++++++++++ .../src/request/utils/convert-deck-utils.ts | 2 +- ...e-fonts.utils.ts => google-fonts-utils.ts} | 0 .../src/request/utils/request-utils.ts | 20 +++-- 7 files changed, 124 insertions(+), 17 deletions(-) create mode 100644 cloud/functions/src/model/api/api.presentation.ts create mode 100644 cloud/functions/src/request/utils/api-utils.ts rename cloud/functions/src/request/utils/{google-fonts.utils.ts => google-fonts-utils.ts} (100%) diff --git a/cloud/functions/src/model/api/api.presentation.ts b/cloud/functions/src/model/api/api.presentation.ts new file mode 100644 index 000000000..d42e93318 --- /dev/null +++ b/cloud/functions/src/model/api/api.presentation.ts @@ -0,0 +1,4 @@ +export interface ApiPresentation { + id: string; + url: string; +} diff --git a/cloud/functions/src/request/publish.ts b/cloud/functions/src/request/publish.ts index 103c55bab..f13614601 100644 --- a/cloud/functions/src/request/publish.ts +++ b/cloud/functions/src/request/publish.ts @@ -2,7 +2,7 @@ import * as functions from 'firebase-functions'; import * as cors from 'cors'; -import {verifyToken} from './utils/request-utils'; +import {geToken, verifyToken} from './utils/request-utils'; import {publishDeck} from './publish/publish-deck'; export async function publishJob(request: functions.Request, response: functions.Response) { @@ -19,7 +19,10 @@ export async function publishJob(request: functions.Request, response: functions } try { - await publishDeck(request); + const token: string | undefined = await geToken(request); + const deckId: string | undefined = request.body.deckId; + + await publishDeck(deckId, token); response.json({ result: 'success', diff --git a/cloud/functions/src/request/publish/publish-deck.ts b/cloud/functions/src/request/publish/publish-deck.ts index e951543ce..ff2e30ce6 100644 --- a/cloud/functions/src/request/publish/publish-deck.ts +++ b/cloud/functions/src/request/publish/publish-deck.ts @@ -1,31 +1,41 @@ -import * as functions from 'firebase-functions'; - import {Deck} from '../../model/deck'; import {ApiDeck} from '../../model/api/api.deck'; +import {ApiPresentation} from '../../model/api/api.presentation'; import {findDeck} from '../../utils/deck-utils'; import {convertDeck} from '../utils/convert-deck-utils'; +import {publishDeckApi} from '../utils/api-utils'; -export function publishDeck(request: functions.Request): Promise { - return new Promise(async (resolve, reject) => { +export function publishDeck(deckId: string | undefined, token: string | undefined): Promise { + return new Promise(async (resolve, reject) => { try { - const deckId: string = request.body.deckId; - if (!deckId) { - reject('No deck information provided'); + reject('No deck information provided.'); + return; + } + + if (!token) { + reject('No token provided.'); return; } const deck: Deck = await findDeck(deckId); if (!deck) { - reject('No matching deck'); + reject('No matching deck.'); return; } const apiDeck: ApiDeck = await convertDeck(deck); - console.log('API DECK', apiDeck); + if (!apiDeck) { + reject('No converted deck.'); + return; + } + + const apiDeckPublish: ApiPresentation = await publishDeckApi(deck, apiDeck, token); + + resolve(apiDeckPublish.url); } 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..f1b654aee --- /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/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 index c11357e90..d42c8d9c5 100644 --- a/cloud/functions/src/request/utils/convert-deck-utils.ts +++ b/cloud/functions/src/request/utils/convert-deck-utils.ts @@ -6,7 +6,7 @@ import {ApiSlide} from '../../model/api/api.slide'; import {Slide, SlideAttributes, SlideTemplate} from '../../model/slide'; import {findSlide} from '../../utils/slide-utils'; -import {getGoogleFontScript} from './google-fonts.utils'; +import {getGoogleFontScript} from './google-fonts-utils'; export function convertDeck(deck: Deck): Promise { return new Promise(async (resolve, reject) => { diff --git a/cloud/functions/src/request/utils/google-fonts.utils.ts b/cloud/functions/src/request/utils/google-fonts-utils.ts similarity index 100% rename from cloud/functions/src/request/utils/google-fonts.utils.ts rename to cloud/functions/src/request/utils/google-fonts-utils.ts diff --git a/cloud/functions/src/request/utils/request-utils.ts b/cloud/functions/src/request/utils/request-utils.ts index 89a42adbb..7c5ee9399 100644 --- a/cloud/functions/src/request/utils/request-utils.ts +++ b/cloud/functions/src/request/utils/request-utils.ts @@ -2,12 +2,12 @@ import * as admin from 'firebase-admin'; import * as functions from 'firebase-functions'; export async function verifyToken(request: functions.Request, acceptAnonymous: boolean = false): Promise { - if (!request.headers.authorization) { - return false; - } - try { - const token: string = request.headers.authorization.replace(/^Bearer\s/, ''); + const token: string | undefined = await geToken(request); + + if (!token) { + return false; + } const payload: admin.auth.DecodedIdToken = await admin.auth().verifyIdToken(token); @@ -16,3 +16,13 @@ export async function verifyToken(request: functions.Request, acceptAnonymous: b 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; +} From cee973bf3f9fdecf5e744815b8c036ed46f4fa87 Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Fri, 4 Sep 2020 11:01:06 +0200 Subject: [PATCH 08/30] feat: return API deck info --- cloud/functions/src/request/publish.ts | 8 ++++---- cloud/functions/src/request/publish/publish-deck.ts | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cloud/functions/src/request/publish.ts b/cloud/functions/src/request/publish.ts index f13614601..10781cc1b 100644 --- a/cloud/functions/src/request/publish.ts +++ b/cloud/functions/src/request/publish.ts @@ -5,6 +5,8 @@ import * as cors from 'cors'; import {geToken, verifyToken} from './utils/request-utils'; import {publishDeck} from './publish/publish-deck'; +import {ApiPresentation} from '../model/api/api.presentation'; + export async function publishJob(request: functions.Request, response: functions.Response) { const corsHandler = cors({origin: true}); @@ -22,11 +24,9 @@ export async function publishJob(request: functions.Request, response: functions const token: string | undefined = await geToken(request); const deckId: string | undefined = request.body.deckId; - await publishDeck(deckId, token); + const apiDeckPublish: ApiPresentation = await publishDeck(deckId, token); - response.json({ - result: 'success', - }); + response.json(apiDeckPublish); } catch (err) { response.status(500).json({ error: err, diff --git a/cloud/functions/src/request/publish/publish-deck.ts b/cloud/functions/src/request/publish/publish-deck.ts index ff2e30ce6..0d62d038c 100644 --- a/cloud/functions/src/request/publish/publish-deck.ts +++ b/cloud/functions/src/request/publish/publish-deck.ts @@ -6,8 +6,8 @@ import {findDeck} from '../../utils/deck-utils'; import {convertDeck} from '../utils/convert-deck-utils'; import {publishDeckApi} from '../utils/api-utils'; -export function publishDeck(deckId: string | undefined, token: string | undefined): Promise { - return new Promise(async (resolve, reject) => { +export function publishDeck(deckId: string | undefined, token: string | undefined): Promise { + return new Promise(async (resolve, reject) => { try { if (!deckId) { reject('No deck information provided.'); @@ -35,7 +35,7 @@ export function publishDeck(deckId: string | undefined, token: string | undefine const apiDeckPublish: ApiPresentation = await publishDeckApi(deck, apiDeck, token); - resolve(apiDeckPublish.url); + resolve(apiDeckPublish); } catch (err) { reject(err); } From 102290c063e7d43553c80c26fbaa40d76ab65f7d Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Fri, 4 Sep 2020 11:26:50 +0200 Subject: [PATCH 09/30] feat: update deck after publish --- .../src/request/publish/publish-deck.ts | 53 ++++++- cloud/functions/src/utils/deck-utils.ts | 23 +++ cloud/functions/src/utils/resources.ts | 22 +-- .../src/watch/info/info-deck-publish.ts | 135 +++++++++--------- .../screenshot/generate-deck-screenshot.ts | 4 +- .../editor/publish/publish.service.tsx | 50 +------ studio/src/app/utils/core/resources.tsx | 1 - 7 files changed, 160 insertions(+), 128 deletions(-) diff --git a/cloud/functions/src/request/publish/publish-deck.ts b/cloud/functions/src/request/publish/publish-deck.ts index 0d62d038c..b382e575c 100644 --- a/cloud/functions/src/request/publish/publish-deck.ts +++ b/cloud/functions/src/request/publish/publish-deck.ts @@ -1,10 +1,12 @@ -import {Deck} from '../../model/deck'; +import {Deck, DeckData, DeckMeta} from '../../model/deck'; import {ApiDeck} from '../../model/api/api.deck'; import {ApiPresentation} from '../../model/api/api.presentation'; -import {findDeck} from '../../utils/deck-utils'; +import {findDeck, updateDeck} from '../../utils/deck-utils'; import {convertDeck} from '../utils/convert-deck-utils'; import {publishDeckApi} from '../utils/api-utils'; +import {Resources} from '../../utils/resources'; +import * as admin from 'firebase-admin'; export function publishDeck(deckId: string | undefined, token: string | undefined): Promise { return new Promise(async (resolve, reject) => { @@ -21,11 +23,16 @@ export function publishDeck(deckId: string | undefined, token: string | undefine const deck: Deck = await findDeck(deckId); - if (!deck) { + if (!deck || !deck.data) { reject('No matching deck.'); return; } + if (!deck.data.meta) { + reject('Deck has no meta.'); + return; + } + const apiDeck: ApiDeck = await convertDeck(deck); if (!apiDeck) { @@ -35,9 +42,49 @@ export function publishDeck(deckId: string | undefined, token: string | undefine 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); + resolve(apiDeckPublish); } catch (err) { 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/utils/deck-utils.ts b/cloud/functions/src/utils/deck-utils.ts index 836361393..e75c464d7 100644 --- a/cloud/functions/src/utils/deck-utils.ts +++ b/cloud/functions/src/utils/deck-utils.ts @@ -1,6 +1,7 @@ import * as admin from 'firebase-admin'; import {Deck, DeckData} from '../model/deck'; +import {DeployData, DeployGitHubRepo} from '../model/deploy'; export function findDeck(deckId: string): Promise { return new Promise(async (resolve, reject) => { @@ -24,3 +25,25 @@ export function findDeck(deckId: string): Promise { } }); } + +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/resources.ts b/cloud/functions/src/utils/resources.ts index f6403cfc2..a98dee95b 100644 --- a/cloud/functions/src/utils/resources.ts +++ b/cloud/functions/src/utils/resources.ts @@ -1,13 +1,13 @@ export class Resources { - - static get Constants(): any { - return { - PRESENTATION: { - URL: 'https://beta.deckdeckgo.io', - FOLDER: 'presentations', - IMAGE: 'deckdeckgo.png' - } - }; - } - + static get Constants(): any { + return { + PRESENTATION: { + FOLDER: 'presentations', + IMAGE: 'deckdeckgo.png', + }, + FEED: { + MIN_SLIDES: 3, + }, + }; + } } diff --git a/cloud/functions/src/watch/info/info-deck-publish.ts b/cloud/functions/src/watch/info/info-deck-publish.ts index 0219e5ea7..42f6876ae 100644 --- a/cloud/functions/src/watch/info/info-deck-publish.ts +++ b/cloud/functions/src/watch/info/info-deck-publish.ts @@ -5,87 +5,88 @@ import * as nodemailer from 'nodemailer'; import {DeckData} from '../../model/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 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/screenshot/generate-deck-screenshot.ts b/cloud/functions/src/watch/screenshot/generate-deck-screenshot.ts index dc780a91a..96d0200a5 100644 --- a/cloud/functions/src/watch/screenshot/generate-deck-screenshot.ts +++ b/cloud/functions/src/watch/screenshot/generate-deck-screenshot.ts @@ -10,6 +10,7 @@ import {Resources} from '../../utils/resources'; import {DeckData} from '../../model/deck'; import {isDeckPublished} from './utils/update-deck'; +import * as functions from 'firebase-functions'; export async function generateDeckScreenshot(change: Change) { const newValue: DeckData = change.after.data() as DeckData; @@ -57,7 +58,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/studio/src/app/services/editor/publish/publish.service.tsx b/studio/src/app/services/editor/publish/publish.service.tsx index a6d367096..e792f412c 100644 --- a/studio/src/app/services/editor/publish/publish.service.tsx +++ b/studio/src/app/services/editor/publish/publish.service.tsx @@ -7,31 +7,22 @@ import publishStore from '../../../stores/publish.store'; import userStore from '../../../stores/user.store'; import {Deck, DeckMetaAuthor} from '../../../models/data/deck'; -import {ApiDeck} from '../../../models/api/api.deck'; import {Resources} from '../../../utils/core/resources'; -import {ApiPresentation} from '../../../models/api/api.presentation'; import {UserSocial} from '../../../models/data/user'; import {DeckService} from '../../data/deck/deck.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 {EnvironmentFirebaseConfig} from '../../core/environment/environment-config'; export class PublishService { private static instance: PublishService; - private apiPresentationService: ApiPresentationService; - private deckService: DeckService; private constructor() { - this.apiPresentationService = ApiPresentationFactoryService.getInstance(); - this.deckService = DeckService.getInstance(); } @@ -62,20 +53,6 @@ export class PublishService { return; } - // TODO - // 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) { @@ -136,26 +113,6 @@ export class PublishService { }); } - publishDeckApi(deck: Deck, apiDeck: ApiDeck): Promise { - return new Promise(async (resolve, reject) => { - try { - const apiDeckPublish: ApiPresentation = await this.createOrUpdatePublish(deck, apiDeck); - - resolve(apiDeckPublish); - } catch (err) { - reject(err); - } - }); - } - - 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); - } - } - // 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 { @@ -220,12 +177,12 @@ export class PublishService { if (!deck.data.meta) { deck.data.meta = { - title: deck.data.name, + title: deck.data.name, // here pathname: url.pathname, published: true, published_at: now, feed: feed, - github: github, + github: github, // here updated_at: now, }; } else { @@ -236,18 +193,21 @@ export class PublishService { deck.data.meta.github = github; } + // here if (description && description !== undefined && description !== '') { deck.data.meta.description = description; } else { deck.data.meta.description = firebase.firestore.FieldValue.delete(); } + // here if (!tags || tags.length <= 0) { deck.data.meta.tags = firebase.firestore.FieldValue.delete(); } else { deck.data.meta.tags = tags; } + // here if (userStore.state.user && userStore.state.user.data && userStore.state.user.data.name) { if (!deck.data.meta.author) { deck.data.meta.author = { 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, From e279eb65e0f05c491728599d9cdad5b6b02df08e Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Fri, 4 Sep 2020 11:45:35 +0200 Subject: [PATCH 10/30] feat: publish deck in the cloud --- studio/src/app/models/data/deck.tsx | 8 +- .../editor/publish/publish.service.tsx | 86 +++++++------------ 2 files changed, 34 insertions(+), 60 deletions(-) diff --git a/studio/src/app/models/data/deck.tsx b/studio/src/app/models/data/deck.tsx index 4619f4dff..a343958f2 100644 --- a/studio/src/app/models/data/deck.tsx +++ b/studio/src/app/models/data/deck.tsx @@ -12,14 +12,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; + feed?: boolean; github?: boolean; updated_at: firebase.firestore.Timestamp; diff --git a/studio/src/app/services/editor/publish/publish.service.tsx b/studio/src/app/services/editor/publish/publish.service.tsx index e792f412c..40491388a 100644 --- a/studio/src/app/services/editor/publish/publish.service.tsx +++ b/studio/src/app/services/editor/publish/publish.service.tsx @@ -8,8 +8,6 @@ import userStore from '../../../stores/user.store'; import {Deck, DeckMetaAuthor} from '../../../models/data/deck'; -import {Resources} from '../../../utils/core/resources'; - import {UserSocial} from '../../../models/data/user'; import {DeckService} from '../../data/deck/deck.service'; @@ -41,7 +39,6 @@ export class PublishService { 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); @@ -53,26 +50,15 @@ export class PublishService { return; } - // - // 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); + await this.updateDeckMeta(description, tags, github); - await this.delayUpdateMeta(deckStore.state.deck, 'https://deckdeckgo.com/todo', description, tags, github, true); + this.progress(0.25); await this.publishDeck(deckStore.state.deck); - // TODO + this.progress(0.75); + + await this.delayRefreshDeck(); resolve('https://deckdeckgo.com/todo'); } catch (err) { @@ -115,30 +101,33 @@ export class PublishService { // 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); + private delayRefreshDeck(): Promise { + return new Promise(async (resolve) => { + const currentDeck: Deck = {...deckStore.state.deck}; - setTimeout( - async () => { - await this.updateDeckMeta(deck, publishedUrl, description, tags, github); + await this.refreshDeck(currentDeck.id); - this.progress(0.95); + this.progress(0.9); - await this.refreshDeck(deck.id); + const newApiId: boolean = currentDeck.data.api_id !== deckStore.state.deck.data.api_id; - this.progressComplete(); + const interval = newApiId + ? setInterval(() => { + this.progress(publishStore.state.progress + 0.01); + }, 7000 / 9) + : undefined; - setTimeout(() => { - resolve(); - }, 500); - }, - delay ? 3500 : 0 - ); + setTimeout( + () => { + if (interval) { + clearInterval(interval); + } + + this.progressComplete(); + + resolve(); }, - delay ? 3500 : 0 + newApiId ? 7000 : 0 ); }); } @@ -157,57 +146,42 @@ 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, // here - pathname: url.pathname, - published: true, - published_at: now, - feed: feed, - github: github, // here + title: deck.data.name, + 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; } - // here if (description && description !== undefined && description !== '') { deck.data.meta.description = description; } else { deck.data.meta.description = firebase.firestore.FieldValue.delete(); } - // here if (!tags || tags.length <= 0) { deck.data.meta.tags = firebase.firestore.FieldValue.delete(); } else { deck.data.meta.tags = tags; } - // here if (userStore.state.user && userStore.state.user.data && userStore.state.user.data.name) { if (!deck.data.meta.author) { deck.data.meta.author = { From 55120aef91709e64e7f3d2372aa911d02521cbdc Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Fri, 4 Sep 2020 11:47:49 +0200 Subject: [PATCH 11/30] feat: remove api presentation from studio, job is done in the cloud --- studio/src/app/models/api/api.deck.tsx | 15 ------- .../src/app/models/api/api.presentation.tsx | 4 -- studio/src/app/models/api/api.slide.tsx | 7 --- .../api.presentation.factory.service.tsx | 18 -------- .../api.presentation.mock.service.tsx | 22 ---------- .../api.presentation.prod.service.tsx | 43 ------------------- .../presentation/api.presentation.service.tsx | 24 ----------- 7 files changed, 133 deletions(-) delete mode 100644 studio/src/app/models/api/api.deck.tsx delete mode 100644 studio/src/app/models/api/api.presentation.tsx delete mode 100644 studio/src/app/models/api/api.slide.tsx delete mode 100644 studio/src/app/services/api/presentation/api.presentation.factory.service.tsx delete mode 100644 studio/src/app/services/api/presentation/api.presentation.mock.service.tsx delete mode 100644 studio/src/app/services/api/presentation/api.presentation.prod.service.tsx delete mode 100644 studio/src/app/services/api/presentation/api.presentation.service.tsx diff --git a/studio/src/app/models/api/api.deck.tsx b/studio/src/app/models/api/api.deck.tsx deleted file mode 100644 index 14ec989b7..000000000 --- a/studio/src/app/models/api/api.deck.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import {DeckAttributes} from '../data/deck'; -import {ApiSlide} from './api.slide'; - -export interface ApiDeck { - id?: string; - slides: ApiSlide[]; - name: string; - description: string; - owner_id: string; - attributes?: DeckAttributes; - background?: string; - header?: string; - footer?: string; - head_extra?: string; -} diff --git a/studio/src/app/models/api/api.presentation.tsx b/studio/src/app/models/api/api.presentation.tsx deleted file mode 100644 index d42e93318..000000000 --- a/studio/src/app/models/api/api.presentation.tsx +++ /dev/null @@ -1,4 +0,0 @@ -export interface ApiPresentation { - id: string; - url: string; -} diff --git a/studio/src/app/models/api/api.slide.tsx b/studio/src/app/models/api/api.slide.tsx deleted file mode 100644 index 8172dcb34..000000000 --- a/studio/src/app/models/api/api.slide.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import {SlideAttributes, SlideTemplate} from '../data/slide'; - -export interface ApiSlide { - content?: string; - template: SlideTemplate; - attributes?: SlideAttributes; -} 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); - } -} From aaf3fe3985f3a39d00b69b9d729daaa259c7c8c5 Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Fri, 4 Sep 2020 11:48:31 +0200 Subject: [PATCH 12/30] feat: add todo --- cloud/functions/src/request/publish.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/cloud/functions/src/request/publish.ts b/cloud/functions/src/request/publish.ts index 10781cc1b..e1d0f9c9f 100644 --- a/cloud/functions/src/request/publish.ts +++ b/cloud/functions/src/request/publish.ts @@ -24,6 +24,7 @@ export async function publishJob(request: functions.Request, response: functions const token: string | undefined = await geToken(request); const deckId: string | undefined = request.body.deckId; + // TODO: Don't publish here but trigger the functions const apiDeckPublish: ApiPresentation = await publishDeck(deckId, token); response.json(apiDeckPublish); From b66fc5c4e084bc3eb1f20e9ee897f908dfc66c6f Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Fri, 4 Sep 2020 11:53:49 +0200 Subject: [PATCH 13/30] fix: build --- cloud/functions/src/utils/deck-utils.ts | 1 - .../src/watch/screenshot/generate-deck-screenshot.ts | 5 ++--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/cloud/functions/src/utils/deck-utils.ts b/cloud/functions/src/utils/deck-utils.ts index e75c464d7..57c721efe 100644 --- a/cloud/functions/src/utils/deck-utils.ts +++ b/cloud/functions/src/utils/deck-utils.ts @@ -1,7 +1,6 @@ import * as admin from 'firebase-admin'; import {Deck, DeckData} from '../model/deck'; -import {DeployData, DeployGitHubRepo} from '../model/deploy'; export function findDeck(deckId: string): Promise { return new Promise(async (resolve, reject) => { diff --git a/cloud/functions/src/watch/screenshot/generate-deck-screenshot.ts b/cloud/functions/src/watch/screenshot/generate-deck-screenshot.ts index 96d0200a5..ffa468bc1 100644 --- a/cloud/functions/src/watch/screenshot/generate-deck-screenshot.ts +++ b/cloud/functions/src/watch/screenshot/generate-deck-screenshot.ts @@ -1,4 +1,4 @@ -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'; @@ -10,9 +10,8 @@ import {Resources} from '../../utils/resources'; import {DeckData} from '../../model/deck'; import {isDeckPublished} from './utils/update-deck'; -import * as functions from 'firebase-functions'; -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; From 926ed415fc6ec83bbf8a96f4faaf55cba1c43236 Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Fri, 4 Sep 2020 12:03:53 +0200 Subject: [PATCH 14/30] refactor: move files to packaces --- cloud/functions/src/model/api/api.deck.ts | 2 +- cloud/functions/src/model/api/api.slide.ts | 2 +- cloud/functions/src/model/{ => data}/deck.ts | 0 .../functions/src/model/{ => data}/deploy.ts | 0 cloud/functions/src/model/{ => data}/slide.ts | 0 cloud/functions/src/model/{ => data}/token.ts | 0 cloud/functions/src/model/{ => data}/user.ts | 0 .../src/request/publish/publish-deck.ts | 6 +- .../functions/src/request/utils/api-utils.ts | 2 +- .../src/request/utils/convert-deck-utils.ts | 6 +- .../src/request/utils/google-fonts-utils.ts | 2 +- .../src/utils/{ => data}/deck-utils.ts | 2 +- .../src/utils/{ => data}/slide-utils.ts | 2 +- .../src/utils/{ => resources}/resources.ts | 0 .../src/watch/clone/clone-deck-slides.ts | 40 +-- .../watch/clone/utils/clone-slides-utils.ts | 8 +- .../src/watch/delete/delete-deck-deploy.ts | 2 +- .../src/watch/delete/delete-decks-slides.ts | 247 +++++++++--------- .../src/watch/delete/delete-deploy.ts | 2 +- .../watch/delete/utils/delete-slides-utils.ts | 2 +- .../src/watch/github/publish-github.ts | 6 +- .../src/watch/github/utils/github-api.ts | 2 +- .../src/watch/github/utils/github-db.ts | 4 +- .../src/watch/github/utils/github-fs.ts | 2 +- .../src/watch/github/utils/github-utils.ts | 4 +- .../src/watch/info/info-deck-publish.ts | 2 +- .../src/watch/mailchimp/mailchimp-member.ts | 2 +- .../screenshot/generate-deck-screenshot.ts | 4 +- .../src/watch/screenshot/utils/update-deck.ts | 2 +- .../src/watch/update/update-deck-meta.ts | 4 +- 30 files changed, 177 insertions(+), 180 deletions(-) rename cloud/functions/src/model/{ => data}/deck.ts (100%) rename cloud/functions/src/model/{ => data}/deploy.ts (100%) rename cloud/functions/src/model/{ => data}/slide.ts (100%) rename cloud/functions/src/model/{ => data}/token.ts (100%) rename cloud/functions/src/model/{ => data}/user.ts (100%) rename cloud/functions/src/utils/{ => data}/deck-utils.ts (95%) rename cloud/functions/src/utils/{ => data}/slide-utils.ts (91%) rename cloud/functions/src/utils/{ => resources}/resources.ts (100%) diff --git a/cloud/functions/src/model/api/api.deck.ts b/cloud/functions/src/model/api/api.deck.ts index 4eaaf2e5b..14ec989b7 100644 --- a/cloud/functions/src/model/api/api.deck.ts +++ b/cloud/functions/src/model/api/api.deck.ts @@ -1,4 +1,4 @@ -import {DeckAttributes} from '../deck'; +import {DeckAttributes} from '../data/deck'; import {ApiSlide} from './api.slide'; export interface ApiDeck { diff --git a/cloud/functions/src/model/api/api.slide.ts b/cloud/functions/src/model/api/api.slide.ts index d72dd1d25..8172dcb34 100644 --- a/cloud/functions/src/model/api/api.slide.ts +++ b/cloud/functions/src/model/api/api.slide.ts @@ -1,4 +1,4 @@ -import {SlideAttributes, SlideTemplate} from '../slide'; +import {SlideAttributes, SlideTemplate} from '../data/slide'; export interface ApiSlide { content?: string; diff --git a/cloud/functions/src/model/deck.ts b/cloud/functions/src/model/data/deck.ts similarity index 100% rename from cloud/functions/src/model/deck.ts rename to cloud/functions/src/model/data/deck.ts diff --git a/cloud/functions/src/model/deploy.ts b/cloud/functions/src/model/data/deploy.ts similarity index 100% rename from cloud/functions/src/model/deploy.ts rename to cloud/functions/src/model/data/deploy.ts diff --git a/cloud/functions/src/model/slide.ts b/cloud/functions/src/model/data/slide.ts similarity index 100% rename from cloud/functions/src/model/slide.ts rename to cloud/functions/src/model/data/slide.ts 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/request/publish/publish-deck.ts b/cloud/functions/src/request/publish/publish-deck.ts index b382e575c..45017364d 100644 --- a/cloud/functions/src/request/publish/publish-deck.ts +++ b/cloud/functions/src/request/publish/publish-deck.ts @@ -1,11 +1,11 @@ -import {Deck, DeckData, DeckMeta} from '../../model/deck'; +import {Deck, DeckData, DeckMeta} from '../../model/data/deck'; import {ApiDeck} from '../../model/api/api.deck'; import {ApiPresentation} from '../../model/api/api.presentation'; -import {findDeck, updateDeck} from '../../utils/deck-utils'; +import {findDeck, updateDeck} from '../../utils/data/deck-utils'; import {convertDeck} from '../utils/convert-deck-utils'; import {publishDeckApi} from '../utils/api-utils'; -import {Resources} from '../../utils/resources'; +import {Resources} from '../../utils/resources/resources'; import * as admin from 'firebase-admin'; export function publishDeck(deckId: string | undefined, token: string | undefined): Promise { diff --git a/cloud/functions/src/request/utils/api-utils.ts b/cloud/functions/src/request/utils/api-utils.ts index f1b654aee..78656f495 100644 --- a/cloud/functions/src/request/utils/api-utils.ts +++ b/cloud/functions/src/request/utils/api-utils.ts @@ -2,7 +2,7 @@ import * as functions from 'firebase-functions'; import fetch, {Response} from 'node-fetch'; -import {Deck} from '../../model/deck'; +import {Deck} from '../../model/data/deck'; import {ApiDeck} from '../../model/api/api.deck'; import {ApiPresentation} from '../../model/api/api.presentation'; diff --git a/cloud/functions/src/request/utils/convert-deck-utils.ts b/cloud/functions/src/request/utils/convert-deck-utils.ts index d42c8d9c5..3addacf4c 100644 --- a/cloud/functions/src/request/utils/convert-deck-utils.ts +++ b/cloud/functions/src/request/utils/convert-deck-utils.ts @@ -1,11 +1,11 @@ import * as functions from 'firebase-functions'; -import {Deck} from '../../model/deck'; +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/slide'; +import {Slide, SlideAttributes, SlideTemplate} from '../../model/data/slide'; -import {findSlide} from '../../utils/slide-utils'; +import {findSlide} from '../../utils/data/slide-utils'; import {getGoogleFontScript} from './google-fonts-utils'; export function convertDeck(deck: Deck): Promise { diff --git a/cloud/functions/src/request/utils/google-fonts-utils.ts b/cloud/functions/src/request/utils/google-fonts-utils.ts index 6ebb615db..16e724631 100644 --- a/cloud/functions/src/request/utils/google-fonts-utils.ts +++ b/cloud/functions/src/request/utils/google-fonts-utils.ts @@ -1,4 +1,4 @@ -import {Deck} from '../../model/deck'; +import {Deck} from '../../model/data/deck'; import {JSDOM} from 'jsdom'; diff --git a/cloud/functions/src/utils/deck-utils.ts b/cloud/functions/src/utils/data/deck-utils.ts similarity index 95% rename from cloud/functions/src/utils/deck-utils.ts rename to cloud/functions/src/utils/data/deck-utils.ts index 57c721efe..5f9a88e0d 100644 --- a/cloud/functions/src/utils/deck-utils.ts +++ b/cloud/functions/src/utils/data/deck-utils.ts @@ -1,6 +1,6 @@ import * as admin from 'firebase-admin'; -import {Deck, DeckData} from '../model/deck'; +import {Deck, DeckData} from '../../model/data/deck'; export function findDeck(deckId: string): Promise { return new Promise(async (resolve, reject) => { diff --git a/cloud/functions/src/utils/slide-utils.ts b/cloud/functions/src/utils/data/slide-utils.ts similarity index 91% rename from cloud/functions/src/utils/slide-utils.ts rename to cloud/functions/src/utils/data/slide-utils.ts index d2bb179cf..ba468808d 100644 --- a/cloud/functions/src/utils/slide-utils.ts +++ b/cloud/functions/src/utils/data/slide-utils.ts @@ -1,6 +1,6 @@ import * as admin from 'firebase-admin'; -import {Slide, SlideData} from '../model/slide'; +import {Slide, SlideData} from '../../model/data/slide'; export function findSlide(deckId: string, slideId: string): Promise { return new Promise(async (resolve, reject) => { diff --git a/cloud/functions/src/utils/resources.ts b/cloud/functions/src/utils/resources/resources.ts similarity index 100% rename from cloud/functions/src/utils/resources.ts rename to cloud/functions/src/utils/resources/resources.ts 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 a1ed04c0d..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,10 +1,10 @@ import * as admin from 'firebase-admin'; -import {Slide} from '../../../model/slide'; -import {Deck} from '../../../model/deck'; +import {Slide} from '../../../model/data/slide'; +import {Deck} from '../../../model/data/deck'; -import {findSlide} from '../../../utils/slide-utils'; -import {findDeck} from '../../../utils/deck-utils'; +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) => { 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 index 025a99b95..b1bf8fdfa 100644 --- a/cloud/functions/src/watch/delete/delete-deploy.ts +++ b/cloud/functions/src/watch/delete/delete-deploy.ts @@ -1,6 +1,6 @@ import * as admin from 'firebase-admin'; -import {Deploy} from '../../model/deploy'; +import {Deploy} from '../../model/data/deploy'; import {deleteDeployForId} from './utils/delete-platform-utils'; 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 index f1adbeaab..4c0b212e5 100644 --- a/cloud/functions/src/watch/github/publish-github.ts +++ b/cloud/functions/src/watch/github/publish-github.ts @@ -1,9 +1,9 @@ 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 {DeckData} from '../../model/data/deck'; +import {Token} from '../../model/data/token'; +import {DeployGitHubRepo} from '../../model/data/deploy'; import {isDeckPublished} from '../screenshot/utils/update-deck'; diff --git a/cloud/functions/src/watch/github/utils/github-api.ts b/cloud/functions/src/watch/github/utils/github-api.ts index d7ec46a3b..ef80c4522 100644 --- a/cloud/functions/src/watch/github/utils/github-api.ts +++ b/cloud/functions/src/watch/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 {DeployGitHubRepo} from '../../../model/data/deploy'; export interface GitHubUser { id: string; diff --git a/cloud/functions/src/watch/github/utils/github-db.ts b/cloud/functions/src/watch/github/utils/github-db.ts index 2e4f08b27..9110f5e09 100644 --- a/cloud/functions/src/watch/github/utils/github-db.ts +++ b/cloud/functions/src/watch/github/utils/github-db.ts @@ -1,7 +1,7 @@ import * as admin from 'firebase-admin'; -import {Token, TokenData} from '../../../model/token'; -import {DeployGitHubRepo, Deploy, DeployData} from '../../../model/deploy'; +import {Token, TokenData} from '../../../model/data/token'; +import {DeployGitHubRepo, Deploy, DeployData} from '../../../model/data/deploy'; export function findToken(userId: string): Promise { return new Promise(async (resolve, reject) => { diff --git a/cloud/functions/src/watch/github/utils/github-fs.ts b/cloud/functions/src/watch/github/utils/github-fs.ts index c024de8fb..ba45ec754 100644 --- a/cloud/functions/src/watch/github/utils/github-fs.ts +++ b/cloud/functions/src/watch/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/github/utils/github-utils.ts index 8d276c0a0..2caf8b344 100644 --- a/cloud/functions/src/watch/github/utils/github-utils.ts +++ b/cloud/functions/src/watch/github/utils/github-utils.ts @@ -1,5 +1,5 @@ -import {DeckMeta} from '../../../model/deck'; -import {DeployGitHubRepo, Deploy, DeployData} from '../../../model/deploy'; +import {DeckMeta} from '../../../model/data/deck'; +import {DeployGitHubRepo, Deploy, DeployData} from '../../../model/data/deploy'; import {createPR, createRepo, findOrCreateRepo, findRepo, GitHubUser} from './github-api'; import {findDeploy, updateDeploy} from './github-db'; diff --git a/cloud/functions/src/watch/info/info-deck-publish.ts b/cloud/functions/src/watch/info/info-deck-publish.ts index 42f6876ae..17bcf837f 100644 --- a/cloud/functions/src/watch/info/info-deck-publish.ts +++ b/cloud/functions/src/watch/info/info-deck-publish.ts @@ -3,7 +3,7 @@ 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'; export async function infoDeckPublish(change: functions.Change) { 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/screenshot/generate-deck-screenshot.ts b/cloud/functions/src/watch/screenshot/generate-deck-screenshot.ts index ffa468bc1..13addefca 100644 --- a/cloud/functions/src/watch/screenshot/generate-deck-screenshot.ts +++ b/cloud/functions/src/watch/screenshot/generate-deck-screenshot.ts @@ -5,9 +5,9 @@ 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'; 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; From e50050c349eaebad8a0c560422a940213b8b299a Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Fri, 4 Sep 2020 12:27:37 +0200 Subject: [PATCH 15/30] feat: schedule task instead of publishing straight --- cloud/functions/src/index.ts | 4 +- cloud/functions/src/model/data/task.ts | 19 +++++++ cloud/functions/src/request/publish.ts | 17 +++--- .../request/publish/schedule-publish-task.ts | 54 +++++++++++++++++++ .../functions/src/request/utils/task-utils.ts | 26 +++++++++ 5 files changed, 107 insertions(+), 13 deletions(-) create mode 100644 cloud/functions/src/model/data/task.ts create mode 100644 cloud/functions/src/request/publish/schedule-publish-task.ts create mode 100644 cloud/functions/src/request/utils/task-utils.ts diff --git a/cloud/functions/src/index.ts b/cloud/functions/src/index.ts index 0e1cbbf44..c73055b59 100644 --- a/cloud/functions/src/index.ts +++ b/cloud/functions/src/index.ts @@ -9,7 +9,7 @@ app.firestore().settings({timestampsInSnapshots: true}); import {applyWatchDeckCreate, applyWatchDeckDelete, applyWatchDeckUpdate} from './watch/watch-deck'; import {applyWatchUserCreate, applyWatchUserDelete, applyWatchUserUpdate} from './watch/watch-user'; -import {publishJob} from './request/publish'; +import {publishTask} from './request/publish'; const runtimeOpts = { timeoutSeconds: 120, @@ -28,4 +28,4 @@ export const watchUserDelete = functions.auth.user().onDelete(applyWatchUserDele export const watchUserCreate = functions.auth.user().onCreate(applyWatchUserCreate); -export const publish = functions.runWith(runtimeOpts).https.onRequest(publishJob); +export const publish = functions.runWith(runtimeOpts).https.onRequest(publishTask); diff --git a/cloud/functions/src/model/data/task.ts b/cloud/functions/src/model/data/task.ts new file mode 100644 index 000000000..958a901a7 --- /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; + + 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/request/publish.ts b/cloud/functions/src/request/publish.ts index e1d0f9c9f..9d4a963e7 100644 --- a/cloud/functions/src/request/publish.ts +++ b/cloud/functions/src/request/publish.ts @@ -2,12 +2,11 @@ import * as functions from 'firebase-functions'; import * as cors from 'cors'; -import {geToken, verifyToken} from './utils/request-utils'; -import {publishDeck} from './publish/publish-deck'; +import {verifyToken} from './utils/request-utils'; -import {ApiPresentation} from '../model/api/api.presentation'; +import {ScheduledPublishTask, schedulePublish} from './publish/schedule-publish-task'; -export async function publishJob(request: functions.Request, response: functions.Response) { +export async function publishTask(request: functions.Request, response: functions.Response) { const corsHandler = cors({origin: true}); corsHandler(request, response, async () => { @@ -15,19 +14,15 @@ export async function publishJob(request: functions.Request, response: functions if (!validToken) { response.status(400).json({ - error: 'Not Authorized', + error: 'Not Authorized.', }); return; } try { - const token: string | undefined = await geToken(request); - const deckId: string | undefined = request.body.deckId; + const scheduledTask: ScheduledPublishTask = await schedulePublish(request); - // TODO: Don't publish here but trigger the functions - const apiDeckPublish: ApiPresentation = await publishDeck(deckId, token); - - response.json(apiDeckPublish); + 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..125b6a369 --- /dev/null +++ b/cloud/functions/src/request/publish/schedule-publish-task.ts @@ -0,0 +1,54 @@ +import * as functions from 'firebase-functions'; + +import {scheduleTask} from '../utils/task-utils'; +import {geToken} from '../utils/request-utils'; + +export interface ScheduledPublishTask {} + +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; + + if (!deckId) { + reject('No deck information provided.'); + return; + } + + if (!token) { + reject('No token provided.'); + return; + } + + const publish: boolean = request.body.publish !== undefined && request.body.publish; + + if (publish) { + await scheduleTask({ + deckId, + token, + type: 'publish-deck', + }); + } + + const github: boolean = request.body.github !== undefined && request.body.github; + + if (github) { + await scheduleTask({ + deckId, + token, + type: 'push-github', + }); + } + + resolve({ + deckId, + status: 'scheduled', + publish: publish, + github: github, + }); + } catch (err) { + reject(err); + } + }); +} diff --git a/cloud/functions/src/request/utils/task-utils.ts b/cloud/functions/src/request/utils/task-utils.ts new file mode 100644 index 000000000..26662d20b --- /dev/null +++ b/cloud/functions/src/request/utils/task-utils.ts @@ -0,0 +1,26 @@ +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); + } + }); +} From c5983c8eb7ba848f5cbeefe4e4e0402da85995d9 Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Fri, 4 Sep 2020 12:43:08 +0200 Subject: [PATCH 16/30] feat: publish when task is created --- cloud/functions/src/index.ts | 5 +- cloud/functions/src/model/data/task.ts | 2 +- .../src/request/publish/publish-deck.ts | 90 --------------- .../request/publish/schedule-publish-task.ts | 2 +- .../functions/src/request/utils/task-utils.ts | 26 ----- cloud/functions/src/utils/data/task-utils.ts | 75 +++++++++++++ cloud/functions/src/watch/publish/publish.ts | 104 ++++++++++++++++++ cloud/functions/src/watch/watch-task.ts | 8 ++ 8 files changed, 193 insertions(+), 119 deletions(-) delete mode 100644 cloud/functions/src/request/publish/publish-deck.ts delete mode 100644 cloud/functions/src/request/utils/task-utils.ts create mode 100644 cloud/functions/src/utils/data/task-utils.ts create mode 100644 cloud/functions/src/watch/publish/publish.ts create mode 100644 cloud/functions/src/watch/watch-task.ts diff --git a/cloud/functions/src/index.ts b/cloud/functions/src/index.ts index c73055b59..216ce332b 100644 --- a/cloud/functions/src/index.ts +++ b/cloud/functions/src/index.ts @@ -8,6 +8,7 @@ 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'; @@ -24,8 +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.runWith(runtimeOpts).https.onRequest(publishTask); +export const publish = functions.https.onRequest(publishTask); diff --git a/cloud/functions/src/model/data/task.ts b/cloud/functions/src/model/data/task.ts index 958a901a7..5ae7b10c2 100644 --- a/cloud/functions/src/model/data/task.ts +++ b/cloud/functions/src/model/data/task.ts @@ -2,7 +2,7 @@ import {firestore} from 'firebase-admin'; export interface TaskData { deckId: string; - token: string; + token: string | firestore.FieldValue; type: 'publish-deck' | 'push-github'; diff --git a/cloud/functions/src/request/publish/publish-deck.ts b/cloud/functions/src/request/publish/publish-deck.ts deleted file mode 100644 index 45017364d..000000000 --- a/cloud/functions/src/request/publish/publish-deck.ts +++ /dev/null @@ -1,90 +0,0 @@ -import {Deck, DeckData, DeckMeta} from '../../model/data/deck'; -import {ApiDeck} from '../../model/api/api.deck'; -import {ApiPresentation} from '../../model/api/api.presentation'; - -import {findDeck, updateDeck} from '../../utils/data/deck-utils'; -import {convertDeck} from '../utils/convert-deck-utils'; -import {publishDeckApi} from '../utils/api-utils'; -import {Resources} from '../../utils/resources/resources'; -import * as admin from 'firebase-admin'; - -export function publishDeck(deckId: string | undefined, token: string | undefined): Promise { - return new Promise(async (resolve, reject) => { - try { - if (!deckId) { - reject('No deck information provided.'); - return; - } - - if (!token) { - reject('No token provided.'); - return; - } - - const deck: Deck = await findDeck(deckId); - - if (!deck || !deck.data) { - reject('No matching deck.'); - return; - } - - if (!deck.data.meta) { - reject('Deck has no meta.'); - return; - } - - const apiDeck: ApiDeck = await convertDeck(deck); - - if (!apiDeck) { - reject('No converted 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); - - resolve(apiDeckPublish); - } catch (err) { - 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/request/publish/schedule-publish-task.ts b/cloud/functions/src/request/publish/schedule-publish-task.ts index 125b6a369..9e9e424f9 100644 --- a/cloud/functions/src/request/publish/schedule-publish-task.ts +++ b/cloud/functions/src/request/publish/schedule-publish-task.ts @@ -1,6 +1,6 @@ import * as functions from 'firebase-functions'; -import {scheduleTask} from '../utils/task-utils'; +import {scheduleTask} from '../../utils/data/task-utils'; import {geToken} from '../utils/request-utils'; export interface ScheduledPublishTask {} diff --git a/cloud/functions/src/request/utils/task-utils.ts b/cloud/functions/src/request/utils/task-utils.ts deleted file mode 100644 index 26662d20b..000000000 --- a/cloud/functions/src/request/utils/task-utils.ts +++ /dev/null @@ -1,26 +0,0 @@ -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); - } - }); -} 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..ddc8f39e8 --- /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 successful(taskId: string): Promise { + return new Promise(async (resolve, reject) => { + try { + await updateStatus(taskId, 'successful'); + + resolve(); + } catch (err) { + reject(err); + } + }); +} + +export function failure(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/watch/publish/publish.ts b/cloud/functions/src/watch/publish/publish.ts new file mode 100644 index 000000000..e79e73a24 --- /dev/null +++ b/cloud/functions/src/watch/publish/publish.ts @@ -0,0 +1,104 @@ +import * as admin from 'firebase-admin'; +import {EventContext} from 'firebase-functions'; +import {DocumentSnapshot} from 'firebase-functions/lib/providers/firestore'; + +import {Deck, DeckData, DeckMeta} from '../../model/data/deck'; +import {ApiDeck} from '../../model/api/api.deck'; +import {ApiPresentation} from '../../model/api/api.presentation'; +import {TaskData} from '../../model/data/task'; + +import {findDeck, updateDeck} from '../../utils/data/deck-utils'; +import {convertDeck} from '../../request/utils/convert-deck-utils'; +import {publishDeckApi} from '../../request/utils/api-utils'; + +import {Resources} from '../../utils/resources/resources'; +import {failure, successful} from '../../utils/data/task-utils'; + +export async function publish(snap: DocumentSnapshot, context: EventContext) { + const taskId: string = context.params.taskId; + + if (!taskId || taskId === undefined || taskId === '') { + return; + } + + const task: TaskData = snap.data() as TaskData; + + if (!task || task === undefined) { + return; + } + + if (!task.deckId || !task.token) { + return; + } + + if (task.status !== 'scheduled') { + return; + } + + try { + const deck: Deck = await findDeck(task.deckId); + + if (!deck || !deck.data) { + return; + } + + if (!deck.data.meta) { + return; + } + + const apiDeck: ApiDeck = await convertDeck(deck); + + if (!apiDeck) { + console.error('Cannot convert deck.'); + return; + } + + const apiDeckPublish: ApiPresentation = await publishDeckApi(deck, apiDeck, task.token as string); + + if (!apiDeckPublish || !apiDeckPublish.id || !apiDeckPublish.url) { + console.error(`Publish for deck ${deck.id} failed.`); + return; + } + + await updateDeckPublished(deck, apiDeckPublish); + + await successful(taskId); + } catch (err) { + console.error(err); + + await failure(taskId); + } +} + +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/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); +} From 539efd78e5bfecf39ce506c7e5a27f7e0a7bfa92 Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Fri, 4 Sep 2020 13:02:27 +0200 Subject: [PATCH 17/30] feat: publish to API or GitHub on task created --- .../src/watch/github/publish-github.ts | 79 ---------------- .../src/watch/publish/api/publish-api.ts | 80 ++++++++++++++++ .../watch/publish/github/publish-github.ts | 68 ++++++++++++++ .../{ => publish}/github/utils/github-api.ts | 2 +- .../{ => publish}/github/utils/github-cmd.ts | 0 .../{ => publish}/github/utils/github-db.ts | 4 +- .../{ => publish}/github/utils/github-fs.ts | 2 +- .../github/utils/github-utils.ts | 4 +- cloud/functions/src/watch/publish/publish.ts | 93 ++++++------------- cloud/functions/src/watch/watch-deck.ts | 3 - 10 files changed, 183 insertions(+), 152 deletions(-) delete mode 100644 cloud/functions/src/watch/github/publish-github.ts create mode 100644 cloud/functions/src/watch/publish/api/publish-api.ts create mode 100644 cloud/functions/src/watch/publish/github/publish-github.ts rename cloud/functions/src/watch/{ => publish}/github/utils/github-api.ts (98%) rename cloud/functions/src/watch/{ => publish}/github/utils/github-cmd.ts (100%) rename cloud/functions/src/watch/{ => publish}/github/utils/github-db.ts (93%) rename cloud/functions/src/watch/{ => publish}/github/utils/github-fs.ts (98%) rename cloud/functions/src/watch/{ => publish}/github/utils/github-utils.ts (96%) 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 4c0b212e5..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/data/deck'; -import {Token} from '../../model/data/token'; -import {DeployGitHubRepo} from '../../model/data/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/publish/api/publish-api.ts b/cloud/functions/src/watch/publish/api/publish-api.ts new file mode 100644 index 000000000..134fcf37c --- /dev/null +++ b/cloud/functions/src/watch/publish/api/publish-api.ts @@ -0,0 +1,80 @@ +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 {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); + + resolve(); + } catch (err) { + 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..c0ae494a4 --- /dev/null +++ b/cloud/functions/src/watch/publish/github/publish-github.ts @@ -0,0 +1,68 @@ +import {DeckData} from '../../../model/data/deck'; +import {Token} from '../../../model/data/token'; +import {DeployGitHubRepo} from '../../../model/data/deploy'; + +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(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; + } + + // 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: DeployGitHubRepo | undefined = await getRepo(platform.data.github.token, user, deckData.owner_id, deckId, deckData.meta); + + 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 updateDeck(platform.data.github.token, user, repo, deckData.meta); + + resolve(); + } catch (err) { + 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 98% rename from cloud/functions/src/watch/github/utils/github-api.ts rename to cloud/functions/src/watch/publish/github/utils/github-api.ts index ef80c4522..90fbae9f1 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/data/deploy'; +import {DeployGitHubRepo} from '../../../../model/data/deploy'; export interface GitHubUser { id: string; 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/github/utils/github-db.ts b/cloud/functions/src/watch/publish/github/utils/github-db.ts similarity index 93% rename from cloud/functions/src/watch/github/utils/github-db.ts rename to cloud/functions/src/watch/publish/github/utils/github-db.ts index 9110f5e09..47ad5f3dd 100644 --- a/cloud/functions/src/watch/github/utils/github-db.ts +++ b/cloud/functions/src/watch/publish/github/utils/github-db.ts @@ -1,7 +1,7 @@ import * as admin from 'firebase-admin'; -import {Token, TokenData} from '../../../model/data/token'; -import {DeployGitHubRepo, Deploy, DeployData} from '../../../model/data/deploy'; +import {Token, TokenData} from '../../../../model/data/token'; +import {DeployGitHubRepo, Deploy, DeployData} from '../../../../model/data/deploy'; export function findToken(userId: string): Promise { return new Promise(async (resolve, reject) => { 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 ba45ec754..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/data/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 96% rename from cloud/functions/src/watch/github/utils/github-utils.ts rename to cloud/functions/src/watch/publish/github/utils/github-utils.ts index 2caf8b344..081bb3447 100644 --- a/cloud/functions/src/watch/github/utils/github-utils.ts +++ b/cloud/functions/src/watch/publish/github/utils/github-utils.ts @@ -1,5 +1,5 @@ -import {DeckMeta} from '../../../model/data/deck'; -import {DeployGitHubRepo, Deploy, DeployData} from '../../../model/data/deploy'; +import {DeckMeta} from '../../../../model/data/deck'; +import {DeployGitHubRepo, Deploy, DeployData} from '../../../../model/data/deploy'; import {createPR, createRepo, findOrCreateRepo, findRepo, GitHubUser} from './github-api'; import {findDeploy, updateDeploy} from './github-db'; diff --git a/cloud/functions/src/watch/publish/publish.ts b/cloud/functions/src/watch/publish/publish.ts index e79e73a24..caae52c3e 100644 --- a/cloud/functions/src/watch/publish/publish.ts +++ b/cloud/functions/src/watch/publish/publish.ts @@ -1,18 +1,14 @@ -import * as admin from 'firebase-admin'; import {EventContext} from 'firebase-functions'; import {DocumentSnapshot} from 'firebase-functions/lib/providers/firestore'; -import {Deck, DeckData, DeckMeta} from '../../model/data/deck'; -import {ApiDeck} from '../../model/api/api.deck'; -import {ApiPresentation} from '../../model/api/api.presentation'; +import {Deck} from '../../model/data/deck'; import {TaskData} from '../../model/data/task'; -import {findDeck, updateDeck} from '../../utils/data/deck-utils'; -import {convertDeck} from '../../request/utils/convert-deck-utils'; -import {publishDeckApi} from '../../request/utils/api-utils'; +import {findDeck} from '../../utils/data/deck-utils'; -import {Resources} from '../../utils/resources/resources'; import {failure, successful} 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; @@ -21,80 +17,49 @@ export async function publish(snap: DocumentSnapshot, context: EventContext) { return; } - const task: TaskData = snap.data() as TaskData; - - if (!task || task === undefined) { - return; - } + try { + await publishJob(snap); - if (!task.deckId || !task.token) { - return; - } + await successful(taskId); + } catch (err) { + console.error(err); - if (task.status !== 'scheduled') { - return; + await failure(taskId); } +} - try { - const deck: Deck = await findDeck(task.deckId); +function publishJob(snap: DocumentSnapshot): Promise { + return new Promise(async (resolve, reject) => { + const task: TaskData = snap.data() as TaskData; - if (!deck || !deck.data) { + if (!task || task === undefined) { + reject('No task data.'); return; } - if (!deck.data.meta) { + if (!task.deckId || !task.token) { + reject('No task token.'); return; } - const apiDeck: ApiDeck = await convertDeck(deck); - - if (!apiDeck) { - console.error('Cannot convert deck.'); + if (task.status !== 'scheduled') { + reject('Task not scheduled.'); return; } - const apiDeckPublish: ApiPresentation = await publishDeckApi(deck, apiDeck, task.token as string); - - if (!apiDeckPublish || !apiDeckPublish.id || !apiDeckPublish.url) { - console.error(`Publish for deck ${deck.id} failed.`); - return; - } - - await updateDeckPublished(deck, apiDeckPublish); - - await successful(taskId); - } catch (err) { - console.error(err); - - await failure(taskId); - } -} - -function updateDeckPublished(deck: Deck, apiDeckPublish: ApiPresentation): Promise { - return new Promise(async (resolve, reject) => { try { - const url: URL = new URL(apiDeckPublish.url); + const deck: Deck = await findDeck(task.deckId); - 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; + if (!deck || !deck.data) { + reject('Task found no deck data.'); + return; } - await updateDeck(deck.id, deckData); + 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) { 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); } From b197f50a2fe47f1b700671ee960db866db2b614f Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Fri, 4 Sep 2020 13:49:50 +0200 Subject: [PATCH 18/30] feat: add status for api and github deployment to deploy --- cloud/functions/src/model/data/deploy.ts | 12 +++- .../request/publish/schedule-publish-task.ts | 51 +++++++++++++++- .../functions/src/utils/data/deploy-utils.ts | 60 +++++++++++++++++++ cloud/functions/src/utils/data/task-utils.ts | 4 +- .../src/watch/publish/api/publish-api.ts | 5 ++ .../watch/publish/github/publish-github.ts | 9 ++- .../watch/publish/github/utils/github-db.ts | 10 +--- .../publish/github/utils/github-utils.ts | 16 ++--- cloud/functions/src/watch/publish/publish.ts | 6 +- 9 files changed, 146 insertions(+), 27 deletions(-) create mode 100644 cloud/functions/src/utils/data/deploy-utils.ts diff --git a/cloud/functions/src/model/data/deploy.ts b/cloud/functions/src/model/data/deploy.ts index 966860805..34cddac8a 100644 --- a/cloud/functions/src/model/data/deploy.ts +++ b/cloud/functions/src/model/data/deploy.ts @@ -8,15 +8,21 @@ export interface DeployGitHubRepo { } export interface DeployGitHub { - repo: DeployGitHubRepo; + repo?: DeployGitHubRepo; + status: 'scheduled' | 'failure' | 'successful'; +} + +export interface DeployApi { + status: 'scheduled' | 'failure' | 'successful'; } export interface DeployData { owner_id: string; - github: DeployGitHub; + github?: DeployGitHub; + + api?: DeployApi; - created_at?: firestore.Timestamp; updated_at?: firestore.Timestamp; } diff --git a/cloud/functions/src/request/publish/schedule-publish-task.ts b/cloud/functions/src/request/publish/schedule-publish-task.ts index 9e9e424f9..afe587945 100644 --- a/cloud/functions/src/request/publish/schedule-publish-task.ts +++ b/cloud/functions/src/request/publish/schedule-publish-task.ts @@ -1,8 +1,11 @@ +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 {DeployData} from '../../model/data/deploy'; + export interface ScheduledPublishTask {} export function schedulePublish(request: functions.Request): Promise { @@ -22,6 +25,17 @@ export function schedulePublish(request: functions.Request): Promise { + return new Promise(async (resolve, reject) => { + try { + if (!deckId || deckId === undefined || !deckId) { + resolve(); + return; + } + + const documentReference: admin.firestore.DocumentReference = admin.firestore().doc(`/deploys/${deckId}/`); + + const updateData: Partial = { + updated_at: admin.firestore.Timestamp.now(), + }; + + if (publish) { + updateData.api = { + status: 'scheduled', + }; + } + + if (github) { + updateData.github = { + status: 'scheduled', + }; + } + + await documentReference.set(updateData, {merge: true}); + + resolve(); + } catch (err) { + reject(err); + } + }); +} diff --git a/cloud/functions/src/utils/data/deploy-utils.ts b/cloud/functions/src/utils/data/deploy-utils.ts new file mode 100644 index 000000000..8d97fc8a0 --- /dev/null +++ b/cloud/functions/src/utils/data/deploy-utils.ts @@ -0,0 +1,60 @@ +import * as admin from 'firebase-admin'; + +import {DeployApi, DeployData, DeployGitHub} from '../../model/data/deploy'; + +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(`/deploys/${deckId}/`); + + const updateData: Partial = { + updated_at: admin.firestore.Timestamp.now(), + }; + + (updateData[type] as DeployGitHub | DeployApi).status = status; + + await admin.firestore().runTransaction((transaction) => { + return transaction.get(documentReference).then((sfDoc) => { + if (!sfDoc.exists) { + throw 'Document does not exist!'; + } + + transaction.set(documentReference, updateData, {merge: true}); + }); + }); + + resolve(); + } catch (err) { + reject(err); + } + }); +} diff --git a/cloud/functions/src/utils/data/task-utils.ts b/cloud/functions/src/utils/data/task-utils.ts index ddc8f39e8..beeb82eaf 100644 --- a/cloud/functions/src/utils/data/task-utils.ts +++ b/cloud/functions/src/utils/data/task-utils.ts @@ -25,7 +25,7 @@ export function scheduleTask(data: Partial): Promise { }); } -export function successful(taskId: string): Promise { +export function successfulTask(taskId: string): Promise { return new Promise(async (resolve, reject) => { try { await updateStatus(taskId, 'successful'); @@ -37,7 +37,7 @@ export function successful(taskId: string): Promise { }); } -export function failure(taskId: string): Promise { +export function failureTask(taskId: string): Promise { return new Promise(async (resolve, reject) => { try { await updateStatus(taskId, 'failure'); diff --git a/cloud/functions/src/watch/publish/api/publish-api.ts b/cloud/functions/src/watch/publish/api/publish-api.ts index 134fcf37c..94dde9336 100644 --- a/cloud/functions/src/watch/publish/api/publish-api.ts +++ b/cloud/functions/src/watch/publish/api/publish-api.ts @@ -7,6 +7,7 @@ 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/deploy-utils'; import {Resources} from '../../../utils/resources/resources'; @@ -39,8 +40,12 @@ export function publishToApi(deck: Deck, token: string): Promise { await updateDeckPublished(deck, apiDeckPublish); + await successfulDeploy(deck.id, 'api'); + resolve(); } catch (err) { + await failureDeploy(deck.id, 'api'); + reject(err); } }); diff --git a/cloud/functions/src/watch/publish/github/publish-github.ts b/cloud/functions/src/watch/publish/github/publish-github.ts index c0ae494a4..0ed43d045 100644 --- a/cloud/functions/src/watch/publish/github/publish-github.ts +++ b/cloud/functions/src/watch/publish/github/publish-github.ts @@ -5,7 +5,8 @@ import {DeployGitHubRepo} from '../../../model/data/deploy'; 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'; +import {getRepo, updateGitHubDeck, updateProject} from './utils/github-utils'; +import {failureDeploy, successfulDeploy} from '../../../utils/data/deploy-utils'; export async function publishToGitHub(deckId: string, deckData: DeckData): Promise { return new Promise(async (resolve, reject) => { @@ -58,10 +59,14 @@ export async function publishToGitHub(deckId: string, deckData: DeckData): Promi await updateProject(platform.data.github.token, user.login, repo.name, repo.url, deckData.meta); - await updateDeck(platform.data.github.token, user, repo, 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/publish/github/utils/github-db.ts b/cloud/functions/src/watch/publish/github/utils/github-db.ts index 47ad5f3dd..1646a9a0c 100644 --- a/cloud/functions/src/watch/publish/github/utils/github-db.ts +++ b/cloud/functions/src/watch/publish/github/utils/github-db.ts @@ -1,7 +1,7 @@ import * as admin from 'firebase-admin'; import {Token, TokenData} from '../../../../model/data/token'; -import {DeployGitHubRepo, Deploy, DeployData} from '../../../../model/data/deploy'; +import {DeployGitHubRepo, Deploy, DeployData, DeployGitHub} from '../../../../model/data/deploy'; export function findToken(userId: string): Promise { return new Promise(async (resolve, reject) => { @@ -49,7 +49,7 @@ export function findDeploy(deckId: string): Promise { }); } -export function updateDeploy(deckId: string, deckData: DeployData, repo: DeployGitHubRepo | undefined): Promise { +export function updateGitHubDeploy(deckId: string, deckData: DeployData, repo: DeployGitHubRepo | undefined): Promise { return new Promise(async (resolve, reject) => { try { if (!deckId || deckId === undefined || deckId === '') { @@ -63,14 +63,10 @@ export function updateDeploy(deckId: string, deckData: DeployData, repo: DeployG } const data: DeployData = {...deckData}; - data.github.repo = {...repo}; + (data.github as DeployGitHub).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}); diff --git a/cloud/functions/src/watch/publish/github/utils/github-utils.ts b/cloud/functions/src/watch/publish/github/utils/github-utils.ts index 081bb3447..7f64ab69c 100644 --- a/cloud/functions/src/watch/publish/github/utils/github-utils.ts +++ b/cloud/functions/src/watch/publish/github/utils/github-utils.ts @@ -1,8 +1,8 @@ import {DeckMeta} from '../../../../model/data/deck'; -import {DeployGitHubRepo, Deploy, DeployData} from '../../../../model/data/deploy'; +import {DeployGitHubRepo, Deploy, DeployData, DeployGitHub} from '../../../../model/data/deploy'; import {createPR, createRepo, findOrCreateRepo, findRepo, GitHubUser} from './github-api'; -import {findDeploy, updateDeploy} from './github-db'; +import {findDeploy, updateGitHubDeploy} 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'; @@ -19,12 +19,12 @@ export async function getRepo( const deploy: Deploy | undefined = await findDeploy(deckId); - if (deploy) { + if (deploy && deploy.data && deploy.data.github && deploy.data.github.repo) { const existingRepo: DeployGitHubRepo | undefined = await findRepo(githubToken, user, deploy.data.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 updateGitHubDeploy(deckId, deploy.data, existingRepo); return existingRepo; } @@ -32,7 +32,7 @@ export async function getRepo( // The user may have delete its repo const createdRepo: DeployGitHubRepo | undefined = await createRepo(githubToken, user, project, description); - await updateDeploy(deckId, deploy.data, createdRepo); + await updateGitHubDeploy(deckId, deploy.data, createdRepo); return createdRepo; } @@ -47,10 +47,10 @@ export async function getRepo( owner_id: userId, github: { repo, - }, + } as DeployGitHub, }; - await updateDeploy(deckId, data, repo); + await updateGitHubDeploy(deckId, data, repo); return repo; } @@ -81,7 +81,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: DeployGitHubRepo, 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 index caae52c3e..677e1fb39 100644 --- a/cloud/functions/src/watch/publish/publish.ts +++ b/cloud/functions/src/watch/publish/publish.ts @@ -6,7 +6,7 @@ import {TaskData} from '../../model/data/task'; import {findDeck} from '../../utils/data/deck-utils'; -import {failure, successful} from '../../utils/data/task-utils'; +import {failureTask, successfulTask} from '../../utils/data/task-utils'; import {publishToApi} from './api/publish-api'; import {publishToGitHub} from './github/publish-github'; @@ -20,11 +20,11 @@ export async function publish(snap: DocumentSnapshot, context: EventContext) { try { await publishJob(snap); - await successful(taskId); + await successfulTask(taskId); } catch (err) { console.error(err); - await failure(taskId); + await failureTask(taskId); } } From e615cdcad14c5abfc1324fa8b187f68b92d215fa Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Fri, 4 Sep 2020 15:14:05 +0200 Subject: [PATCH 19/30] fix: update data --- cloud/functions/src/utils/data/deploy-utils.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/cloud/functions/src/utils/data/deploy-utils.ts b/cloud/functions/src/utils/data/deploy-utils.ts index 8d97fc8a0..e344ca111 100644 --- a/cloud/functions/src/utils/data/deploy-utils.ts +++ b/cloud/functions/src/utils/data/deploy-utils.ts @@ -1,6 +1,6 @@ import * as admin from 'firebase-admin'; -import {DeployApi, DeployData, DeployGitHub} from '../../model/data/deploy'; +import {DeployData} from '../../model/data/deploy'; export function successfulDeploy(deckId: string, type: 'github' | 'api'): Promise { return new Promise(async (resolve, reject) => { @@ -40,12 +40,20 @@ function updateStatus(deckId: string, type: 'github' | 'api', status: 'scheduled updated_at: admin.firestore.Timestamp.now(), }; - (updateData[type] as DeployGitHub | DeployApi).status = status; + if (type === 'github') { + updateData.github = { + status, + }; + } else if (type === 'api') { + updateData.api = { + status, + }; + } await admin.firestore().runTransaction((transaction) => { return transaction.get(documentReference).then((sfDoc) => { if (!sfDoc.exists) { - throw 'Document does not exist!'; + throw new Error('Document does not exist!'); } transaction.set(documentReference, updateData, {merge: true}); From 03008f69d5287665285a773d5744572471c65b06 Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Fri, 4 Sep 2020 15:50:21 +0200 Subject: [PATCH 20/30] feat: publish progress logic in component according deploy api state --- .../app-publish-edit/app-publish-edit.tsx | 115 +++++++++++++++--- studio/src/app/models/data/deploy.tsx | 12 +- .../services/data/deploy/deploy.service.ts | 6 +- .../editor/publish/publish.service.tsx | 65 ++-------- studio/src/app/stores/publish.store.ts | 11 -- 5 files changed, 114 insertions(+), 95 deletions(-) delete mode 100644 studio/src/app/stores/publish.store.ts 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..4450932fe 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,18 +5,20 @@ 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'; +import {Deploy} from '../../../../models/data/deploy'; 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 +43,9 @@ export class AppPublishEdit { @State() private publishing: boolean = false; + @State() + private progress: number | undefined = undefined; + @State() private tag: string; @@ -58,6 +63,8 @@ export class AppPublishEdit { private publishService: PublishService; + private destroyDeployListener; + constructor() { this.deckService = DeckService.getInstance(); @@ -70,12 +77,26 @@ export class AppPublishEdit { async componentWillLoad() { await this.init(); + + this.destroyDeployListener = deployStore.onChange('deploy', async (_deploy: Deploy | undefined) => { + this.publishing = + deployStore.state.deploy && + deployStore.state.deploy.data && + deployStore.state.deploy.data.api && + (deployStore.state.deploy.data.api.status === 'scheduled' || deployStore.state.deploy.data.api.status === 'failure'); + }); } componentDidLoad() { this.validateCaptionInput(); } + disconnectedCallback() { + if (this.destroyDeployListener) { + this.destroyDeployListener(); + } + } + private async init() { if (!deckStore.state.deck || !deckStore.state.deck.data) { return; @@ -155,14 +176,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 +189,55 @@ export class AppPublishEdit { }); } + private onSuccessfulPublish() { + const destroyDeploySuccessfulListener = deployStore.onChange('deploy', async (deploy: Deploy | undefined) => { + if (deploy && deploy.data && deploy.data.api && deploy.data.api.status === 'successful') { + destroyDeploySuccessfulListener(); + + await this.delayRefreshDeck(); + } + }); + } + + // 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 delayRefreshDeck() { + this.progress = 0; + + const currentDeck: Deck = {...deckStore.state.deck}; + + await this.publishService.refreshDeck(currentDeck.id); + + // In case the user would have browse the feed before, reset it to fetch is updated or new presentation + feedStore.reset(); + + const newApiId: boolean = currentDeck.data.api_id !== deckStore.state.deck.data.api_id; + + 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 +371,8 @@ export class AppPublishEdit { } render() { + const disable: boolean = this.publishing || this.progress !== undefined; + return (

Share your presentation online

@@ -333,7 +400,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 +422,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 +438,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 +446,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 +470,8 @@ export class AppPublishEdit { ); } - private renderPublish() { - if (!this.publishing) { + private renderPublish(disable: boolean) { + if (!disable) { return ( Publish now @@ -413,14 +480,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 +508,12 @@ export class AppPublishEdit { this.onGitHubChange($event)}> - + Yes - + No diff --git a/studio/src/app/models/data/deploy.tsx b/studio/src/app/models/data/deploy.tsx index e615288ac..272759295 100644 --- a/studio/src/app/models/data/deploy.tsx +++ b/studio/src/app/models/data/deploy.tsx @@ -6,16 +6,22 @@ export interface DeployGitHubRepo { } export interface DeployGitHub { - repo: DeployGitHubRepo; + repo?: DeployGitHubRepo; + status: 'scheduled' | 'failure' | 'successful'; +} + +export interface DeployApi { + status: 'scheduled' | 'failure' | 'successful'; } export interface DeployData { owner_id: string; - github: DeployGitHub; + github?: DeployGitHub; + + api?: DeployApi; updated_at?: firebase.firestore.Timestamp; - created_at?: firebase.firestore.Timestamp; } export interface Deploy { diff --git a/studio/src/app/services/data/deploy/deploy.service.ts b/studio/src/app/services/data/deploy/deploy.service.ts index 14b8e781e..84ed6355a 100644 --- a/studio/src/app/services/data/deploy/deploy.service.ts +++ b/studio/src/app/services/data/deploy/deploy.service.ts @@ -25,18 +25,16 @@ export class DeployService { snapshot(): Promise<() => void | undefined> { return new Promise<() => void | undefined>((resolve) => { + deployStore.reset(); + const deck: Deck = deckStore.state.deck; if (!deck || !deck.id) { - deployStore.reset(); - resolve(undefined); return; } if (!authStore.state.gitHub) { - deployStore.reset(); - resolve(undefined); return; } diff --git a/studio/src/app/services/editor/publish/publish.service.tsx b/studio/src/app/services/editor/publish/publish.service.tsx index 40491388a..83be64993 100644 --- a/studio/src/app/services/editor/publish/publish.service.tsx +++ b/studio/src/app/services/editor/publish/publish.service.tsx @@ -3,7 +3,6 @@ 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 {Deck, DeckMetaAuthor} from '../../../models/data/deck'; @@ -31,38 +30,20 @@ export class PublishService { return PublishService.instance; } - private progress(progress: number) { - publishStore.state.progress = progress; - } - - private progressComplete() { - publishStore.state.progress = 1; - } - - 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; } await this.updateDeckMeta(description, tags, github); - this.progress(0.25); - await this.publishDeck(deckStore.state.deck); - this.progress(0.75); - - await this.delayRefreshDeck(); - - resolve('https://deckdeckgo.com/todo'); + resolve(); } catch (err) { - this.progressComplete(); reject(err); } }); @@ -84,6 +65,8 @@ export class PublishService { }, body: JSON.stringify({ deckId: deck.id, + publish: true, + github: deck.data.meta.github, }), }); @@ -99,41 +82,8 @@ export class PublishService { }); } - // 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 delayRefreshDeck(): Promise { - return new Promise(async (resolve) => { - const currentDeck: Deck = {...deckStore.state.deck}; - - await this.refreshDeck(currentDeck.id); - - this.progress(0.9); - - const newApiId: boolean = currentDeck.data.api_id !== deckStore.state.deck.data.api_id; - - const interval = newApiId - ? setInterval(() => { - this.progress(publishStore.state.progress + 0.01); - }, 7000 / 9) - : undefined; - - setTimeout( - () => { - if (interval) { - clearInterval(interval); - } - - this.progressComplete(); - - resolve(); - }, - newApiId ? 7000 : 0 - ); - }); - } - // Otherwise we gonna kept in memory references like firebase.firestore.FieldValue.delete instead of null values - private refreshDeck(deckId: string): Promise { + refreshDeck(deckId: string): Promise { return new Promise(async (resolve, reject) => { try { const freshDeck: Deck = await this.deckService.get(deckId); @@ -211,7 +161,8 @@ export class PublishService { deck.data.meta.author = firebase.firestore.FieldValue.delete(); } - await this.deckService.update(deck); + const updatedDeck: Deck = await this.deckService.update(deckStore.state.deck); + deckStore.state.deck = {...updatedDeck}; resolve(); } catch (err) { 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}; From a29210c4e30bba1769cda4cce9577d9ca3cf4b2a Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Fri, 4 Sep 2020 16:15:55 +0200 Subject: [PATCH 21/30] fix: owner_id always provided for deployment information --- .../src/request/publish/schedule-publish-task.ts | 13 ++++++++++--- .../src/app/services/data/deploy/deploy.service.ts | 5 ++++- .../app/services/editor/publish/publish.service.tsx | 1 + 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/cloud/functions/src/request/publish/schedule-publish-task.ts b/cloud/functions/src/request/publish/schedule-publish-task.ts index afe587945..e0ed8d98f 100644 --- a/cloud/functions/src/request/publish/schedule-publish-task.ts +++ b/cloud/functions/src/request/publish/schedule-publish-task.ts @@ -13,6 +13,7 @@ export function schedulePublish(request: functions.Request): Promise { +function scheduleDeploy(deckId: string, ownerId: string, publish: boolean, github: boolean): Promise { return new Promise(async (resolve, reject) => { try { if (!deckId || deckId === undefined || !deckId) { @@ -75,7 +81,8 @@ function scheduleDeploy(deckId: string, publish: boolean, github: boolean): Prom const documentReference: admin.firestore.DocumentReference = admin.firestore().doc(`/deploys/${deckId}/`); - const updateData: Partial = { + const updateData: DeployData = { + owner_id: ownerId, updated_at: admin.firestore.Timestamp.now(), }; diff --git a/studio/src/app/services/data/deploy/deploy.service.ts b/studio/src/app/services/data/deploy/deploy.service.ts index 84ed6355a..7ffe31013 100644 --- a/studio/src/app/services/data/deploy/deploy.service.ts +++ b/studio/src/app/services/data/deploy/deploy.service.ts @@ -39,6 +39,8 @@ export class DeployService { return; } + console.log(deck); + const firestore: firebase.firestore.Firestore = firebase.firestore(); const unsubscribe = firestore .collection(`deploys`) @@ -51,7 +53,8 @@ export class DeployService { }; }, (_err) => { - errorStore.state.error = 'GitHub deploy information cannot be retrieved.'; + console.log(_err); + errorStore.state.error = 'Cannont retrieve the deploy information.'; } ); diff --git a/studio/src/app/services/editor/publish/publish.service.tsx b/studio/src/app/services/editor/publish/publish.service.tsx index 83be64993..90a4160a5 100644 --- a/studio/src/app/services/editor/publish/publish.service.tsx +++ b/studio/src/app/services/editor/publish/publish.service.tsx @@ -65,6 +65,7 @@ export class PublishService { }, body: JSON.stringify({ deckId: deck.id, + ownerId: deck.data.owner_id, publish: true, github: deck.data.meta.github, }), From c0f0304a55aab2f20ae4c36902462a8a0e1140a4 Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Fri, 4 Sep 2020 16:16:29 +0200 Subject: [PATCH 22/30] chore: remove console.log --- studio/src/app/services/data/deploy/deploy.service.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/studio/src/app/services/data/deploy/deploy.service.ts b/studio/src/app/services/data/deploy/deploy.service.ts index 7ffe31013..229354ac5 100644 --- a/studio/src/app/services/data/deploy/deploy.service.ts +++ b/studio/src/app/services/data/deploy/deploy.service.ts @@ -39,8 +39,6 @@ export class DeployService { return; } - console.log(deck); - const firestore: firebase.firestore.Firestore = firebase.firestore(); const unsubscribe = firestore .collection(`deploys`) @@ -53,7 +51,6 @@ export class DeployService { }; }, (_err) => { - console.log(_err); errorStore.state.error = 'Cannont retrieve the deploy information.'; } ); From 108c36b1656e680d24a9dfbfb1a3b18d89d2993d Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Fri, 4 Sep 2020 17:33:14 +0200 Subject: [PATCH 23/30] refactor: use firebase to resolve token --- studio/src/app/models/auth/auth.user.tsx | 1 - studio/src/app/pages/core/app-settings/app-settings.tsx | 7 +++++-- studio/src/app/pages/core/app-signin/app-signin.tsx | 7 ++++++- studio/src/app/services/api/user/api.user.service.tsx | 7 ++++++- studio/src/app/services/auth/auth.service.tsx | 3 --- studio/src/app/stores/auth.store.ts | 6 ------ 6 files changed, 17 insertions(+), 14 deletions(-) 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/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/user/api.user.service.tsx b/studio/src/app/services/api/user/api.user.service.tsx index fdfcc3adc..47a5e5131 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 * as firebase from 'firebase'; +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/stores/auth.store.ts b/studio/src/app/stores/auth.store.ts index 6b7c6b886..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,17 +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; - - // TODO: Remove bearer from store - state.bearer = `Bearer ${authUser ? authUser.token : ''}`; - state.loggedIn = authUser && !authUser.anonymous; state.gitHub = authUser ? authUser.gitHub : false; }); From 9437f74a896474a2357856d6cf7d90c5a288c249 Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Fri, 4 Sep 2020 17:36:03 +0200 Subject: [PATCH 24/30] chore: no dev --- studio/src/app/services/editor/publish/publish.service.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/studio/src/app/services/editor/publish/publish.service.tsx b/studio/src/app/services/editor/publish/publish.service.tsx index 90a4160a5..662d313b8 100644 --- a/studio/src/app/services/editor/publish/publish.service.tsx +++ b/studio/src/app/services/editor/publish/publish.service.tsx @@ -49,7 +49,7 @@ export class PublishService { }); } - private async publishDeck(deck: Deck): Promise { + private publishDeck(deck: Deck): Promise { return new Promise(async (resolve, reject) => { try { const config: EnvironmentFirebaseConfig = EnvironmentConfigService.getInstance().get('firebase'); From 094dffb567bd228d21d854681e9e3fec26fe554b Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Sat, 5 Sep 2020 09:23:08 +0200 Subject: [PATCH 25/30] feat: remove collection deploy and use deck --- cloud/functions/src/model/data/deck.ts | 25 ++++++++ cloud/functions/src/model/data/deploy.ts | 33 ---------- .../request/publish/schedule-publish-task.ts | 38 ++++++------ .../{deploy-utils.ts => deck-deploy-utils.ts} | 20 ++++--- .../src/watch/delete/delete-deploy.ts | 60 ------------------- .../src/watch/publish/api/publish-api.ts | 2 +- .../watch/publish/github/publish-github.ts | 7 +-- .../watch/publish/github/utils/github-api.ts | 18 +++--- .../watch/publish/github/utils/github-db.ts | 39 +++--------- .../publish/github/utils/github-utils.ts | 47 ++++++--------- cloud/functions/src/watch/watch-user.ts | 2 - 11 files changed, 99 insertions(+), 192 deletions(-) delete mode 100644 cloud/functions/src/model/data/deploy.ts rename cloud/functions/src/utils/data/{deploy-utils.ts => deck-deploy-utils.ts} (78%) delete mode 100644 cloud/functions/src/watch/delete/delete-deploy.ts diff --git a/cloud/functions/src/model/data/deck.ts b/cloud/functions/src/model/data/deck.ts index 2c78dcc45..88ace1317 100644 --- a/cloud/functions/src/model/data/deck.ts +++ b/cloud/functions/src/model/data/deck.ts @@ -2,6 +2,27 @@ 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; +} + export interface DeckMetaAuthor { name: string; photo_url?: string | firestore.FieldValue; @@ -53,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/deploy.ts b/cloud/functions/src/model/data/deploy.ts deleted file mode 100644 index 34cddac8a..000000000 --- a/cloud/functions/src/model/data/deploy.ts +++ /dev/null @@ -1,33 +0,0 @@ -import {firestore} from 'firebase-admin'; - -export interface DeployGitHubRepo { - id: string; - url: string; - name: string; - nameWithOwner: string; -} - -export interface DeployGitHub { - repo?: DeployGitHubRepo; - status: 'scheduled' | 'failure' | 'successful'; -} - -export interface DeployApi { - status: 'scheduled' | 'failure' | 'successful'; -} - -export interface DeployData { - owner_id: string; - - github?: DeployGitHub; - - api?: DeployApi; - - updated_at?: firestore.Timestamp; -} - -export interface Deploy { - id: string; - ref: firestore.DocumentReference; - data: DeployData; -} diff --git a/cloud/functions/src/request/publish/schedule-publish-task.ts b/cloud/functions/src/request/publish/schedule-publish-task.ts index e0ed8d98f..bd4f293b1 100644 --- a/cloud/functions/src/request/publish/schedule-publish-task.ts +++ b/cloud/functions/src/request/publish/schedule-publish-task.ts @@ -4,7 +4,7 @@ import * as functions from 'firebase-functions'; import {scheduleTask} from '../../utils/data/task-utils'; import {geToken} from '../utils/request-utils'; -import {DeployData} from '../../model/data/deploy'; +import {DeckData, DeckDeployData} from '../../model/data/deck'; export interface ScheduledPublishTask {} @@ -39,7 +39,7 @@ export function schedulePublish(request: functions.Request): Promise { +function updateDeckDeploy(deckId: string, ownerId: string, publish: boolean, github: boolean): Promise { return new Promise(async (resolve, reject) => { try { if (!deckId || deckId === undefined || !deckId) { @@ -79,24 +79,28 @@ function scheduleDeploy(deckId: string, ownerId: string, publish: boolean, githu return; } - const documentReference: admin.firestore.DocumentReference = admin.firestore().doc(`/deploys/${deckId}/`); + const documentReference: admin.firestore.DocumentReference = admin.firestore().doc(`/decks/${deckId}/`); - const updateData: DeployData = { - owner_id: ownerId, + const deployData: DeckDeployData = { + status: 'scheduled', updated_at: admin.firestore.Timestamp.now(), }; - if (publish) { - updateData.api = { - status: 'scheduled', - }; - } - - if (github) { - updateData.github = { - status: 'scheduled', - }; - } + const updateData: Partial = publish + ? { + deploy: { + api: { + ...deployData, + }, + }, + } + : { + deploy: { + github: { + ...deployData, + }, + }, + }; await documentReference.set(updateData, {merge: true}); diff --git a/cloud/functions/src/utils/data/deploy-utils.ts b/cloud/functions/src/utils/data/deck-deploy-utils.ts similarity index 78% rename from cloud/functions/src/utils/data/deploy-utils.ts rename to cloud/functions/src/utils/data/deck-deploy-utils.ts index e344ca111..1c7def91a 100644 --- a/cloud/functions/src/utils/data/deploy-utils.ts +++ b/cloud/functions/src/utils/data/deck-deploy-utils.ts @@ -1,6 +1,6 @@ import * as admin from 'firebase-admin'; -import {DeployData} from '../../model/data/deploy'; +import {DeckData} from '../../model/data/deck'; export function successfulDeploy(deckId: string, type: 'github' | 'api'): Promise { return new Promise(async (resolve, reject) => { @@ -34,19 +34,25 @@ function updateStatus(deckId: string, type: 'github' | 'api', status: 'scheduled return; } - const documentReference: admin.firestore.DocumentReference = admin.firestore().doc(`/deploys/${deckId}/`); + const documentReference: admin.firestore.DocumentReference = admin.firestore().doc(`/decks/${deckId}/`); - const updateData: Partial = { + const updateData: Partial = { updated_at: admin.firestore.Timestamp.now(), }; if (type === 'github') { - updateData.github = { - status, + updateData.deploy = { + github: { + status, + updated_at: admin.firestore.Timestamp.now(), + }, }; } else if (type === 'api') { - updateData.api = { - status, + updateData.deploy = { + api: { + status, + updated_at: admin.firestore.Timestamp.now(), + }, }; } 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 b1bf8fdfa..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/data/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/publish/api/publish-api.ts b/cloud/functions/src/watch/publish/api/publish-api.ts index 94dde9336..72926c874 100644 --- a/cloud/functions/src/watch/publish/api/publish-api.ts +++ b/cloud/functions/src/watch/publish/api/publish-api.ts @@ -7,7 +7,7 @@ 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/deploy-utils'; +import {failureDeploy, successfulDeploy} from '../../../utils/data/deck-deploy-utils'; import {Resources} from '../../../utils/resources/resources'; diff --git a/cloud/functions/src/watch/publish/github/publish-github.ts b/cloud/functions/src/watch/publish/github/publish-github.ts index 0ed43d045..fe972778f 100644 --- a/cloud/functions/src/watch/publish/github/publish-github.ts +++ b/cloud/functions/src/watch/publish/github/publish-github.ts @@ -1,12 +1,11 @@ -import {DeckData} from '../../../model/data/deck'; +import {DeckData, DeckGitHubRepo} from '../../../model/data/deck'; import {Token} from '../../../model/data/token'; -import {DeployGitHubRepo} from '../../../model/data/deploy'; 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/deploy-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) => { @@ -46,7 +45,7 @@ export async function publishToGitHub(deckId: string, deckData: DeckData): Promi // Get or create GitHub repo / project - const repo: DeployGitHubRepo | undefined = await getRepo(platform.data.github.token, user, deckData.owner_id, deckId, deckData.meta); + 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.'); diff --git a/cloud/functions/src/watch/publish/github/utils/github-api.ts b/cloud/functions/src/watch/publish/github/utils/github-api.ts index 90fbae9f1..bbd9cf538 100644 --- a/cloud/functions/src/watch/publish/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/data/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/publish/github/utils/github-db.ts b/cloud/functions/src/watch/publish/github/utils/github-db.ts index 1646a9a0c..7eb27c32c 100644 --- a/cloud/functions/src/watch/publish/github/utils/github-db.ts +++ b/cloud/functions/src/watch/publish/github/utils/github-db.ts @@ -1,7 +1,7 @@ import * as admin from 'firebase-admin'; import {Token, TokenData} from '../../../../model/data/token'; -import {DeployGitHubRepo, Deploy, DeployData, DeployGitHub} from '../../../../model/data/deploy'; +import {DeckData, DeckGitHubRepo} from '../../../../model/data/deck'; export function findToken(userId: string): Promise { return new Promise(async (resolve, reject) => { @@ -26,30 +26,7 @@ export function findToken(userId: string): Promise { }); } -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 updateGitHubDeploy(deckId: string, deckData: DeployData, repo: DeployGitHubRepo | undefined): Promise { +export function updateDeckGitHub(deckId: string, repo: DeckGitHubRepo | undefined): Promise { return new Promise(async (resolve, reject) => { try { if (!deckId || deckId === undefined || deckId === '') { @@ -62,12 +39,14 @@ export function updateGitHubDeploy(deckId: string, deckData: DeployData, repo: D return; } - const data: DeployData = {...deckData}; - (data.github as DeployGitHub).repo = {...repo}; - - data.updated_at = admin.firestore.Timestamp.now(); + const data: Partial = { + updated_at: admin.firestore.Timestamp.now(), + github: { + repo: {...repo}, + }, + }; - const documentReference: admin.firestore.DocumentReference = admin.firestore().doc(`/deploys/${deckId}`); + const documentReference: admin.firestore.DocumentReference = admin.firestore().doc(`/decks/${deckId}`); await documentReference.set(data, {merge: true}); diff --git a/cloud/functions/src/watch/publish/github/utils/github-utils.ts b/cloud/functions/src/watch/publish/github/utils/github-utils.ts index 7f64ab69c..fbead7587 100644 --- a/cloud/functions/src/watch/publish/github/utils/github-utils.ts +++ b/cloud/functions/src/watch/publish/github/utils/github-utils.ts @@ -1,56 +1,45 @@ -import {DeckMeta} from '../../../../model/data/deck'; -import {DeployGitHubRepo, Deploy, DeployData, DeployGitHub} from '../../../../model/data/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, updateGitHubDeploy} 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 && deploy.data && deploy.data.github && deploy.data.github.repo) { - const existingRepo: DeployGitHubRepo | undefined = await findRepo(githubToken, user, deploy.data.github.repo.name); + if (deckData.github) { + 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 updateGitHubDeploy(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 updateGitHubDeploy(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, - } as DeployGitHub, - }; - - await updateGitHubDeploy(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 updateGitHubDeck(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/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) { From 30d6fcd698754591fef42b18c2c6438259e0c7eb Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Sat, 5 Sep 2020 09:23:39 +0200 Subject: [PATCH 26/30] feat: remove collection deploy and use deck --- .../app-publish-done/app-publish-done.tsx | 5 +- .../app-publish-edit/app-publish-edit.tsx | 43 ++++++------- .../modals/editor/app-publish/app-publish.tsx | 8 +-- studio/src/app/models/data/deck.tsx | 25 ++++++++ studio/src/app/models/data/deploy.tsx | 30 --------- .../services/data/deploy/deploy.service.ts | 61 ------------------- .../editor/publish/publish.service.tsx | 46 +++++++++----- studio/src/app/stores/deck.store.ts | 2 +- studio/src/app/stores/deploy.store.ts | 13 ---- 9 files changed, 81 insertions(+), 152 deletions(-) delete mode 100644 studio/src/app/models/data/deploy.tsx delete mode 100644 studio/src/app/services/data/deploy/deploy.service.ts delete mode 100644 studio/src/app/stores/deploy.store.ts 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..f56c6c143 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', @@ -59,7 +58,7 @@ export class AppPublishDone { return undefined; } - if (!deployStore.state.deploy || !deployStore.state.deploy.data) { + if (!deckStore.state.deck.data.github) { 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 4450932fe..0109624f1 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 @@ -7,10 +7,8 @@ import errorStore from '../../../../stores/error.store'; import feedStore from '../../../../stores/feed.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'; -import {Deploy} from '../../../../models/data/deploy'; import {Resources} from '../../../../utils/core/resources'; @@ -63,7 +61,7 @@ export class AppPublishEdit { private publishService: PublishService; - private destroyDeployListener; + private destroyDeckListener; constructor() { this.deckService = DeckService.getInstance(); @@ -78,12 +76,11 @@ export class AppPublishEdit { async componentWillLoad() { await this.init(); - this.destroyDeployListener = deployStore.onChange('deploy', async (_deploy: Deploy | undefined) => { + this.destroyDeckListener = deckStore.onChange('deck', async (deck: Deck | undefined) => { + // Deck is maybe updating while we have set it to true manually this.publishing = - deployStore.state.deploy && - deployStore.state.deploy.data && - deployStore.state.deploy.data.api && - (deployStore.state.deploy.data.api.status === 'scheduled' || deployStore.state.deploy.data.api.status === 'failure'); + this.publishing || + (deck.data.deploy && deck.data.deploy.api && (deck.data.deploy.api.status === 'scheduled' || deck.data.deploy.api.status === 'failure')); }); } @@ -92,8 +89,8 @@ export class AppPublishEdit { } disconnectedCallback() { - if (this.destroyDeployListener) { - this.destroyDeployListener(); + if (this.destroyDeckListener) { + this.destroyDeckListener(); } } @@ -190,29 +187,25 @@ export class AppPublishEdit { } private onSuccessfulPublish() { - const destroyDeploySuccessfulListener = deployStore.onChange('deploy', async (deploy: Deploy | undefined) => { - if (deploy && deploy.data && deploy.data.api && deploy.data.api.status === 'successful') { - destroyDeploySuccessfulListener(); + 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(); - await this.delayRefreshDeck(); + // 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 delayRefreshDeck() { + private async delayNavigation(newApiId: boolean) { this.progress = 0; - const currentDeck: Deck = {...deckStore.state.deck}; - - await this.publishService.refreshDeck(currentDeck.id); - - // In case the user would have browse the feed before, reset it to fetch is updated or new presentation - feedStore.reset(); - - const newApiId: boolean = currentDeck.data.api_id !== deckStore.state.deck.data.api_id; - const interval = setInterval( () => { this.progress += 0.1; @@ -522,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/data/deck.tsx b/studio/src/app/models/data/deck.tsx index a343958f2..6786b7cc6 100644 --- a/studio/src/app/models/data/deck.tsx +++ b/studio/src/app/models/data/deck.tsx @@ -1,5 +1,26 @@ 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; +} + export interface DeckMetaAuthor { name: string; photo_url?: string; @@ -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 272759295..000000000 --- a/studio/src/app/models/data/deploy.tsx +++ /dev/null @@ -1,30 +0,0 @@ -export interface DeployGitHubRepo { - id: string; - url: string; - name: string; - nameWithOwner: string; -} - -export interface DeployGitHub { - repo?: DeployGitHubRepo; - status: 'scheduled' | 'failure' | 'successful'; -} - -export interface DeployApi { - status: 'scheduled' | 'failure' | 'successful'; -} - -export interface DeployData { - owner_id: string; - - github?: DeployGitHub; - - api?: DeployApi; - - updated_at?: firebase.firestore.Timestamp; -} - -export interface Deploy { - id: string; - data: DeployData | undefined; -} 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 229354ac5..000000000 --- a/studio/src/app/services/data/deploy/deploy.service.ts +++ /dev/null @@ -1,61 +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) => { - deployStore.reset(); - - const deck: Deck = deckStore.state.deck; - - if (!deck || !deck.id) { - resolve(undefined); - return; - } - - if (!authStore.state.gitHub) { - 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 = 'Cannont retrieve the deploy information.'; - } - ); - - 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 662d313b8..9d00508e1 100644 --- a/studio/src/app/services/editor/publish/publish.service.tsx +++ b/studio/src/app/services/editor/publish/publish.service.tsx @@ -4,8 +4,9 @@ import 'firebase/auth'; import deckStore from '../../../stores/deck.store'; import userStore from '../../../stores/user.store'; +import errorStore from '../../../stores/error.store'; -import {Deck, DeckMetaAuthor} from '../../../models/data/deck'; +import {Deck, DeckData, DeckMetaAuthor} from '../../../models/data/deck'; import {UserSocial} from '../../../models/data/user'; @@ -83,20 +84,6 @@ export class PublishService { }); } - // Otherwise we gonna kept in memory references like firebase.firestore.FieldValue.delete instead of null values - 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); - } - }); - } - private updateDeckMeta(description: string, tags: string[], github: boolean): Promise { return new Promise(async (resolve, reject) => { try { @@ -171,4 +158,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/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}; From b31aaf1ca7d7b1e36683f93574d22d93d0f16cce Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Sat, 5 Sep 2020 09:46:49 +0200 Subject: [PATCH 27/30] refactor: github publish information --- cloud/functions/src/model/data/deck.ts | 4 ++-- .../src/watch/publish/github/utils/github-db.ts | 4 ++-- .../watch/publish/github/utils/github-utils.ts | 2 +- .../app-publish-done/app-publish-done.tsx | 4 ++-- .../app-publish-edit/app-publish-edit.tsx | 2 +- studio/src/app/models/data/deck.tsx | 4 ++-- .../services/editor/publish/publish.service.tsx | 16 +++++++++++++--- 7 files changed, 23 insertions(+), 13 deletions(-) diff --git a/cloud/functions/src/model/data/deck.ts b/cloud/functions/src/model/data/deck.ts index 88ace1317..77626364f 100644 --- a/cloud/functions/src/model/data/deck.ts +++ b/cloud/functions/src/model/data/deck.ts @@ -20,7 +20,8 @@ export interface DeckGitHubRepo { } export interface DeckGitHub { - repo: DeckGitHubRepo; + repo?: DeckGitHubRepo; + publish: boolean; } export interface DeckMetaAuthor { @@ -43,7 +44,6 @@ export interface DeckMeta { published_at: firestore.Timestamp; feed: boolean; - github?: boolean; updated_at: firestore.Timestamp; } diff --git a/cloud/functions/src/watch/publish/github/utils/github-db.ts b/cloud/functions/src/watch/publish/github/utils/github-db.ts index 7eb27c32c..05b3406d5 100644 --- a/cloud/functions/src/watch/publish/github/utils/github-db.ts +++ b/cloud/functions/src/watch/publish/github/utils/github-db.ts @@ -1,7 +1,7 @@ import * as admin from 'firebase-admin'; import {Token, TokenData} from '../../../../model/data/token'; -import {DeckData, DeckGitHubRepo} from '../../../../model/data/deck'; +import {DeckData, DeckGitHub, DeckGitHubRepo} from '../../../../model/data/deck'; export function findToken(userId: string): Promise { return new Promise(async (resolve, reject) => { @@ -43,7 +43,7 @@ export function updateDeckGitHub(deckId: string, repo: DeckGitHubRepo | undefine updated_at: admin.firestore.Timestamp.now(), github: { repo: {...repo}, - }, + } as DeckGitHub, }; const documentReference: admin.firestore.DocumentReference = admin.firestore().doc(`/decks/${deckId}`); diff --git a/cloud/functions/src/watch/publish/github/utils/github-utils.ts b/cloud/functions/src/watch/publish/github/utils/github-utils.ts index fbead7587..cfbb1e109 100644 --- a/cloud/functions/src/watch/publish/github/utils/github-utils.ts +++ b/cloud/functions/src/watch/publish/github/utils/github-utils.ts @@ -15,7 +15,7 @@ export async function getRepo(githubToken: string, user: GitHubUser, userId: str const project: string = deckData.meta.title.replace(' ', '-').toLowerCase(); const description: string = deckData.meta.description ? (deckData.meta.description as string) : ''; - if (deckData.github) { + if (deckData.github && deckData.github.repo) { const existingRepo: DeckGitHubRepo | undefined = await findRepo(githubToken, user, deckData.github.repo.name); if (existingRepo) { 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 f56c6c143..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 @@ -54,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 (!deckStore.state.deck.data.github) { + if (!deckStore.state.deck.data.github || !deckStore.state.deck.data.github.repo) { return ( The source code of the presentation is processing 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 0109624f1..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 @@ -105,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 { diff --git a/studio/src/app/models/data/deck.tsx b/studio/src/app/models/data/deck.tsx index 6786b7cc6..526dea6c1 100644 --- a/studio/src/app/models/data/deck.tsx +++ b/studio/src/app/models/data/deck.tsx @@ -18,7 +18,8 @@ export interface DeckGitHubRepo { } export interface DeckGitHub { - repo: DeckGitHubRepo; + repo?: DeckGitHubRepo; + publish: boolean; } export interface DeckMetaAuthor { @@ -41,7 +42,6 @@ export interface DeckMeta { published_at?: firebase.firestore.Timestamp; feed?: boolean; - github?: boolean; updated_at: firebase.firestore.Timestamp; } diff --git a/studio/src/app/services/editor/publish/publish.service.tsx b/studio/src/app/services/editor/publish/publish.service.tsx index 9d00508e1..a30df0cd8 100644 --- a/studio/src/app/services/editor/publish/publish.service.tsx +++ b/studio/src/app/services/editor/publish/publish.service.tsx @@ -5,6 +5,7 @@ import 'firebase/auth'; import deckStore from '../../../stores/deck.store'; import userStore from '../../../stores/user.store'; import errorStore from '../../../stores/error.store'; +import authStore from '../../../stores/auth.store'; import {Deck, DeckData, DeckMetaAuthor} from '../../../models/data/deck'; @@ -68,7 +69,7 @@ export class PublishService { deckId: deck.id, ownerId: deck.data.owner_id, publish: true, - github: deck.data.meta.github, + github: deck.data.github ? deck.data.github.publish : false, }), }); @@ -99,13 +100,11 @@ export class PublishService { if (!deck.data.meta) { deck.data.meta = { title: deck.data.name, - github: github, updated_at: now, }; } else { deck.data.meta.title = deck.data.name; deck.data.meta.updated_at = now; - deck.data.meta.github = github; } if (description && description !== undefined && description !== '') { @@ -149,6 +148,17 @@ export class PublishService { deck.data.meta.author = firebase.firestore.FieldValue.delete(); } + // 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}; From c10031980ef8b73b2d4f921fd5c4d9ebf2ad9de0 Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Sat, 5 Sep 2020 12:08:25 +0200 Subject: [PATCH 28/30] fix: prerendering --- studio/src/app/services/api/user/api.user.service.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 47a5e5131..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,5 +1,5 @@ -import * as firebase from 'firebase'; -import 'firebase/auth'; +import firebase from '@firebase/app'; +import '@firebase/auth'; import apiUserStore from '../../../stores/api.user.store'; From 26385c72a40c3b0783ecda8002035c3966f1648d Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Sat, 5 Sep 2020 14:05:22 +0200 Subject: [PATCH 29/30] feat: skip options --- cloud/config/set-cloud-config-dev.sh | 4 ++++ cloud/functions/src/watch/info/info-deck-publish.ts | 6 ++++++ .../src/watch/publish/github/publish-github.ts | 11 +++++++++++ 3 files changed, 21 insertions(+) create mode 100755 cloud/config/set-cloud-config-dev.sh 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/functions/src/watch/info/info-deck-publish.ts b/cloud/functions/src/watch/info/info-deck-publish.ts index 17bcf837f..3cca5c2db 100644 --- a/cloud/functions/src/watch/info/info-deck-publish.ts +++ b/cloud/functions/src/watch/info/info-deck-publish.ts @@ -46,6 +46,12 @@ function isFirstTimePublished(previousValue: DeckData, newValue: DeckData): Prom function sendInfo(deckId: string, deckData: DeckData): Promise { 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; diff --git a/cloud/functions/src/watch/publish/github/publish-github.ts b/cloud/functions/src/watch/publish/github/publish-github.ts index fe972778f..f889dee6c 100644 --- a/cloud/functions/src/watch/publish/github/publish-github.ts +++ b/cloud/functions/src/watch/publish/github/publish-github.ts @@ -1,3 +1,5 @@ +import * as functions from 'firebase-functions'; + import {DeckData, DeckGitHubRepo} from '../../../model/data/deck'; import {Token} from '../../../model/data/token'; @@ -25,6 +27,15 @@ export async function publishToGitHub(deckId: string, deckData: DeckData): Promi 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); From 1f803c1c68a7597a8c29b4e1f4afe5b22b857c5b Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Sat, 5 Sep 2020 14:05:42 +0200 Subject: [PATCH 30/30] chore: missing definition --- .../functions/src/request/publish/schedule-publish-task.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/cloud/functions/src/request/publish/schedule-publish-task.ts b/cloud/functions/src/request/publish/schedule-publish-task.ts index bd4f293b1..e9465338b 100644 --- a/cloud/functions/src/request/publish/schedule-publish-task.ts +++ b/cloud/functions/src/request/publish/schedule-publish-task.ts @@ -6,7 +6,12 @@ import {geToken} from '../utils/request-utils'; import {DeckData, DeckDeployData} from '../../model/data/deck'; -export interface ScheduledPublishTask {} +export interface ScheduledPublishTask { + deckId: string; + status: 'scheduled'; + publish: boolean; + github: boolean; +} export function schedulePublish(request: functions.Request): Promise { return new Promise(async (resolve, reject) => {