diff --git a/package-lock.json b/package-lock.json index d0d1e0fde..52d8bf200 100644 --- a/package-lock.json +++ b/package-lock.json @@ -217,6 +217,17 @@ "negotiator": "0.6.2" } }, + "ajv": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", + "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, "apollo-cache-control": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/apollo-cache-control/-/apollo-cache-control-0.6.0.tgz", @@ -440,6 +451,19 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, "async": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", @@ -466,6 +490,16 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" + }, "axios": { "version": "0.18.0", "resolved": "https://registry.npmjs.org/axios/-/axios-0.18.0.tgz", @@ -475,11 +509,75 @@ "is-buffer": "^1.1.5" } }, + "azure-storage": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/azure-storage/-/azure-storage-2.10.3.tgz", + "integrity": "sha512-IGLs5Xj6kO8Ii90KerQrrwuJKexLgSwYC4oLWmc11mzKe7Jt2E5IVg+ZQ8K53YWZACtVTMBNO3iGuA+4ipjJxQ==", + "requires": { + "browserify-mime": "~1.2.9", + "extend": "^3.0.2", + "json-edm-parser": "0.1.2", + "md5.js": "1.3.4", + "readable-stream": "~2.0.0", + "request": "^2.86.0", + "underscore": "~1.8.3", + "uuid": "^3.0.0", + "validator": "~9.4.1", + "xml2js": "0.2.8", + "xmlbuilder": "^9.0.7" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" + }, + "readable-stream": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "~1.0.0", + "process-nextick-args": "~1.0.6", + "string_decoder": "~0.10.x", + "util-deprecate": "~1.0.1" + } + }, + "sax": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/sax/-/sax-0.5.8.tgz", + "integrity": "sha1-1HLbIo6zMcJQaw6MFVJK25OdEsE=" + }, + "xml2js": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.2.8.tgz", + "integrity": "sha1-m4FpCTFjH/CdGVdUn69U9PmAs8I=", + "requires": { + "sax": "0.5.x" + } + } + } + }, "backo2": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=" }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "requires": { + "tweetnacl": "^0.14.3" + } + }, "bluebird": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", @@ -502,6 +600,11 @@ "type-is": "~1.6.17" } }, + "browserify-mime": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/browserify-mime/-/browserify-mime-1.2.9.tgz", + "integrity": "sha1-rrGvKN5sDXpqLOQK22j/GEIq8x8=" + }, "bson": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/bson/-/bson-1.0.9.tgz", @@ -521,6 +624,11 @@ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, "color": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz", @@ -618,6 +726,14 @@ "vary": "^1" } }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "^1.0.0" + } + }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -678,6 +794,15 @@ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-6.0.0.tgz", "integrity": "sha512-FlWbnhgjtwD+uNLUGHbMykMOYQaTivdHEmYwAKFjn6GKe/CqY0fNae93ZHTd20snh9ZLr8mTzIL9m0APQ1pjQg==" }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -880,6 +1005,21 @@ } } }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + }, "fast-json-stable-stringify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", @@ -939,6 +1079,11 @@ } } }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, "form-data": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", @@ -969,6 +1114,14 @@ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "^1.0.0" + } + }, "graphql": { "version": "0.13.2", "resolved": "https://registry.npmjs.org/graphql/-/graphql-0.13.2.tgz", @@ -1039,6 +1192,20 @@ } } }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -1052,6 +1219,15 @@ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=" }, + "hash-base": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", + "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, "http-errors": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", @@ -1064,6 +1240,16 @@ "toidentifier": "1.0.0" } }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -1123,16 +1309,75 @@ "has-symbols": "^1.0.0" } }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, "isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, "iterall": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.2.2.tgz", "integrity": "sha512-yynBb1g+RFUPY64fTrFv7nsjRrENBQJaX2UL+2Szc9REFrSNm1rpSXHGzhmAy7a9uv3vlvgBlXnf9RqmPH1/DA==" }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "json-beautify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/json-beautify/-/json-beautify-1.1.0.tgz", + "integrity": "sha512-Y8lEcqhyJKRq2IhYBGtRIQO7EgOlyeSCYXnQCzAnGO5DhCBzMBEZ90PTw620NwpfQccJ/sj4+bU+r4GBI0b2Lw==" + }, + "json-edm-parser": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/json-edm-parser/-/json-edm-parser-0.1.2.tgz", + "integrity": "sha1-HmCw/vG8CvZ7wNFG393lSGzWFbQ=", + "requires": { + "jsonparse": "~1.2.0" + } + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "jsonparse": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.2.0.tgz", + "integrity": "sha1-XAxWhRBxYOcv50ib3eoLRMK8Z70=" + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, "kareem": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.0.tgz", @@ -1193,6 +1438,15 @@ "yallist": "^3.0.2" } }, + "md5.js": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.4.tgz", + "integrity": "sha1-6b296UogpawYsENA/Fdk1bCdkB0=", + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -1326,6 +1580,11 @@ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -1373,6 +1632,11 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, "process-nextick-args": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", @@ -1466,6 +1730,54 @@ "resolved": "https://registry.npmjs.org/regexp-clone/-/regexp-clone-0.0.1.tgz", "integrity": "sha1-p8LgmJH9vzj7sQ03b7cwA+aKxYk=" }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + } + } + } + }, "require_optional": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz", @@ -1604,6 +1916,22 @@ "memory-pager": "^1.0.2" } }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, "stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", @@ -1688,6 +2016,19 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==" }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, "type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -1697,11 +2038,24 @@ "mime-types": "~2.1.24" } }, + "underscore": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", + "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=" + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "requires": { + "punycode": "^2.1.0" + } + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -1726,11 +2080,26 @@ "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" }, + "validator": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/validator/-/validator-9.4.1.tgz", + "integrity": "sha512-YV5KjzvRmSyJ1ee/Dm5UED0G+1L4GZnLN3w6/T+zZm8scVua4sOhYKWTUrKa0H/tMiJyO9QLHMPN+9mB/aMunA==" + }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, "winston": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/winston/-/winston-3.2.1.tgz", diff --git a/package.json b/package.json index 1e9b23411..6dab68f4f 100644 --- a/package.json +++ b/package.json @@ -19,10 +19,12 @@ "@azure/storage-blob": "^10.3.0", "apollo-server-express": "2.1.0", "axios": "0.18.0", + "azure-storage": "^2.10.3", "dotenv": "6.0.0", "esm": "^3.2.22", "express": "4.16.3", "graphql": "0.13.2", + "json-beautify": "^1.1.0", "mongoose": "5.3.4", "winston": "3.2.1" } diff --git a/src/app.js b/src/app.js index 2b29d8a1a..69f1f14ce 100644 --- a/src/app.js +++ b/src/app.js @@ -4,6 +4,7 @@ import { ApolloServer } from 'apollo-server-express'; import schema from './schema'; import wrongPreshared from './errors/notAllowed.json'; import db from './db'; +import logger from './utils/logger' const app = express(); @@ -42,12 +43,12 @@ server.applyMiddleware({ }); db.on('error', (err) => { - console.log(err); + logger.error(err); }); db.once('open', () => { - console.log('Connected to the DB.'); + logger.info('Connected to the DB.'); }); app.listen(process.env.PORT, () => { - console.log(`Listening on port ${process.env.PORT}`); + logger.info(`Listening on port ${process.env.PORT}`); }); diff --git a/src/models/ecosystem.js b/src/models/ecosystem.js index 85384b9e8..5856d8f19 100644 --- a/src/models/ecosystem.js +++ b/src/models/ecosystem.js @@ -20,6 +20,10 @@ const ecosystemSchema = new Schema({ type: String, required: true }, + storage: { + type: String, + required: true + }, container: { type: String, required: true @@ -30,13 +34,21 @@ const ecosystemSchema = new Schema({ } }, bots: [{ - repo: { + codeName: { type: String, required: true }, - configFile: { + displayName: { type: String, required: true + }, + repo: { + type: String, + required: false + }, + configFile: { + type: String, + required: false } }], plotters: [{ @@ -64,6 +76,10 @@ const ecosystemSchema = new Schema({ type: String, required: true }, + storage: { + type: String, + required: true + }, container: { type: String, required: true diff --git a/src/schema/mutations/AddTeam.js b/src/schema/mutations/AddTeam.js index 69cdd86c2..44e6402d0 100644 --- a/src/schema/mutations/AddTeam.js +++ b/src/schema/mutations/AddTeam.js @@ -1,17 +1,11 @@ -import { AuthenticationError } from '../../errors'; -import { TeamType } from '../types'; -import { Ecosystem } from '../../models'; -import { TeamInputType } from '../types/input'; -import logger from '../../utils/logger'; -import { createContainer } from '../../storage/AzureStorage'; +import logger from '../../utils/logger' +import { AuthenticationError, WrongArgumentsError } from '../../errors' +import { TeamType } from '../types' +import { Ecosystem } from '../../models' +import { TeamInputType } from '../types/input' +import { createTeam } from '../../storage/CreateTeam' -import { - GraphQLString -} from 'graphql'; - -export const args = { - team: { type: TeamInputType }, -}; +export const args = { team: { type: TeamInputType } } const resolve = async (parent, { team }, context) => { logger.debug('addTeam -> Entering Fuction.') @@ -33,36 +27,42 @@ const resolve = async (parent, { team }, context) => { userEcosystem.authId = context.userId } - let containerName = team.codeName - await createContainer(containerName) - // let accessKey = await createPrivateAccessKey() - let accessKey = "test" + for (let i = 0; i < userEcosystem.teams.length; i++) { + const auxTeam = userEcosystem.teams[i]; + if (auxTeam.codeName === team.codeName) { + throw new WrongArgumentsError("The team already exist on the user ecosystem.") + } + } + let accessKey = await createTeam(team) if (!team.host) { let nodeEndpoint = { url: process.env.SUPERALGOS_NODE_ENDPOINT, - container: containerName, + container: team.codeName, accessKey: accessKey, } team.host = nodeEndpoint } + let bots= [] + bots.push(team.bot) + team.bots = bots + userEcosystem.teams.push(team) await userEcosystem.save() - + return team } catch (err) { logger.error('addTeam -> Error: %s', err.stack) - // throw new ServerError('There has been an error adding the team.') - return 'There has been an error adding the team.' + throw err } -}; +} const mutation = { addTeam: { - type: GraphQLString, + type: TeamType, args, resolve, - }, -}; + } +} export default mutation; diff --git a/src/schema/mutations/DeleteTeam.js b/src/schema/mutations/DeleteTeam.js new file mode 100644 index 000000000..85f4c403a --- /dev/null +++ b/src/schema/mutations/DeleteTeam.js @@ -0,0 +1,60 @@ +import logger from '../../utils/logger' +import { AuthenticationError, WrongArgumentsError } from '../../errors' +import { TeamType } from '../types' +import { Ecosystem } from '../../models' +import { TeamInputType } from '../types/input' +import { deleteTeam } from '../../storage/DeleteTeam' + +export const args = { team: { type: TeamInputType } } + +const resolve = async (parent, { team }, context) => { + logger.debug('deleteTeam -> Entering Fuction.') + + if (!context.userId) { + throw new AuthenticationError() + } + + try { + let userEcosystem = await Ecosystem.findOne({ + authId: context.userId + }) + + if (!userEcosystem) { + logger.debug('deleteTeam -> Ecosystem not found for the user.') + throw new WrongArgumentsError('The user does not have an ecosystem configured.') + } + + let teamFound = false + for (let i = 0; i < userEcosystem.teams.length; i++) { + const auxTeam = userEcosystem.teams[i] + if (auxTeam.codeName === team.codeName) { + logger.debug('deleteTeam -> Team found, removing.') + teamFound = true + await deleteTeam(team) + userEcosystem.teams.splice(i, 1) + await userEcosystem.save() + break + } + } + + if (!teamFound) { + throw new WrongArgumentsError('Team was not found on the user ecosystem.') + } else { + return team + } + + } catch (err) { + logger.error('deleteTeam -> Error: %s', err.stack) + throw err + } +} + +const mutation = { + deleteTeam: { + type: TeamType, + args, + resolve, + } +} + +export default mutation; diff --git a/src/schema/mutations/index.js b/src/schema/mutations/index.js index fd97d2baa..85ef11366 100644 --- a/src/schema/mutations/index.js +++ b/src/schema/mutations/index.js @@ -3,11 +3,13 @@ import { } from 'graphql'; import AddTeamMutation from './AddTeam'; +import DeleteTeamMutation from './DeleteTeam'; const Mutation = new GraphQLObjectType({ name: 'Mutation', fields: Object.assign( AddTeamMutation, + DeleteTeamMutation ), }); diff --git a/src/schema/queries/Ecosystem.js b/src/schema/queries/Ecosystem.js new file mode 100644 index 000000000..ca55b7fce --- /dev/null +++ b/src/schema/queries/Ecosystem.js @@ -0,0 +1,42 @@ +import logger from '../../utils/logger' +import { Ecosystem } from '../../models' +import { EcosystemType } from '../types' + +const args = {} + +const resolve = async (parent, args, context) => { + logger.debug('getEcosystem -> Entering Fuction.') + + try { + if (!context.userId) { + logger.debug('getEcosystem -> User not logged in, returning default ecosystem.') + let defaultEcosystem = require('../../config/ecosystem.json') + return defaultEcosystem + } else { + let userEcosystem = await Ecosystem.findOne({ + authId: context.userId + }) + + if (!userEcosystem) { + logger.debug('addTeam -> Ecosystem not found for the user, retrieving default.') + let defaultEcosystem = require('../../config/ecosystem.json') + return defaultEcosystem + } + + return userEcosystem + } + } catch (err) { + logger.error('getEcosystem -> Error: %s', err.stack) + throw err + } +} + +const query = { + getEcosystem: { + type: EcosystemType, + args, + resolve, + } +} + +export default query diff --git a/src/schema/queries/FileContent.js b/src/schema/queries/FileContent.js new file mode 100644 index 000000000..910fa8df5 --- /dev/null +++ b/src/schema/queries/FileContent.js @@ -0,0 +1,33 @@ +import logger from '../../utils/logger' +import { FileInputType } from '../types/input' +import { GraphQLString } from 'graphql' + +export const args = { file: { type: FileInputType } } + +const resolve = async (parent, { file }, context) => { + logger.debug('getFileContent -> Entering Fuction: ' + file.filePath) + + try { + + let storage = require('../../storage/providers/AzureStorage') // Default value + if (process.env.STORAGE_PROVIDER === 'Minio') { + storage = require('../storage/providers/MinioStorage') + } + + return storage.getFileContentRemote(file.container, file.filePath, file.storage, file.accessKey) + + } catch (err) { + logger.error('getFileContent -> Error: %s', err.stack) + throw err + } +} + +const query = { + fileContent: { + type: GraphQLString, + args, + resolve, + } +} + +export default query diff --git a/src/schema/queries/Teams.js b/src/schema/queries/Teams.js deleted file mode 100644 index 2c278e453..000000000 --- a/src/schema/queries/Teams.js +++ /dev/null @@ -1,21 +0,0 @@ -import { GraphQLList } from 'graphql'; -import { TeamType } from '../types'; -import { Ecosystem } from '../../models'; -import logger from '../../utils/logger' - -const args = {}; - -const resolve = (parent, args, context) => { - logger.debug('Listing teams.') - -} - -const query = { - teams: { - type: new GraphQLList(TeamType), - args, - resolve, - }, -}; - -export default query; diff --git a/src/schema/queries/index.js b/src/schema/queries/index.js index 5483b5df6..1a9a0fbf6 100644 --- a/src/schema/queries/index.js +++ b/src/schema/queries/index.js @@ -2,12 +2,14 @@ import { GraphQLObjectType, } from 'graphql'; -import TeamsQuery from './Teams'; +import EcosystemQuery from './Ecosystem'; +import FileContentQuery from './FileContent'; const RootQuery = new GraphQLObjectType({ name: 'RootQueryType', fields: Object.assign( - TeamsQuery + EcosystemQuery, + FileContentQuery ), }); diff --git a/src/schema/types/Bot.js b/src/schema/types/Bot.js index 7e02f2e38..6701c467e 100644 --- a/src/schema/types/Bot.js +++ b/src/schema/types/Bot.js @@ -6,6 +6,8 @@ import { const BotType = new GraphQLObjectType({ name: 'Bot', fields: () => ({ + codeName: { type: GraphQLString }, + displayName: { type: GraphQLString }, repo: { type: GraphQLString }, configFile: { type: GraphQLString } }), diff --git a/src/schema/types/Ecosystem.js b/src/schema/types/Ecosystem.js index 828361682..d31cbbde2 100644 --- a/src/schema/types/Ecosystem.js +++ b/src/schema/types/Ecosystem.js @@ -1,7 +1,8 @@ import { GraphQLObjectType, GraphQLString, - GraphQLID + GraphQLID, + GraphQLList } from 'graphql'; import { TeamType, EventHostType } from './index'; diff --git a/src/schema/types/EventHost.js b/src/schema/types/EventHost.js index 91e2c7533..571bab36e 100644 --- a/src/schema/types/EventHost.js +++ b/src/schema/types/EventHost.js @@ -1,6 +1,7 @@ import { GraphQLObjectType, - GraphQLString + GraphQLString, + GraphQLList } from 'graphql'; import { BotType } from './index'; diff --git a/src/schema/types/File.js b/src/schema/types/File.js new file mode 100644 index 000000000..bf3c11136 --- /dev/null +++ b/src/schema/types/File.js @@ -0,0 +1,17 @@ +import { + GraphQLObjectType, + GraphQLString +} from 'graphql'; + +const FileType = new GraphQLObjectType({ + name: 'File', + fields: () => ({ + container: { type: GraphQLString }, + filePath: { type: GraphQLString }, + storage: { type: GraphQLString }, + accessKey: { type: GraphQLString }, + fileContent: { type: GraphQLString } + }), +}); + +export default FileType; diff --git a/src/schema/types/Host.js b/src/schema/types/Host.js index 4c31de9e7..fbc45bcd1 100644 --- a/src/schema/types/Host.js +++ b/src/schema/types/Host.js @@ -7,6 +7,7 @@ const HostType = new GraphQLObjectType({ name: 'Host', fields: () => ({ url: { type: GraphQLString }, + storage: { type: GraphQLString }, container: { type: GraphQLString }, accessKey: { type: GraphQLString } }), diff --git a/src/schema/types/index.js b/src/schema/types/index.js index 8f8f70ce1..17d90c59a 100644 --- a/src/schema/types/index.js +++ b/src/schema/types/index.js @@ -2,10 +2,14 @@ import TeamType from './Team'; import BotType from './Bot'; import EventHostType from './EventHost'; import HostType from './Host'; +import EcosystemType from './Ecosystem'; +import FileType from './File'; export { TeamType, BotType, EventHostType, - HostType + HostType, + EcosystemType, + FileType }; diff --git a/src/schema/types/input/Bot.js b/src/schema/types/input/Bot.js index 75f10ebde..dfbadf5fb 100644 --- a/src/schema/types/input/Bot.js +++ b/src/schema/types/input/Bot.js @@ -1,12 +1,15 @@ import { GraphQLInputObjectType, - GraphQLString + GraphQLString, + GraphQLNonNull } from 'graphql'; const Type = new GraphQLInputObjectType({ name: 'BotInput', description: 'Payload for bot input.', fields: () => ({ + codeName: { type: new GraphQLNonNull(GraphQLString) }, + displayName: { type: new GraphQLNonNull(GraphQLString) }, repo: { type: GraphQLString }, configFile: { type: GraphQLString } }), diff --git a/src/schema/types/input/File.js b/src/schema/types/input/File.js new file mode 100644 index 000000000..d92d4c6f0 --- /dev/null +++ b/src/schema/types/input/File.js @@ -0,0 +1,19 @@ +import { + GraphQLInputObjectType, + GraphQLString, + GraphQLNonNull +} from 'graphql'; + +const Type = new GraphQLInputObjectType({ + name: 'FileInput', + description: 'Payload for file input.', + fields: () => ({ + container: { type: new GraphQLNonNull(GraphQLString) }, + filePath: { type: new GraphQLNonNull(GraphQLString) }, + storage: { type: new GraphQLNonNull(GraphQLString) }, + accessKey: { type: new GraphQLNonNull(GraphQLString) }, + fileContent: { type: GraphQLString } + }), +}); + +export default Type; diff --git a/src/schema/types/input/Host.js b/src/schema/types/input/Host.js index 9574ce095..6f9d4bb5f 100644 --- a/src/schema/types/input/Host.js +++ b/src/schema/types/input/Host.js @@ -8,6 +8,7 @@ const Type = new GraphQLInputObjectType({ description: 'Payload for host input.', fields: () => ({ url: { type: GraphQLString }, + storage: { type: GraphQLString }, container: { type: GraphQLString }, accessKey: { type: GraphQLString } }), diff --git a/src/schema/types/input/Team.js b/src/schema/types/input/Team.js index 878dca112..37f49a33c 100644 --- a/src/schema/types/input/Team.js +++ b/src/schema/types/input/Team.js @@ -14,8 +14,8 @@ const Type = new GraphQLInputObjectType({ codeName: { type: new GraphQLNonNull(GraphQLString) }, displayName: { type: new GraphQLNonNull(GraphQLString) }, host: { type: HostInputType }, - bots: { type: new GraphQLNonNull(new GraphQLList(BotInputType)) }, - plotters: { type: new GraphQLList(BotInputType) } + bot: { type: new GraphQLNonNull(BotInputType) }, + plotter: { type: BotInputType } }), }); diff --git a/src/schema/types/input/index.js b/src/schema/types/input/index.js index bee4b6f16..ec822c308 100644 --- a/src/schema/types/input/index.js +++ b/src/schema/types/input/index.js @@ -1,9 +1,11 @@ import TeamInputType from './Team' import BotInputType from './Bot' import HostInputType from './Host' +import FileInputType from './File' export { TeamInputType, BotInputType, - HostInputType + HostInputType, + FileInputType }; diff --git a/src/storage/CopyBot.js b/src/storage/CopyBot.js new file mode 100644 index 000000000..357f1c493 --- /dev/null +++ b/src/storage/CopyBot.js @@ -0,0 +1,60 @@ +import beautify from 'json-beautify' +import logger from '../utils/logger' + +export const copyBot = async (storage, teamCodeName, botCodeName, botDisplayName) => { + try { + logger.debug('copyBot started for bot %s', botCodeName) + + let containerName = 'aavikings' + let templatePath = 'AAVikings/bots/blumblebee-Trading-Bot' + let config = await storage.getFileContent(containerName, templatePath + '/this.bot.config.json') + let code = await storage.getFileContent(containerName, templatePath + '/Trading-Process/User.Bot.js') + + // Change config to adapt the new indicator + let parsedConfig = JSON.parse(config) + parsedConfig.displayName = botDisplayName + parsedConfig.codeName = botCodeName + parsedConfig.devTeam = teamCodeName + parsedConfig.profilePicture = botCodeName + '.png' + + parsedConfig.processes[0].statusDependencies[0].devTeam = teamCodeName + parsedConfig.processes[0].statusDependencies[0].bot = botCodeName + parsedConfig.processes[0].statusDependencies[1].devTeam = teamCodeName + parsedConfig.processes[0].statusDependencies[1].bot = 'simulator-' + botCodeName + parsedConfig.processes[0].statusDependencies[2].devTeam = teamCodeName + parsedConfig.processes[0].statusDependencies[2].bot = 'simulator-' + botCodeName + + parsedConfig.processes[0].dataDependencies[1].devTeam = teamCodeName + parsedConfig.processes[0].dataDependencies[1].bot = 'simulator-' + botCodeName + parsedConfig.processes[0].dataDependencies[2].devTeam = teamCodeName + parsedConfig.processes[0].dataDependencies[2].bot = 'simulator-' + botCodeName + + // Write the new files + let newBotPath = teamCodeName + '/bots/' + botCodeName + '-Trading-Bot' + await storage.writeFileContent(teamCodeName, newBotPath + '/this.bot.config.json', beautify(parsedConfig, null, 2, 80)) + await storage.writeFileContent(teamCodeName, newBotPath + '/Trading-Bot/User.Bot.js', code) + + logger.debug('copyBot completed for bot %s', botCodeName) + + } catch (err) { + logger.error('copyBot error for bot: %s. %s', botCodeName, err) + throw err + } +} + +export const removeBot = async (storage, teamCodeName, botCodeName) => { + try { + logger.debug('removeBot started for bot %s', botCodeName) + + // Delete bot code + let newBotPath = teamCodeName + '/bots/' + 'bot-' + botCodeName + await storage.deleteBlob(teamCodeName, newBotPath + '/this.bot.config.json') + await storage.deleteBlob(teamCodeName, newBotPath + '/Trading-Bot/User.Bot.js') + + logger.debug('removeBot completed for bot %s', botCodeName) + + } catch (err) { + logger.error('copyBot error for bot %s: %s', botCodeName, err) + throw err + } +} diff --git a/src/storage/CopySimulator.js b/src/storage/CopySimulator.js new file mode 100644 index 000000000..ca2466891 --- /dev/null +++ b/src/storage/CopySimulator.js @@ -0,0 +1,83 @@ +import beautify from 'json-beautify' +import logger from '../utils/logger' + +export const copySimulator = async (storage, teamCodeName, botCodeName, botDisplayName) => { + try { + logger.debug('copySimulator started for bot %s', 'simulator-' + botCodeName) + + let containerName = 'aamasters' + let templatePath = 'AAMasters/bots/AAJason-Indicator-Bot' + let commons = await storage.getFileContent(containerName, templatePath + '/Commons.js') + let config = await storage.getFileContent(containerName, templatePath + '/this.bot.config.json') + let codeDaily = await storage.getFileContent(containerName, templatePath + '/Multi-Period-Daily/User.Bot.js') + let codeMarket = await storage.getFileContent(containerName, templatePath + '/Multi-Period-Market/User.Bot.js') + + // Change config to adapt the new indicator + let newBotCodeName = 'simulator-' + botCodeName + let parsedConfig = JSON.parse(config) + parsedConfig.displayName = 'Simulator ' + botDisplayName + parsedConfig.codeName = newBotCodeName + parsedConfig.devTeam = teamCodeName + parsedConfig.profilePicture = newBotCodeName + '.png' + + parsedConfig.processes[0].statusDependencies[0].devTeam = teamCodeName + parsedConfig.processes[0].statusDependencies[0].bot = newBotCodeName + parsedConfig.processes[1].statusDependencies[2].devTeam = teamCodeName + parsedConfig.processes[1].statusDependencies[2].bot = newBotCodeName + + //Changing Indicator Output + parsedConfig.products[0].storageAccount = teamCodeName + parsedConfig.products[0].dataSets[0].filePath = teamCodeName + '/' + parsedConfig.codeName + '.1.0/AACloud.1.1/@Exchange/dataSet.V1/Output/Trading-Simulation/Multi-Period-Market/@Period' + parsedConfig.products[0].dataSets[1].filePath = teamCodeName + '/' + parsedConfig.codeName + '.1.0/AACloud.1.1/@Exchange/dataSet.V1/Output/Trading-Simulation/Multi-Period-Daily/@Period/@Year/@Month/@Day' + parsedConfig.products[0].dataSets[1].dataRange.filePath = teamCodeName + '/' + parsedConfig.codeName + '.1.0/AACloud.1.1/@Exchange/dataSet.V1/Output/Trading-Simulation/Multi-Period-Daily' + + parsedConfig.products[1].storageAccount = teamCodeName + parsedConfig.products[1].dataSets[0].filePath = teamCodeName + '/' + parsedConfig.codeName + '.1.0/AACloud.1.1/@Exchange/dataSet.V1/Output/Simulation-Conditions/Multi-Period-Market/@Period' + parsedConfig.products[1].dataSets[1].filePath = teamCodeName + '/' + parsedConfig.codeName + '.1.0/AACloud.1.1/@Exchange/dataSet.V1/Output/Simulation-Conditions/Multi-Period-Daily/@Period/@Year/@Month/@Day' + parsedConfig.products[1].dataSets[1].dataRange.filePath = teamCodeName + '/' + parsedConfig.codeName + '.1.0/AACloud.1.1/@Exchange/dataSet.V1/Output/Simulation-Conditions/Multi-Period-Daily' + + parsedConfig.products[2].storageAccount = teamCodeName + parsedConfig.products[2].dataSets[0].filePath = teamCodeName + '/' + parsedConfig.codeName + '.1.0/AACloud.1.1/@Exchange/dataSet.V1/Output/Simulation-Strategies/Multi-Period-Market/@Period' + parsedConfig.products[2].dataSets[1].filePath = teamCodeName + '/' + parsedConfig.codeName + '.1.0/AACloud.1.1/@Exchange/dataSet.V1/Output/Simulation-Strategies/Multi-Period-Daily/@Period/@Year/@Month/@Day' + parsedConfig.products[2].dataSets[1].dataRange.filePath = teamCodeName + '/' + parsedConfig.codeName + '.1.0/AACloud.1.1/@Exchange/dataSet.V1/Output/Simulation-Strategies/Multi-Period-Daily' + + parsedConfig.products[3].storageAccount = teamCodeName + parsedConfig.products[3].dataSets[0].filePath = teamCodeName + '/' + parsedConfig.codeName + '.1.0/AACloud.1.1/@Exchange/dataSet.V1/Output/Simulation-Trades/Multi-Period-Market/@Period' + parsedConfig.products[3].dataSets[1].filePath = teamCodeName + '/' + parsedConfig.codeName + '.1.0/AACloud.1.1/@Exchange/dataSet.V1/Output/Simulation-Trades/Multi-Period-Daily/@Period/@Year/@Month/@Day' + parsedConfig.products[3].dataSets[1].dataRange.filePath = teamCodeName + '/' + parsedConfig.codeName + '.1.0/AACloud.1.1/@Exchange/dataSet.V1/Output/Simulation-Trades/Multi-Period-Daily' + + // Write the new files + let newSimulatorName = 'simulator-' + newBotCodeName + '-Indicator-Bot' + let newSimulatorPath = teamCodeName + '/bots/' + newSimulatorName + await storage.writeFileContent(teamCodeName, newSimulatorPath + '/Commons.js', commons) + await storage.writeFileContent(teamCodeName, newSimulatorPath + '/this.bot.config.json', beautify(parsedConfig, null, 2, 80)) + await storage.writeFileContent(teamCodeName, newSimulatorPath + '/Multi-Period-Daily/User.Bot.js', codeDaily) + await storage.writeFileContent(teamCodeName, newSimulatorPath + '/Multi-Period-Market/User.Bot.js', codeMarket) + + logger.debug('copySimulator completed for bot %s', newBotCodeName) + + } catch (err) { + logger.error('copySimulator error for bot: %s. %s', botCodeName, err) + throw err + } +} + +export const removeSimulator = async (storage, teamCodeName, botCodeName) => { + try { + logger.debug('removeSimulator started for bot %s', botCodeName) + + // Delete simulator code + let newSimulatorName = 'simulator-' + botCodeName + '-Indicator-Bot' + let newSimulatorPath = teamCodeName + '/bots/' + newSimulatorName + await storage.deleteBlob(teamCodeName, newSimulatorPath + '/Commons.js') + await storage.deleteBlob(teamCodeName, newSimulatorPath + '/this.bot.config.json') + await storage.deleteBlob(teamCodeName, newSimulatorPath + '/Multi-Period-Daily/User.Bot.js') + await storage.deleteBlob(teamCodeName, newSimulatorPath + '/Multi-Period-Market/User.Bot.js') + + logger.debug('removeSimulator completed for bot %s', botCodeName) + + } catch (err) { + logger.error('copySimulator error for bot %s: %s', botCodeName, err) + throw err + } +} diff --git a/src/storage/CreateTeam.js b/src/storage/CreateTeam.js new file mode 100644 index 000000000..a2833250d --- /dev/null +++ b/src/storage/CreateTeam.js @@ -0,0 +1,27 @@ +import logger from '../utils/logger' +import { copyBot } from './CopyBot' +import { copySimulator } from './CopySimulator' + +export const createTeam = async (team) => { + try { + logger.debug('createTeam started for team %s', team.codeName) + + let storage = require ('./providers/AzureStorage') // Default value + if (process.env.STORAGE_PROVIDER === 'Minio') { + storage = require ('./providers/MinioStorage') + } + + let containerName = team.codeName + await storage.createContainer(containerName) + let accessKey = storage.createPrivateAccessKey(containerName, "WRITE", 365) + + await copyBot(storage, team.codeName, team.bot.codeName, team.bot.displayName) + await copySimulator(storage, team.codeName, team.bot.codeName, team.bot.displayName) + + return accessKey + + } catch (err) { + logger.error('createTeam error for teamCodeName: %s, error: %s', team.bot.codeName, err.stack) + throw err + } +} diff --git a/src/storage/DeleteTeam.js b/src/storage/DeleteTeam.js new file mode 100644 index 000000000..1f984d634 --- /dev/null +++ b/src/storage/DeleteTeam.js @@ -0,0 +1,18 @@ +import logger from '../utils/logger' + +export const deleteTeam = async (team) => { + try { + logger.debug('deleteTeam started for team %s', team.codeName) + + let storage = require ('./providers/AzureStorage') // Default value + if (process.env.STORAGE_PROVIDER === 'Minio') { + storage = require ('./providers/MinioStorage') + } + + await storage.deleteContainer(team.codeName) + + } catch (err) { + logger.error('deleteTeam error for teamCodeName: %s, error: %s', team.codeName, err.stack) + throw err + } +} diff --git a/src/storage/AzureStorage.js b/src/storage/providers/AzureStorage.js similarity index 51% rename from src/storage/AzureStorage.js rename to src/storage/providers/AzureStorage.js index ea2d49c07..0992f7efb 100644 --- a/src/storage/AzureStorage.js +++ b/src/storage/providers/AzureStorage.js @@ -1,37 +1,49 @@ -import logger from '../utils/logger' -const { +import logger from '../../utils/logger' +import { Aborter, BlobURL, BlockBlobURL, ContainerURL, ServiceURL, StorageURL, - SharedKeyCredential -} = require("@azure/storage-blob") + SharedKeyCredential, + AnonymousCredential +} from '@azure/storage-blob' const AzureServiceURL = () => { - // Enter your storage account name and shared key - const account = process.env.STORAGE_BASE_URL; - const accountKey = process.env.STORAGE_CONNECTION_STRING; - // Use SharedKeyCredential with storage account and account key - const sharedKeyCredential = new SharedKeyCredential(account, accountKey); + const sharedKeyCredential = new SharedKeyCredential(process.env.STORAGE_BASE_URL, process.env.STORAGE_CONNECTION_STRING); // Use sharedKeyCredential, tokenCredential or anonymousCredential to create a pipeline - const pipeline = StorageURL.newPipeline(sharedKeyCredential); + const pipeline = StorageURL.newPipeline(sharedKeyCredential, { + retryOptions: { maxTries: 10 } + }); // List containers const serviceURL = new ServiceURL( // When using AnonymousCredential, following url should include a valid SAS or support public access - `https://${account}.blob.core.windows.net`, + `https://${process.env.STORAGE_BASE_URL}.blob.core.windows.net`, pipeline - ); - logger.info("Connected to te file Storage.") + ) return serviceURL } const serviceURL = AzureServiceURL() +export const getFileContentRemote = async (containerName, blobName, storage, accessKey) => { + const pipeline = StorageURL.newPipeline(new AnonymousCredential(), { + retryOptions: { maxTries: 10 } + }); + const serviceURLWithAccessKey = new ServiceURL( + `${storage}${accessKey}`, + pipeline + ); + const containerURL = ContainerURL.fromServiceURL(serviceURLWithAccessKey, containerName) + const blockBlobURL = BlockBlobURL.fromContainerURL(containerURL, blobName) + let downloadResponse = await blockBlobURL.download(Aborter.none, 0) + return await streamToString(downloadResponse.readableStreamBody) +} + export const getFileContent = async (containerName, blobName) => { const containerURL = ContainerURL.fromServiceURL(serviceURL, containerName) const blockBlobURL = BlockBlobURL.fromContainerURL(containerURL, blobName) @@ -76,12 +88,52 @@ export const createContainer = async (containerName) => { async function streamToString(readableStream) { return new Promise((resolve, reject) => { const chunks = []; - readableStream.on("data", data => { + readableStream.on('data', data => { chunks.push(data.toString()); }); - readableStream.on("end", () => { - resolve(chunks.join("")); + readableStream.on('end', () => { + resolve(chunks.join('')); }); - readableStream.on("error", reject) + readableStream.on('error', reject) }); } + +export const createPrivateAccessKey = (container, type, days) => { + let azure = require('azure-storage'); + let blobService = azure.createBlobService(process.env.AZURE_STORAGE_CONNECTION_STRING); + + let startDate = new Date(); + startDate.setDate(startDate.getDate() - 1); + + let expiryDate = new Date(startDate); + expiryDate.setDate(startDate.getDate() + days); + + let azurePermissions; + + switch (type) { + case 'READ': { + azurePermissions = azure.BlobUtilities.SharedAccessPermissions.READ; + break; + } + case 'WRITE': { + azurePermissions = azure.BlobUtilities.SharedAccessPermissions.WRITE; + break; + } + case 'DELETE': { + azurePermissions = azure.BlobUtilities.SharedAccessPermissions.DELETE; + break; + } + } + + let blobName = undefined; + + let sharedAccessPolicy = { + AccessPolicy: { + Permissions: azurePermissions, + Start: startDate, + Expiry: expiryDate + } + }; + + return blobService.generateSharedAccessSignature(container, blobName, sharedAccessPolicy); +} diff --git a/src/utils/logger.js b/src/utils/logger.js index 9ef06e941..3932769cc 100644 --- a/src/utils/logger.js +++ b/src/utils/logger.js @@ -13,7 +13,7 @@ const options = { timestamp: true }, console: { - level: 'debug', + level: process.env.LOG_LEVEL || 'debug', handleExceptions: true, json: false, colorize: true, @@ -32,7 +32,7 @@ const logger = createLogger({ ) ), transports: [ - new transports.File(options.file), + // new transports.File(options.file), new transports.Console(options.console) ], exitOnError: false // do not exit on handled exceptions