diff --git a/.gitignore b/.gitignore index f043384..982d25a 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ test/*.log dist .idea config.yaml +sqz-force +greenvironment.db diff --git a/CHANGELOG.md b/CHANGELOG.md index 01a9eb2..3edaa34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,10 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Connection to Postgres Database - Graphql Schema - default-config file and generation of config file on startup - DTOs - Home Route -- database caching - session management +- Sequelize models and integration +- Sequelize-typescript integration diff --git a/package-lock.json b/package-lock.json index 44c83f2..6ccf67c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -45,6 +45,12 @@ "@types/babel-types": "*" } }, + "@types/bluebird": { + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.27.tgz", + "integrity": "sha512-6BmYWSBea18+tSjjSC3QIyV93ZKAeNWGM7R6aYt1ryTZXrlHF+QLV0G2yV0viEGVyRkyQsWfMoJ0k/YghBX5sQ==", + "dev": true + }, "@types/body-parser": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.17.1.tgz", @@ -84,6 +90,15 @@ "@types/pg": "*" } }, + "@types/continuation-local-storage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/continuation-local-storage/-/continuation-local-storage-3.2.2.tgz", + "integrity": "sha512-aItm+aYPJ4rT1cHmAxO+OdWjSviQ9iB5UKb5f0Uvgln0N4hS2mcDodHtPiqicYBXViUYhqyBjhA5uyOcT+S34Q==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/cookie-parser": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.2.tgz", @@ -186,6 +201,12 @@ "integrity": "sha512-Q7DYAOi9O/+cLLhdaSvKdaumWyHbm7HAk/bFwwyTuU0arR5yyCeW5GOoqt4tJTpDRxhpx9Q8kQL6vMpuw9hDSw==", "dev": true }, + "@types/lodash": { + "version": "4.14.144", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.144.tgz", + "integrity": "sha512-ogI4g9W5qIQQUhXAclq6zhqgqNUr7UlFaqDHbch7WLSLeeM/7d3CRaw7GLajxvyFvhJqw4Rpcz5bhoaYtIx6Tg==", + "dev": true + }, "@types/markdown-it": { "version": "0.0.9", "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-0.0.9.tgz", @@ -202,10 +223,9 @@ "dev": true }, "@types/node": { - "version": "12.7.8", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.8.tgz", - "integrity": "sha512-FMdVn84tJJdV+xe+53sYiZS4R5yn1mAIxfj+DVoNiQjTYz1+OYmjwEZr1ev9nU0axXwda0QDbYl06QHanRVH3A==", - "dev": true + "version": "12.7.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.12.tgz", + "integrity": "sha512-KPYGmfD0/b1eXurQ59fXD1GBzhSQfz6/lKBxkaHX9dKTzjXbK68Zt7yGUxUsCS1jeTy/8aL+d9JEr+S54mpkWQ==" }, "@types/pg": { "version": "7.11.0", @@ -232,6 +252,18 @@ "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==", "dev": true }, + "@types/sequelize": { + "version": "4.28.5", + "resolved": "https://registry.npmjs.org/@types/sequelize/-/sequelize-4.28.5.tgz", + "integrity": "sha512-1n2GJuTnUPzXzO4pv/m531y1lYkWjxgtv+FGIa3DDF30XZMPzXBWfDt/XpXaR64OQ/6CWwf5ZMwJ2CucO0VFvw==", + "dev": true, + "requires": { + "@types/bluebird": "*", + "@types/continuation-local-storage": "*", + "@types/lodash": "*", + "@types/validator": "*" + } + }, "@types/serve-static": { "version": "1.13.3", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.3.tgz", @@ -251,6 +283,12 @@ "@types/node": "*" } }, + "@types/validator": { + "version": "10.11.3", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-10.11.3.tgz", + "integrity": "sha512-GKF2VnEkMmEeEGvoo03ocrP9ySMuX1ypKazIYMlsjfslfBMhOAtC5dmEWKdJioW4lJN7MZRS88kalTsVClyQ9w==", + "dev": true + }, "@types/winston": { "version": "2.4.4", "resolved": "https://registry.npmjs.org/@types/winston/-/winston-2.4.4.tgz", @@ -263,8 +301,7 @@ "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, "accepts": { "version": "1.3.7", @@ -304,7 +341,6 @@ "version": "6.10.2", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", - "dev": true, "requires": { "fast-deep-equal": "^2.0.1", "fast-json-stable-stringify": "^2.0.0", @@ -374,8 +410,7 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, "ansi-styles": { "version": "3.2.1", @@ -392,6 +427,11 @@ "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=", "dev": true }, + "any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" + }, "anymatch": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", @@ -414,8 +454,7 @@ "aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "dev": true + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" }, "archy": { "version": "1.0.0", @@ -427,7 +466,6 @@ "version": "1.1.5", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", - "dev": true, "requires": { "delegates": "^1.0.0", "readable-stream": "^2.0.6" @@ -437,7 +475,6 @@ "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -451,14 +488,12 @@ "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==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, "requires": { "safe-buffer": "~5.1.0" } @@ -612,7 +647,6 @@ "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "dev": true, "requires": { "safer-buffer": "~2.1.0" } @@ -620,8 +654,7 @@ "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" }, "assign-symbols": { "version": "1.0.0", @@ -684,8 +717,7 @@ "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "atob": { "version": "2.1.2", @@ -696,14 +728,12 @@ "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=", - "dev": true + "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==", - "dev": true + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" }, "babel-code-frame": { "version": "6.26.0", @@ -802,8 +832,7 @@ "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "base": { "version": "0.11.2", @@ -885,7 +914,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "dev": true, "requires": { "tweetnacl": "^0.14.3" } @@ -918,6 +946,11 @@ "inherits": "~2.0.0" } }, + "bluebird": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.0.tgz", + "integrity": "sha512-aBQ1FxIa7kSWCcmKHlcHFlT2jt6J/l4FzC7KcPELkOJOsPOb/bccdhmIrKDfXhwFrmc7vDoDrrepFvGqjyXGJg==" + }, "body-parser": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", @@ -946,7 +979,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1048,8 +1080,7 @@ "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "dev": true + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, "center-align": { "version": "0.1.3", @@ -1116,6 +1147,11 @@ } } }, + "chownr": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.3.tgz", + "integrity": "sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==" + }, "class-utils": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", @@ -1237,11 +1273,19 @@ } } }, + "cls-bluebird": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cls-bluebird/-/cls-bluebird-2.1.0.tgz", + "integrity": "sha1-N+8eCAqP+1XC9BZPU28ZGeeWiu4=", + "requires": { + "is-bluebird": "^1.0.2", + "shimmer": "^1.1.0" + } + }, "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, "collection-map": { "version": "1.0.0", @@ -1324,7 +1368,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "requires": { "delayed-stream": "~1.0.0" } @@ -1388,8 +1431,7 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "concat-stream": { "version": "1.6.2", @@ -1435,19 +1477,34 @@ } } }, - "connect-pg-simple": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/connect-pg-simple/-/connect-pg-simple-6.0.1.tgz", - "integrity": "sha512-zW5AOtRNOLcXxphSmQ+oYj0snlLs1Je3u5K2NWyF7WhMVoPvnQXraK2wzS8f7qLwhMcmYukah2ymu0Gdxf7Qsg==", + "connect-session-sequelize": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/connect-session-sequelize/-/connect-session-sequelize-6.0.0.tgz", + "integrity": "sha512-XC71xJd5rqObdL7700S/qFD+gSRA4o6WVJAyFY0Vjah73id5bBElM0SHQR1ME5Bxrt4JL8alvggseNDVTlKyxA==", "requires": { - "pg": "^7.4.3" + "debug": "^3.1.0", + "deep-equal": "^1.0.1" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } } }, "console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "dev": true + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" }, "constantinople": { "version": "3.1.2", @@ -1599,7 +1656,6 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "dev": true, "requires": { "assert-plus": "^1.0.0" } @@ -1623,6 +1679,24 @@ "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", "dev": true }, + "deep-equal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.0.tgz", + "integrity": "sha512-ZbfWJq/wN1Z273o7mUSjILYqehAktR2NVoSrOukDkU9kg2v/Uv89yU4Cvz8seJeAmtN5oqiefKq8FPuXOboqLw==", + "requires": { + "is-arguments": "^1.0.4", + "is-date-object": "^1.0.1", + "is-regex": "^1.0.4", + "object-is": "^1.0.1", + "object-keys": "^1.1.1", + "regexp.prototype.flags": "^1.2.0" + } + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" + }, "default-compare": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/default-compare/-/default-compare-1.0.0.tgz", @@ -1650,7 +1724,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, "requires": { "object-keys": "^1.0.12" } @@ -1705,14 +1778,12 @@ "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, "delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", - "dev": true + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" }, "delete": { "version": "1.1.0", @@ -1742,6 +1813,11 @@ "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", "dev": true }, + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" + }, "diagnostics": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/diagnostics/-/diagnostics-1.1.1.tgz", @@ -1763,6 +1839,11 @@ "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz", "integrity": "sha1-6oCxBqh1OHdOijpKWv4pPeSJ4Kk=" }, + "dottie": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.1.tgz", + "integrity": "sha512-ch5OQgvGDK2u8pSZeSYAQaV/lczImd7pMJ7BcEPXmnFVjy4yJIzP6CsODJUTH8mg1tyH1Z2abOiuJO3DjZ/GBw==" + }, "duplexify": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", @@ -1821,7 +1902,6 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "dev": true, "requires": { "jsbn": "~0.1.0", "safer-buffer": "^2.1.0" @@ -2197,8 +2277,7 @@ "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, "extend-shallow": { "version": "2.0.1", @@ -2274,8 +2353,7 @@ "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "dev": true + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" }, "fancy-log": { "version": "1.3.3", @@ -2292,14 +2370,12 @@ "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=", - "dev": true + "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", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", - "dev": true + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" }, "fast-safe-stringify": { "version": "2.0.6", @@ -2449,14 +2525,12 @@ "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "dev": true + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" }, "form-data": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.6", @@ -2492,6 +2566,14 @@ "universalify": "^0.1.0" } }, + "fs-minipass": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", + "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", + "requires": { + "minipass": "^2.6.0" + } + }, "fs-mkdirp-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz", @@ -2547,8 +2629,7 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { "version": "1.2.9", @@ -2570,8 +2651,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -2592,14 +2672,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2614,20 +2692,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -2744,8 +2819,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -2757,7 +2831,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -2772,7 +2845,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -2780,14 +2852,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -2806,7 +2876,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -2887,8 +2956,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -2900,7 +2968,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -2986,8 +3053,7 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -3023,7 +3089,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -3043,7 +3108,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -3087,14 +3151,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, @@ -3115,16 +3177,10 @@ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, - "g": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/g/-/g-2.0.1.tgz", - "integrity": "sha1-C1lj69DKcOO8jGdmk0oCGCHIuFc=" - }, "gauge": { "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", - "dev": true, "requires": { "aproba": "^1.0.3", "console-control-strings": "^1.0.0", @@ -3140,7 +3196,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, "requires": { "ansi-regex": "^2.0.0" } @@ -3178,7 +3233,6 @@ "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "dev": true, "requires": { "assert-plus": "^1.0.0" } @@ -3187,7 +3241,6 @@ "version": "7.1.4", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", - "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -3573,14 +3626,12 @@ "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", - "dev": true + "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==", - "dev": true, "requires": { "ajv": "^6.5.5", "har-schema": "^2.0.0" @@ -3647,8 +3698,7 @@ "has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", - "dev": true + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" }, "has-value": { "version": "1.0.0", @@ -3720,7 +3770,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "dev": true, "requires": { "assert-plus": "^1.0.0", "jsprim": "^1.2.2", @@ -3745,6 +3794,14 @@ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" }, + "ignore-walk": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", + "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", + "requires": { + "minimatch": "^3.0.4" + } + }, "in-publish": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.0.tgz", @@ -3765,11 +3822,15 @@ "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=" }, + "inflection": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.12.0.tgz", + "integrity": "sha1-ogCTVlbW9fa8TcdQLhrstwMihBY=" + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -3783,8 +3844,7 @@ "ini": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", - "dev": true + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" }, "interpret": { "version": "1.2.0", @@ -3833,6 +3893,11 @@ } } }, + "is-arguments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", + "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==" + }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -3848,6 +3913,11 @@ "binary-extensions": "^1.0.0" } }, + "is-bluebird": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-bluebird/-/is-bluebird-1.0.2.tgz", + "integrity": "sha1-CWQ5Bg9KpBGr7hkUOoTWpVNG1uI=" + }, "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", @@ -3873,6 +3943,11 @@ } } }, + "is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=" + }, "is-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", @@ -3933,7 +4008,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, "requires": { "number-is-nan": "^1.0.0" } @@ -4012,8 +4086,7 @@ "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, "is-unc-path": { "version": "1.0.0", @@ -4062,8 +4135,7 @@ "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "dev": true + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, "iterall": { "version": "1.2.2", @@ -4099,20 +4171,17 @@ "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, "json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", - "dev": true + "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==", - "dev": true + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -4123,8 +4192,7 @@ "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=", - "dev": true + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, "jsonfile": { "version": "4.0.0", @@ -4138,7 +4206,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "dev": true, "requires": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", @@ -4544,7 +4611,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -4552,8 +4618,31 @@ "minimist": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + }, + "minipass": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", + "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + }, + "dependencies": { + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + } + } + }, + "minizlib": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", + "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", + "requires": { + "minipass": "^2.9.0" + } }, "mixin-deep": { "version": "1.3.2", @@ -4580,7 +4669,6 @@ "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, "requires": { "minimist": "0.0.8" }, @@ -4588,16 +4676,22 @@ "minimist": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" } } }, "moment": { "version": "2.24.0", "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", - "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==", - "dev": true + "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" + }, + "moment-timezone": { + "version": "0.5.26", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.26.tgz", + "integrity": "sha512-sFP4cgEKTCymBBKgoxZjYzlSovC20Y6J7y3nanDc5RoBIXKlZhoYwBoZGe3flwU6A372AcRwScH8KiwV6zjy1g==", + "requires": { + "moment": ">= 2.9.0" + } }, "ms": { "version": "2.0.0", @@ -4613,8 +4707,7 @@ "nan": { "version": "2.14.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", - "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", - "dev": true + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" }, "nanomatch": { "version": "1.2.13", @@ -4662,6 +4755,31 @@ } } }, + "needle": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.4.0.tgz", + "integrity": "sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==", + "requires": { + "debug": "^3.2.6", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, "negotiator": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", @@ -4701,6 +4819,53 @@ } } }, + "node-pre-gyp": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz", + "integrity": "sha512-TwWAOZb0j7e9eGaf9esRx3ZcLaE5tQ2lvYy1pb5IAaG1a2e2Kv5Lms1Y4hpj+ciXJRofIxxlt5haeQ/2ANeE0Q==", + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.1", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + }, + "dependencies": { + "nopt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "tar": { + "version": "4.4.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", + "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.8.6", + "minizlib": "^1.2.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.3" + } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + } + } + }, "node-sass": { "version": "4.12.0", "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.12.0.tgz", @@ -4801,11 +4966,24 @@ "once": "^1.3.2" } }, + "npm-bundled": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.6.tgz", + "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==" + }, + "npm-packlist": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.6.tgz", + "integrity": "sha512-u65uQdb+qwtGvEJh/DgQgW1Xg7sqeNbmxYyrvlNznaVTjV3E5P6F/EFjM+BVHXl7JJlsdG8A64M0XI8FI/IOlg==", + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, "npmlog": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", - "dev": true, "requires": { "are-we-there-yet": "~1.1.2", "console-control-strings": "~1.1.0", @@ -4816,14 +4994,12 @@ "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, "oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" }, "object-assign": { "version": "4.1.1", @@ -4866,11 +5042,15 @@ } } }, + "object-is": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.0.1.tgz", + "integrity": "sha1-CqYOyZiaCz7Xlc9NBvYs8a1lObY=" + }, "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" }, "object-visit": { "version": "1.0.1", @@ -4959,7 +5139,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "requires": { "wrappy": "1" } @@ -5031,8 +5210,7 @@ "os-homedir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", - "dev": true + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" }, "os-locale": { "version": "1.4.0", @@ -5046,14 +5224,12 @@ "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" }, "osenv": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", - "dev": true, "requires": { "os-homedir": "^1.0.0", "os-tmpdir": "^1.0.0" @@ -5141,8 +5317,7 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-parse": { "version": "1.0.6", @@ -5183,8 +5358,7 @@ "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "dev": true + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, "pg": { "version": "7.12.1", @@ -5371,8 +5545,7 @@ "psl": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.3.0.tgz", - "integrity": "sha512-avHdspHO+9rQTLbv1RO+MPYeP/SzsCoxofjVnHanETfQhTJrmB0HlDoW+EiN/R+C0BZ+gERab9NY0lPN2TxNag==", - "dev": true + "integrity": "sha512-avHdspHO+9rQTLbv1RO+MPYeP/SzsCoxofjVnHanETfQhTJrmB0HlDoW+EiN/R+C0BZ+gERab9NY0lPN2TxNag==" }, "pug": { "version": "2.0.4", @@ -5512,14 +5685,12 @@ "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", - "dev": true + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, "random-bytes": { "version": "1.0.0", @@ -5542,6 +5713,17 @@ "unpipe": "1.0.0" } }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + } + }, "read-pkg": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", @@ -5635,6 +5817,11 @@ "strip-indent": "^1.0.1" } }, + "reflect-metadata": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==" + }, "regenerator-runtime": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", @@ -5671,6 +5858,14 @@ } } }, + "regexp.prototype.flags": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.2.0.tgz", + "integrity": "sha512-ztaw4M1VqgMwl9HlPpOuiYgItcHlunW0He2fE6eNfT6E/CF2FtYi9ofOYe4mKntstYk0Fyh/rDRBdS3AnxjlrA==", + "requires": { + "define-properties": "^1.1.2" + } + }, "remove-bom-buffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz", @@ -5791,7 +5986,6 @@ "version": "2.88.0", "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", - "dev": true, "requires": { "aws-sign2": "~0.7.0", "aws4": "^1.8.0", @@ -5871,6 +6065,14 @@ "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", "dev": true }, + "retry-as-promised": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-3.2.0.tgz", + "integrity": "sha512-CybGs60B7oYU/qSQ6kuaFmRd9sTZ6oXSc0toqePvV74Ac6/IFZSI1ReFQmtCN+uvW1Mtqdwpvt/LGOiCBAY2Mg==", + "requires": { + "any-promise": "^1.3.0" + } + }, "right-align": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", @@ -5883,7 +6085,6 @@ "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, "requires": { "glob": "^7.1.3" } @@ -5919,6 +6120,11 @@ "yargs": "^7.0.0" } }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, "scss-tokenizer": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz", @@ -5943,8 +6149,7 @@ "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" }, "semver-greatest-satisfied-range": { "version": "1.1.0", @@ -5982,6 +6187,76 @@ } } }, + "sequelize": { + "version": "5.19.6", + "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-5.19.6.tgz", + "integrity": "sha512-LPDD+v+iEmjwIw4HveoWR0OZXz0PFH74wmL+wLSSqQFYWRQCi6ml0n9XTRsVDCdVXA1VwUcwvRAaDkyQTvSmRA==", + "requires": { + "bluebird": "^3.5.0", + "cls-bluebird": "^2.1.0", + "debug": "^4.1.1", + "dottie": "^2.0.0", + "inflection": "1.12.0", + "lodash": "^4.17.15", + "moment": "^2.24.0", + "moment-timezone": "^0.5.21", + "retry-as-promised": "^3.2.0", + "semver": "^6.3.0", + "sequelize-pool": "^2.3.0", + "toposort-class": "^1.0.1", + "uuid": "^3.3.3", + "validator": "^10.11.0", + "wkx": "^0.4.8" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, + "sequelize-pool": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-2.3.0.tgz", + "integrity": "sha512-Ibz08vnXvkZ8LJTiUOxRcj1Ckdn7qafNZ2t59jYHMX1VIebTAOYefWdRYFt6z6+hy52WGthAHAoLc9hvk3onqA==" + }, + "sequelize-typescript": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/sequelize-typescript/-/sequelize-typescript-1.0.0.tgz", + "integrity": "sha512-oXyvHRTOyI8sJettpISL5LO30GaMMrLqzxiLCy6MjUmBJdaQDpdjn7ofge4J87MSdw+YPzkjrJLogMc9ONY2Tg==", + "requires": { + "glob": "7.1.2" + }, + "dependencies": { + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, "serve-static": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", @@ -5996,8 +6271,7 @@ "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, "set-value": { "version": "2.0.1", @@ -6016,11 +6290,15 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" }, + "shimmer": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", + "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==" + }, "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", - "dev": true + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" }, "simple-swizzle": { "version": "0.2.2", @@ -6370,11 +6648,20 @@ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, + "sqlite3": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-4.1.0.tgz", + "integrity": "sha512-RvqoKxq+8pDHsJo7aXxsFR18i+dU2Wp5o12qAJOV5LNcDt+fgJsc2QKKg3sIRfXrN9ZjzY1T7SNe/DFVqAXjaw==", + "requires": { + "nan": "^2.12.1", + "node-pre-gyp": "^0.11.0", + "request": "^2.87.0" + } + }, "sshpk": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", - "dev": true, "requires": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", @@ -6475,7 +6762,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -6486,7 +6772,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, "requires": { "ansi-regex": "^2.0.0" } @@ -6536,6 +6821,11 @@ "get-stdin": "^4.0.1" } }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -6806,11 +7096,15 @@ "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-0.0.1.tgz", "integrity": "sha1-zu78cXp2xDFvEm0LnbqlXX598Bo=" }, + "toposort-class": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz", + "integrity": "sha1-f/0feMi+KMO6Rc1OGj9e4ZO9mYg=" + }, "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==", - "dev": true, "requires": { "psl": "^1.1.24", "punycode": "^1.4.1" @@ -6819,8 +7113,7 @@ "punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" } } }, @@ -6940,7 +7233,6 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "dev": true, "requires": { "safe-buffer": "^5.0.1" } @@ -6948,8 +7240,7 @@ "tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" }, "type": { "version": "1.2.0", @@ -7156,7 +7447,6 @@ "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "dev": true, "requires": { "punycode": "^2.1.0" } @@ -7186,8 +7476,7 @@ "uuid": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", - "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==", - "dev": true + "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==" }, "v8flags": { "version": "3.1.3", @@ -7208,6 +7497,11 @@ "spdx-expression-parse": "^3.0.0" } }, + "validator": { + "version": "10.11.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-10.11.0.tgz", + "integrity": "sha512-X/p3UZerAIsbBfN/IwahhYaBbY68EN/UQBWHtsbXGT5bfrH/p4NQzUCG1kF/rtKaNpnJ7jAu6NGTdSNtyNIXMw==" + }, "value-or-function": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz", @@ -7223,7 +7517,6 @@ "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "dev": true, "requires": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", @@ -7367,7 +7660,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "dev": true, "requires": { "string-width": "^1.0.2 || 2" } @@ -7440,6 +7732,14 @@ "acorn-globals": "^3.0.0" } }, + "wkx": { + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.4.8.tgz", + "integrity": "sha512-ikPXMM9IR/gy/LwiOSqWlSL3X/J5uk9EO2hHNRXS41eTLXaUFEVw9fn/593jW/tE5tedNg8YjT5HkCa4FqQZyQ==", + "requires": { + "@types/node": "*" + } + }, "wordwrap": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", @@ -7470,8 +7770,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "ws": { "version": "6.1.4", diff --git a/package.json b/package.json index cd1a34f..94d36ad 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "author": "SoftEngI", "license": "ISC", "devDependencies": { + "@types/bluebird": "^3.5.27", "@types/compression": "^1.0.1", "@types/connect-pg-simple": "^4.2.0", "@types/cookie-parser": "^1.4.2", @@ -33,9 +34,11 @@ "@types/http-status": "^0.2.30", "@types/js-yaml": "^3.12.1", "@types/markdown-it": "0.0.9", - "@types/node": "^12.7.8", + "@types/node": "^12.7.12", "@types/pg": "^7.11.0", + "@types/sequelize": "^4.28.5", "@types/socket.io": "^2.1.2", + "@types/validator": "^10.11.3", "@types/winston": "^2.4.4", "delete": "^1.1.0", "gulp": "^4.0.2", @@ -49,7 +52,7 @@ }, "dependencies": { "compression": "^1.7.4", - "connect-pg-simple": "^6.0.1", + "connect-session-sequelize": "^6.0.0", "cookie-parser": "^1.4.4", "cors": "^2.8.5", "express": "^4.17.1", @@ -57,7 +60,6 @@ "express-session": "^1.16.2", "express-socket.io-session": "^1.3.5", "fs-extra": "^8.1.0", - "g": "^2.0.1", "graphql": "^14.4.2", "graphql-import": "^0.7.1", "http-status": "^1.3.2", @@ -66,7 +68,11 @@ "markdown-it-emoji": "^1.4.0", "pg": "^7.12.1", "pug": "^2.0.4", + "reflect-metadata": "^0.1.13", + "sequelize": "^5.19.6", + "sequelize-typescript": "^1.0.0", "socket.io": "^2.2.0", + "sqlite3": "^4.1.0", "winston": "^3.2.1" } } diff --git a/src/app.ts b/src/app.ts index fe01b63..0a565a6 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,42 +1,43 @@ import * as compression from "compression"; -import connectPgSimple = require("connect-pg-simple"); import * as cookieParser from "cookie-parser"; import * as cors from "cors"; import * as express from "express"; import * as graphqlHTTP from "express-graphql"; import * as session from "express-session"; import sharedsession = require("express-socket.io-session"); +import * as fsx from "fs-extra"; import {buildSchema} from "graphql"; import {importSchema} from "graphql-import"; import * as http from "http"; import * as path from "path"; +import {Sequelize} from "sequelize-typescript"; import * as socketIo from "socket.io"; import {resolver} from "./graphql/resolvers"; -import dataaccess, {queryHelper} from "./lib/dataaccess"; +import dataaccess from "./lib/dataaccess"; import globals from "./lib/globals"; import routes from "./routes"; +const SequelizeStore = require("connect-session-sequelize")(session.Store); const logger = globals.logger; -const PgSession = connectPgSimple(session); - class App { public app: express.Application; public io: socketIo.Server; public server: http.Server; + public readonly sequelize: Sequelize; constructor() { this.app = express(); this.server = new http.Server(this.app); this.io = socketIo(this.server); + this.sequelize = new Sequelize(globals.config.database.connectionUri ); } /** * initializes everything that needs to be initialized asynchronous. */ public async init() { - await dataaccess.init(); - await routes.ioListeners(this.io); + await dataaccess.init(this.sequelize); const appSession = session({ cookie: { @@ -46,12 +47,14 @@ class App { resave: false, saveUninitialized: false, secret: globals.config.session.secret, - store: new PgSession({ - pool: dataaccess.pool, - tableName: "user_sessions", - }), + store: new SequelizeStore({db: this.sequelize}), }); + const force = fsx.existsSync("sqz-force"); + logger.info(`Sequelize Table force: ${force}`); + await this.sequelize.sync({force, logging: (msg) => logger.silly(msg)}); + await routes.ioListeners(this.io); + this.io.use(sharedsession(appSession, {autoSave: true})); this.app.set("views", path.join(__dirname, "views")); diff --git a/src/default-config.yaml b/src/default-config.yaml index 0996432..0c24242 100644 --- a/src/default-config.yaml +++ b/src/default-config.yaml @@ -1,10 +1,6 @@ # database connection info database: - host: - port: - user: - password: - database: + connectionUri: "sqlite://greenvironment.db" # http server configuration server: diff --git a/src/graphql/resolvers.ts b/src/graphql/resolvers.ts index 7b35d4b..a764a9c 100644 --- a/src/graphql/resolvers.ts +++ b/src/graphql/resolvers.ts @@ -1,11 +1,8 @@ import {GraphQLError} from "graphql"; import * as status from "http-status"; import dataaccess from "../lib/dataaccess"; -import {Chatroom} from "../lib/dataaccess/Chatroom"; -import {Post} from "../lib/dataaccess/Post"; -import {Profile} from "../lib/dataaccess/Profile"; -import {User} from "../lib/dataaccess/User"; -import {NotLoggedInGqlError} from "../lib/errors/graphqlErrors"; +import * as models from "../lib/models"; +import {NotLoggedInGqlError, PostNotFoundGqlError} from "../lib/errors/graphqlErrors"; import globals from "../lib/globals"; import {InternalEvents} from "../lib/InternalEvents"; import {is} from "../lib/regex"; @@ -17,9 +14,9 @@ import {is} from "../lib/regex"; */ export function resolver(req: any, res: any): any { return { - getSelf() { + async getSelf() { if (req.session.userId) { - return new Profile(req.session.userId); + return models.User.findByPk(req.session.userId); } else { res.status(status.UNAUTHORIZED); return new NotLoggedInGqlError(); @@ -29,7 +26,7 @@ export function resolver(req: any, res: any): any { if (handle) { return await dataaccess.getUserByHandle(handle); } else if (userId) { - return new User(userId); + return models.User.findByPk(userId); } else { res.status(status.BAD_REQUEST); return new GraphQLError("No userId or handle provided."); @@ -45,7 +42,7 @@ export function resolver(req: any, res: any): any { }, async getChat({chatId}: { chatId: number }) { if (chatId) { - return new Chatroom(chatId); + return models.ChatRoom.findByPk(chatId); } else { res.status(status.BAD_REQUEST); return new GraphQLError("No chatId given."); @@ -105,7 +102,13 @@ export function resolver(req: any, res: any): any { async vote({postId, type}: { postId: number, type: dataaccess.VoteType }) { if (postId && type) { if (req.session.userId) { - return await (new Post(postId)).vote(req.session.userId, type); + const post = await models.Post.findByPk(postId); + if (post) { + return await post.vote(req.session.userId, type); + } else { + res.status(400); + return new PostNotFoundGqlError(postId); + } } else { res.status(status.UNAUTHORIZED); return new NotLoggedInGqlError(); @@ -132,8 +135,8 @@ export function resolver(req: any, res: any): any { }, async deletePost({postId}: { postId: number }) { if (postId) { - const post = new Post(postId); - if ((await post.author()).id === req.session.userId) { + const post = await models.Post.findByPk(postId, {include: [models.User]}); + if (post.rAuthor.id === req.session.userId) { return await dataaccess.deletePost(post.id); } else { res.status(status.FORBIDDEN); @@ -194,8 +197,8 @@ export function resolver(req: any, res: any): any { return new NotLoggedInGqlError(); } if (sender && type) { - const profile = new Profile(req.session.userId); - await profile.denyRequest(sender, type); + const user = await models.User.findByPk(req.session.userId); + await user.denyRequest(sender, type); return true; } else { res.status(status.BAD_REQUEST); @@ -209,8 +212,8 @@ export function resolver(req: any, res: any): any { } if (sender && type) { try { - const profile = new Profile(req.session.userId); - await profile.acceptRequest(sender, type); + const user = await models.User.findByPk(req.session.userId); + await user.acceptRequest(sender, type); return true; } catch (err) { globals.logger.warn(err.message); diff --git a/src/graphql/schema.graphql b/src/graphql/schema.graphql index a33be9a..681b0f4 100644 --- a/src/graphql/schema.graphql +++ b/src/graphql/schema.graphql @@ -206,6 +206,9 @@ type ChatRoom { } type ChatMessage { + "Id of the chat message" + id: ID! + "The author of the chat message." author: User! diff --git a/src/lib/MemoryCache.ts b/src/lib/MemoryCache.ts deleted file mode 100644 index 1e76834..0000000 --- a/src/lib/MemoryCache.ts +++ /dev/null @@ -1,73 +0,0 @@ -import * as crypto from "crypto"; -import {EventEmitter} from "events"; - -export class MemoryCache extends EventEmitter { - private cacheItems: any = {}; - private cacheExpires: any = {}; - private expireCheck: NodeJS.Timeout; - - /** - * Creates interval function. - * @param ttl - */ - constructor(private ttl: number = 500) { - super(); - this.expireCheck = setInterval(() => this.checkExpires(), ttl / 2); - } - - /** - * Creates a md5 hash of the given key. - * @param key - */ - public hashKey(key: string): string { - const hash = crypto.createHash("sha1"); - const data = hash.update(key, "utf8"); - return data.digest("hex"); - } - - /** - * Sets an entry. - * @param key - * @param value - */ - public set(key: string, value: any) { - this.cacheItems[key] = value; - this.cacheExpires[key] = Date.now() + this.ttl; - this.emit("set", key, value); - } - - /** - * Returns the entry stored with the given key. - * @param key - */ - public get(key: string) { - if (this.cacheItems.hasOwnProperty(key)) { - this.emit("hit", key, this.cacheItems[key]); - return this.cacheItems[key]; - } else { - this.emit("miss", key); - } - } - - /** - * Deletes a cache item. - * @param key - */ - public delete(key: string) { - this.emit("delete", key); - delete this.cacheItems[key]; - } - - /** - * Checks expires and clears items that are over the expire value. - */ - private checkExpires() { - for (const [key, value] of Object.entries(this.cacheExpires)) { - if (value < Date.now()) { - this.emit("delete", key); - delete this.cacheItems[key]; - delete this.cacheExpires[key]; - } - } - } -} diff --git a/src/lib/QueryHelper.ts b/src/lib/QueryHelper.ts deleted file mode 100644 index 83b36fc..0000000 --- a/src/lib/QueryHelper.ts +++ /dev/null @@ -1,212 +0,0 @@ -/** - * @author Trivernis - * @remarks - * - * Taken from {@link https://github.com/Trivernis/whooshy} - */ - -import * as fsx from "fs-extra"; -import {Pool, PoolClient, QueryConfig, QueryResult} from "pg"; -import globals from "./globals"; - -const logger = globals.logger; - -export interface IAdvancedQueryConfig extends QueryConfig { - cache?: boolean; -} - -/** - * Transaction class to wrap SQL transactions. - */ -export class SqlTransaction { - /** - * Constructor. - * @param client - */ - constructor(private client: PoolClient) { - } - - /** - * Begins the transaction. - */ - public async begin() { - return await this.client.query("BEGIN"); - } - - /** - * Commits the transaction - */ - public async commit() { - return await this.client.query("COMMIT"); - } - - /** - * Rolls back the transaction - */ - public async rollback() { - return await this.client.query("ROLLBACK"); - } - - /** - * Executes a query inside the transaction. - * @param query - */ - public async query(query: QueryConfig) { - return await this.client.query(query); - } - - /** - * Releases the client back to the pool. - */ - public release() { - this.client.release(); - } -} - -/** - * Query helper for easyer fetching of a specific row count. - */ -export class QueryHelper { - private pool: Pool; - - /** - * Constructor. - * @param pgPool - * @param [tableCreationFile] - * @param [tableUpdateFile] - */ - constructor(pgPool: Pool, private tableCreationFile?: string, private tableUpdateFile?: string) { - this.pool = pgPool; - } - - /** - * Async init function - */ - public async init() { - await this.pool.connect(); - await this.createTables(); - await this.updateTableDefinitions(); - } - - /** - * creates all tables needed if a filepath was given with the constructor - */ - public async createTables() { - if (this.tableCreationFile) { - logger.info("Creating nonexistent tables..."); - const tableSql = await fsx.readFile(this.tableCreationFile, "utf-8"); - const trans = await this.createTransaction(); - await trans.begin(); - try { - await trans.query({text: tableSql}); - await trans.commit(); - } catch (err) { - globals.logger.error(`Error on table creation ${err.message}`); - globals.logger.debug(err.stack); - await trans.rollback(); - } finally { - trans.release(); - } - } - } - - /** - * Updates the definition of the tables if the table update file was passed in the constructor - */ - public async updateTableDefinitions() { - if (this.tableUpdateFile) { - logger.info("Updating table definitions..."); - const tableSql = await fsx.readFile(this.tableUpdateFile, "utf-8"); - const trans = await this.createTransaction(); - await trans.begin(); - try { - await trans.query({text: tableSql}); - await trans.commit(); - } catch (err) { - globals.logger.error(`Error on table update ${err.message}`); - globals.logger.debug(err.stack); - await trans.rollback(); - } finally { - trans.release(); - } - } - } - - /** - * executes the sql query with values and returns all results. - * @param query - */ - public async all(query: IAdvancedQueryConfig): Promise { - const result = await this.query(query); - return result.rows; - } - - /** - * executes the sql query with values and returns the first result. - * @param query - */ - public async first(query: IAdvancedQueryConfig): Promise { - const result = await this.query(query); - if (result.rows && result.rows.length > 0) { - return result.rows[0]; - } - } - - /** - * Creates a new Transaction to be uses with error handling. - */ - public async createTransaction() { - const client: PoolClient = await this.pool.connect(); - return new SqlTransaction(client); - } - - /** - * Queries the database with error handling. - * @param query - the sql and values to execute - */ - private async query(query: IAdvancedQueryConfig): Promise { - try { - query.text = query.text.replace(/[\r\n]/g, " "); - globals.logger.silly(`Executing sql '${JSON.stringify(query)}'`); - - if (query.cache) { - const key = globals.cache.hashKey(JSON.stringify(query)); - const cacheResult = globals.cache.get(key); - if (cacheResult) { - return cacheResult; - } else { - const result = await this.pool.query(query); - globals.cache.set(key, result); - return result; - } - } else { - return await this.pool.query(query); - } - } catch (err) { - logger.debug(`Error on query "${JSON.stringify(query)}".`); - logger.error(`Sql query failed: ${err}`); - logger.verbose(err.stack); - return { - rows: null, - }; - } - } -} - -/** - * Returns the parameterized value sql for inserting - * @param columnCount - * @param rowCount - * @param [offset] - */ -export function buildSqlParameters(columnCount: number, rowCount: number, offset?: number): string { - let sql = ""; - for (let i = 0; i < rowCount; i++) { - sql += "("; - for (let j = 0; j < columnCount; j++) { - sql += `$${(i * columnCount) + j + 1 + offset},`; - } - sql = sql.replace(/,$/, "") + "),"; - } - return sql.replace(/,$/, ""); -} diff --git a/src/lib/Route.ts b/src/lib/Route.ts index 37bed62..be4988e 100644 --- a/src/lib/Route.ts +++ b/src/lib/Route.ts @@ -20,6 +20,7 @@ abstract class Route { protected ions?: Namespace; public abstract async init(...params: any): Promise; + public abstract async destroy(...params: any): Promise; } diff --git a/src/lib/dataaccess.ts b/src/lib/dataaccess.ts new file mode 100644 index 0000000..e13faca --- /dev/null +++ b/src/lib/dataaccess.ts @@ -0,0 +1,230 @@ +import {Sequelize} from "sequelize-typescript"; +import {ChatNotFoundError} from "./errors/ChatNotFoundError"; +import {EmailAlreadyRegisteredError} from "./errors/EmailAlreadyRegisteredError"; +import {UserNotFoundError} from "./errors/UserNotFoundError"; +import globals from "./globals"; +import {InternalEvents} from "./InternalEvents"; +import * as models from "./models"; + +/** + * Generates a new handle from the username and a base64 string of the current time. + * @param username + */ +function generateHandle(username: string) { + return `${username}.${Buffer.from(Date.now().toString()).toString("base64")}`; +} + +/** + * Namespace with functions to fetch initial data for wrapping. + */ +namespace dataaccess { + + let sequelize: Sequelize; + + /** + * Initializes everything that needs to be initialized asynchronous. + */ + export async function init(seq: Sequelize) { + sequelize = seq; + try { + await sequelize.addModels([ + models.ChatMember, + models.ChatMessage, + models.ChatRoom, + models.Friendship, + models.Post, + models.PostVote, + models.Request, + models.User, + ]); + } catch (err) { + globals.logger.error(err.message); + globals.logger.debug(err.stack); + } + } + + /** + * Returns the user by handle. + * @param userHandle + */ + export async function getUserByHandle(userHandle: string): Promise { + const user = await models.User.findOne({where: {handle: userHandle}}); + if (user) { + return user; + } else { + throw new UserNotFoundError(userHandle); + } + } + + /** + * Returns the user by email and password + * @param email + * @param password + */ + export async function getUserByLogin(email: string, password: string): Promise { + const user = await models.User.findOne({where: {email, password}}); + if (user) { + return user; + } else { + throw new UserNotFoundError(email); + } + } + + /** + * Registers a user with a username and password returning a user + * @param username + * @param email + * @param password + */ + export async function registerUser(username: string, email: string, password: string): Promise { + const existResult = !!(await models.User.findOne({where: {username, email, password}})); + const handle = generateHandle(username); + if (!existResult) { + return models.User.create({username, email, password, handle}); + } else { + throw new EmailAlreadyRegisteredError(email); + } + } + + /** + * Returns a post for a given postId.s + * @param postId + */ + export async function getPost(postId: number): Promise { + const post = await models.Post.findByPk(postId); + if (post) { + return post; + } else { + return null; + } + } + + /** + * Returns all posts sorted by new or top with pagination. + * @param first + * @param offset + * @param sort + */ + export async function getPosts(first: number, offset: number, sort: SortType) { + if (sort === SortType.NEW) { + return models.Post.findAll({ + include: [{association: "rVotes"}], + limit: first, + offset, + order: [["createdAt", "DESC"]], + }); + } else { + return await sequelize.query( + `SELECT * FROM ( + SELECT *, + (SELECT count(*) FROM post_votes WHERE vote_type = 'UPVOTE' AND post_id = posts.id) AS upvotes , + (SELECT count(*) FROM post_votes WHERE vote_type = 'DOWNVOTE' AND post_id = posts.id) AS downvotes + FROM posts) AS a ORDER BY (a.upvotes - a.downvotes) DESC LIMIT ? OFFSET ?`, + {replacements: [first, offset], mapToModel: true, model: models.Post}) as models.Post[]; + } + } + + /** + * Creates a post + * @param content + * @param authorId + * @param type + */ + export async function createPost(content: string, authorId: number, type?: string): Promise { + type = type || "MISC"; + const post = await models.Post.create({content, authorId}); + globals.internalEmitter.emit(InternalEvents.POSTCREATE, post); + return post; + } + + /** + * Deletes a post + * @param postId + */ + export async function deletePost(postId: number): Promise { + await (await models.Post.findByPk(postId)).destroy(); + return true; + } + + /** + * Creates a chatroom containing two users + * @param members + */ + export async function createChat(...members: number[]): Promise { + return sequelize.transaction(async (t) => { + const chat = await models.ChatRoom.create({}, {transaction: t, include: [models.User]}); + for (const member of members) { + const user = await models.User.findByPk(member); + await chat.$add("rMember", user, {transaction: t}); + } + await chat.save({transaction: t}); + globals.internalEmitter.emit(InternalEvents.CHATCREATE, chat); + return chat; + }); + } + + /** + * Sends a message into a chat. + * @param authorId + * @param chatId + * @param content + */ + export async function sendChatMessage(authorId: number, chatId: number, content: string) { + const chat = await models.ChatRoom.findByPk(chatId); + if (chat) { + const message = await chat.$create("rMessage", {content, authorId}) as models.ChatMessage; + globals.internalEmitter.emit(InternalEvents.CHATMESSAGE, message); + return message; + } else { + throw new ChatNotFoundError(chatId); + } + } + + /** + * Returns all rChats. + */ + export async function getAllChats(): Promise { + return models.ChatRoom.findAll(); + } + + /** + * Sends a request to a user. + * @param sender + * @param receiver + * @param requestType + */ + export async function createRequest(sender: number, receiver: number, requestType?: RequestType) { + requestType = requestType || RequestType.FRIENDREQUEST; + + const request = await models.Request.create({senderId: sender, receiverId: receiver, requestType}); + globals.internalEmitter.emit(InternalEvents.REQUESTCREATE, Request); + return request; + } + + /** + * Enum representing the types of votes that can be performed on a post. + */ + export enum VoteType { + UPVOTE = "UPVOTE", + DOWNVOTE = "DOWNVOTE", + } + + /** + * Enum representing the types of request that can be created. + */ + export enum RequestType { + FRIENDREQUEST = "FRIENDREQUEST", + GROUPINVITE = "GROUPINVITE", + EVENTINVITE = "EVENTINVITE", + } + + /** + * Enum representing the types of sorting in the feed. + */ + export enum SortType { + TOP = "TOP", + NEW = "NEW", + } +} + +export default dataaccess; diff --git a/src/lib/dataaccess/ChatMessage.ts b/src/lib/dataaccess/ChatMessage.ts deleted file mode 100644 index 4c91a16..0000000 --- a/src/lib/dataaccess/ChatMessage.ts +++ /dev/null @@ -1,31 +0,0 @@ -import markdown from "../markdown"; -import {Chatroom} from "./Chatroom"; -import {User} from "./User"; - -export class ChatMessage { - constructor( - public readonly author: User, - public readonly chat: Chatroom, - public readonly createdAt: number, - public readonly content: string) {} - - /** - * The content rendered by markdown-it. - */ - public htmlContent(): string { - return markdown.renderInline(this.content); - } - - /** - * Returns resolved and rendered content of the chat message. - */ - public resolvedContent() { - return { - author: this.author.id, - chat: this.chat.id, - content: this.content, - createdAt: this.createdAt, - htmlContent: this.htmlContent(), - }; - } -} diff --git a/src/lib/dataaccess/Chatroom.ts b/src/lib/dataaccess/Chatroom.ts deleted file mode 100644 index e541f06..0000000 --- a/src/lib/dataaccess/Chatroom.ts +++ /dev/null @@ -1,76 +0,0 @@ -import globals from "../globals"; -import {ChatMessage} from "./ChatMessage"; -import {queryHelper} from "./index"; -import {User} from "./User"; - -export class Chatroom { - - public namespace: string; - constructor(public readonly id: number) { - this.id = Number(id); - this.namespace = `/chat/${id}`; - } - - /** - * Returns if the chat exists. - */ - public async exists(): Promise { - const result = await queryHelper.first({ - text: "SELECT id FROM chats WHERE id = $1", - values: [this.id], - }); - return !!result.id; - } - - /** - * Returns all members of a chatroom. - */ - public async members(): Promise { - const result = await queryHelper.all({ - cache: true, - text: `SELECT * FROM chat_members - JOIN users ON (chat_members.member = users.id) - WHERE chat_members.chat = $1;`, - values: [this.id], - }); - const chatMembers = []; - for (const row of result) { - const user = new User(row.id, row); - chatMembers.push(user); - } - return chatMembers; - } - - /** - * Returns messages of the chat - * @param limit - the limit of messages to return - * @param offset - the offset of messages to return - * @param containing - filter by containing - */ - public async messages({first, offset, containing}: {first?: number, offset?: number, containing?: string}) { - const lim = first || 16; - const offs = offset || 0; - - const result = await queryHelper.all({ - cache: true, - text: "SELECT * FROM chat_messages WHERE chat = $1 ORDER BY created_at DESC LIMIT $2 OFFSET $3", - values: [this.id, lim, offs], - }); - - const messages = []; - const users: any = {}; - for (const row of result) { - if (!users[row.author]) { - const user = new User(row.author); - await user.exists(); - users[row.author] = user; - } - messages.push(new ChatMessage(users[row.author], this, row.created_at, row.content)); - } - if (containing) { - return messages.filter((x) => x.content.includes(containing)); - } else { - return messages; - } - } -} diff --git a/src/lib/dataaccess/DataObject.ts b/src/lib/dataaccess/DataObject.ts deleted file mode 100644 index 26fc809..0000000 --- a/src/lib/dataaccess/DataObject.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * abstact DataObject class - */ -import {EventEmitter} from "events"; - -export abstract class DataObject extends EventEmitter { - protected dataLoaded: boolean = false; - private loadingData: boolean = false; - - constructor(public id: number, protected row?: any) { - super(); - this.id = Number(id); - } - - /** - * Returns if the object extists by trying to load data. - */ - public async exists() { - await this.loadDataIfNotExists(); - return this.dataLoaded; - } - - protected abstract loadData(): Promise; - - /** - * Loads data from the database if data has not been loaded - */ - protected async loadDataIfNotExists() { - if (!this.dataLoaded && !this.loadingData) { - this.loadingData = true; - await this.loadData(); - this.loadingData = false; - this.emit("loaded"); - } else if (this.loadingData) { - return new Promise((res) => { - this.on("loaded", () => res()); - }); - } - } -} diff --git a/src/lib/dataaccess/Post.ts b/src/lib/dataaccess/Post.ts deleted file mode 100644 index e4912c7..0000000 --- a/src/lib/dataaccess/Post.ts +++ /dev/null @@ -1,159 +0,0 @@ -import markdown from "../markdown"; -import {DataObject} from "./DataObject"; -import {queryHelper} from "./index"; -import dataaccess from "./index"; -import {User} from "./User"; - -export class Post extends DataObject { - public readonly id: number; - private $createdAt: string; - private $content: string; - private $author: number; - private $type: string; - - /** - * Returns the resolved data of the post. - */ - public async resolvedData() { - await this.loadDataIfNotExists(); - return { - authorId: this.$author, - content: this.$content, - createdAt: this.$createdAt, - id: this.id, - type: this.$type, - }; - } - /** - * Returns the upvotes of a post. - */ - public async upvotes(): Promise { - const result = await queryHelper.first({ - cache: true, - text: "SELECT COUNT(*) count FROM votes WHERE item_id = $1 AND vote_type = 'UPVOTE'", - values: [this.id], - }); - return result.count; - } - - /** - * Returns the downvotes of the post - */ - public async downvotes(): Promise { - const result = await queryHelper.first({ - cache: true, - text: "SELECT COUNT(*) count FROM votes WHERE item_id = $1 AND vote_type = 'DOWNVOTE'", - values: [this.id], - }); - return result.count; - } - - /** - * The content of the post (markdown) - */ - public async content(): Promise { - await this.loadDataIfNotExists(); - return this.$content; - } - - /** - * the content rendered by markdown-it. - */ - public async htmlContent(): Promise { - await this.loadDataIfNotExists(); - return markdown.render(this.$content); - } - - /** - * The date the post was created at. - */ - public async createdAt(): Promise { - await this.loadDataIfNotExists(); - return this.$createdAt; - } - - /** - * The autor of the post. - */ - public async author(): Promise { - await this.loadDataIfNotExists(); - return new User(this.$author); - } - - /** - * Deletes the post. - */ - public async delete(): Promise { - const query = await queryHelper.first({ - text: "DELETE FROM posts WHERE id = $1", - values: [this.id], - }); - } - - /** - * The type of vote the user performed on the post. - */ - public async userVote(userId: number): Promise { - const result = await queryHelper.first({ - cache: true, - text: "SELECT vote_type FROM votes WHERE user_id = $1 AND item_id = $2", - values: [userId, this.id], - }); - if (result) { - return result.vote_type; - } else { - return null; - } - } - - /** - * Performs a vote on a post. - * @param userId - * @param type - */ - public async vote(userId: number, type: dataaccess.VoteType): Promise { - const uVote = await this.userVote(userId); - if (uVote === type) { - await queryHelper.first({ - text: "DELETE FROM votes WHERE item_id = $1 AND user_id = $2", - values: [this.id, userId], - }); - } else { - if (uVote) { - await queryHelper.first({ - text: "UPDATE votes SET vote_type = $1 WHERE user_id = $2 AND item_id = $3", - values: [type, userId, this.id], - }); - } else { - await queryHelper.first({ - text: "INSERT INTO votes (user_id, item_id, vote_type) values ($1, $2, $3)", - values: [userId, this.id, type], - }); - } - return type; - } - } - - /** - * Loads the data from the database if needed. - */ - protected async loadData(): Promise { - let result: any; - if (this.row) { - result = this.row; - } else { - result = await queryHelper.first({ - cache: true, - text: "SELECT * FROM posts WHERE posts.id = $1", - values: [this.id], - }); - } - if (result) { - this.$author = result.author; - this.$content = result.content; - this.$createdAt = result.created_at; - this.$type = result.type; - this.dataLoaded = true; - } - } -} diff --git a/src/lib/dataaccess/Profile.ts b/src/lib/dataaccess/Profile.ts deleted file mode 100644 index 090e6da..0000000 --- a/src/lib/dataaccess/Profile.ts +++ /dev/null @@ -1,163 +0,0 @@ -import {RequestNotFoundError} from "../errors/RequestNotFoundError"; -import {Chatroom} from "./Chatroom"; -import dataaccess, {queryHelper} from "./index"; -import {User} from "./User"; -import {Request} from "./Request"; - -export class Profile extends User { - - /** - * Returns all chatrooms (with pagination). - * Skips the query if the user doesn't exist. - * @param first - * @param offset - */ - public async chats({first, offset}: {first: number, offset?: number}): Promise { - if (!(await this.exists())) { - return []; - } - first = first || 10; - offset = offset || 0; - - const result = await queryHelper.all({ - text: "SELECT chat FROM chat_members WHERE member = $1 LIMIT $2 OFFSET $3", - values: [this.id, first, offset], - }); - if (result) { - return result.map((row) => new Chatroom(row.chat)); - } else { - return []; - } - } - - /** - * Returns all open requests the user has send. - */ - public async sentRequests() { - const result = await queryHelper.all({ - cache: true, - text: "SELECT * FROM requests WHERE sender = $1", - values: [this.id], - }); - return this.getRequests(result); - } - - /** - * Returns all received requests of the user. - */ - public async receivedRequests() { - const result = await queryHelper.all({ - cache: true, - text: "SELECT * FROM requests WHERE receiver = $1", - values: [this.id], - }); - return this.getRequests(result); - } - - /** - * Sets the greenpoints of a user. - * @param points - */ - public async setGreenpoints(points: number): Promise { - const result = await queryHelper.first({ - text: "UPDATE users SET greenpoints = $1 WHERE id = $2 RETURNING greenpoints", - values: [points, this.id], - }); - return result.greenpoints; - } - - /** - * Sets the email of the user - * @param email - */ - public async setEmail(email: string): Promise { - const result = await queryHelper.first({ - text: "UPDATE users SET email = $1 WHERE users.id = $2 RETURNING email", - values: [email, this.id], - }); - return result.email; - } - - /** - * Updates the handle of the user - */ - public async setHandle(handle: string): Promise { - const result = await queryHelper.first({ - text: "UPDATE users SET handle = $1 WHERE id = $2", - values: [handle, this.id], - }); - return result.handle; - } - - /** - * Sets the username of the user - * @param name - */ - public async setName(name: string): Promise { - const result = await queryHelper.first({ - text: "UPDATE users SET name = $1 WHERE id = $2", - values: [name, this.id], - }); - return result.name; - } - - /** - * Denys a request. - * @param sender - * @param type - */ - public async denyRequest(sender: number, type: dataaccess.RequestType) { - await queryHelper.first({ - text: "DELETE FROM requests WHERE receiver = $1 AND sender = $2 AND type = $3", - values: [this.id, sender, type], - }); - } - - /** - * Accepts a request. - * @param sender - * @param type - */ - public async acceptRequest(sender: number, type: dataaccess.RequestType) { - const exists = await queryHelper.first({ - cache: true, - text: "SELECT 1 FROM requests WHERE receiver = $1 AND sender = $2 AND type = $3", - values: [this.id, sender, type], - }); - if (exists) { - if (type === dataaccess.RequestType.FRIENDREQUEST) { - await queryHelper.first({ - text: "INSERT INTO user_friends (user_id, friend_id) VALUES ($1, $2)", - values: [this.id, sender], - }); - } - } else { - throw new RequestNotFoundError(sender, this.id, type); - } - } - - /** - * Returns request wrapper for a row database request result. - * @param rows - */ - private getRequests(rows: any) { - const requests = []; - const requestUsers: any = {}; - - for (const row of rows) { - let sender = requestUsers[row.sender]; - - if (!sender) { - sender = new User(row.sender); - requestUsers[row.sender] = sender; - } - let receiver = requestUsers[row.receiver]; - if (!receiver) { - receiver = new User(row.receiver); - requestUsers[row.receiver] = receiver; - } - requests.push(new Request(sender, receiver, row.type)); - } - return requests; - } -} diff --git a/src/lib/dataaccess/Request.ts b/src/lib/dataaccess/Request.ts deleted file mode 100644 index 7e8f1bc..0000000 --- a/src/lib/dataaccess/Request.ts +++ /dev/null @@ -1,24 +0,0 @@ -import dataaccess from "./index"; -import {User} from "./User"; - -/** - * Represents a request to a user. - */ -export class Request { - constructor( - public readonly sender: User, - public readonly receiver: User, - public readonly type: dataaccess.RequestType) { - } - - /** - * Returns the resolved request data. - */ - public resolvedData() { - return { - receiverId: this.receiver.id, - senderId: this.sender.id, - type: this.type, - }; - } -} diff --git a/src/lib/dataaccess/User.ts b/src/lib/dataaccess/User.ts deleted file mode 100644 index b3501cc..0000000 --- a/src/lib/dataaccess/User.ts +++ /dev/null @@ -1,128 +0,0 @@ -import globals from "../globals"; -import {DataObject} from "./DataObject"; -import {queryHelper} from "./index"; -import {Post} from "./Post"; - -export class User extends DataObject { - private $name: string; - private $handle: string; - private $email: string; - private $greenpoints: number; - private $joinedAt: string; - private $exists: boolean; - - /** - * The name of the user - */ - public async name(): Promise { - await this.loadDataIfNotExists(); - return this.$name; - } - - /** - * The unique handle of the user. - */ - public async handle(): Promise { - await this.loadDataIfNotExists(); - return this.$handle; - } - - /** - * The email of the user - */ - public async email(): Promise { - await this.loadDataIfNotExists(); - return this.$email; - } - - /** - * The number of greenpoints of the user - */ - public async greenpoints(): Promise { - await this.loadDataIfNotExists(); - return this.$greenpoints; - } - - /** - * Returns the number of posts the user created - */ - public async numberOfPosts(): Promise { - const result = await queryHelper.first({ - cache: true, - text: "SELECT COUNT(*) count FROM posts WHERE author = $1", - values: [this.id], - }); - return result.count; - } - - /** - * The date the user joined the platform - */ - public async joinedAt(): Promise { - await this.loadDataIfNotExists(); - return new Date(this.$joinedAt); - } - - /** - * Returns all friends of the user. - */ - public async friends(): Promise { - const result = await queryHelper.all({ - cache: true, - text: "SELECT * FROM user_friends WHERE user_id = $1 OR friend_id = $1", - values: [this.id], - }); - const userFriends = []; - for (const row of result) { - if (row.user_id === this.id) { - userFriends.push(new User(row.friend_id)); - } else { - userFriends.push(new User(row.user_id)); - } - } - return userFriends; - } - - /** - * Returns all posts for a user. - */ - public async posts({first, offset}: {first: number, offset: number}): Promise { - first = first || 10; - offset = offset || 0; - const result = await queryHelper.all({ - cache: true, - text: "SELECT * FROM posts WHERE author = $1 ORDER BY created_at DESC LIMIT $2 OFFSET $3", - values: [this.id, first, offset], - }); - const posts = []; - - for (const row of result) { - posts.push(new Post(row.id, row)); - } - return posts; - } - - /** - * Fetches the data for the user. - */ - protected async loadData(): Promise { - let result: any; - if (this.row) { - result = this.row; - } else { - result = await queryHelper.first({ - cache: true, - text: "SELECT * FROM users WHERE users.id = $1", - values: [this.id], - }); - } - if (result) { - this.$name = result.name; - this.$handle = result.handle; - this.$email = result.email; - this.$greenpoints = result.greenpoints; - this.$joinedAt = result.joined_at; - this.dataLoaded = true; - } - } -} diff --git a/src/lib/dataaccess/index.ts b/src/lib/dataaccess/index.ts deleted file mode 100644 index 3e6628d..0000000 --- a/src/lib/dataaccess/index.ts +++ /dev/null @@ -1,304 +0,0 @@ -import {Pool} from "pg"; -import {ChatNotFoundError} from "../errors/ChatNotFoundError"; -import {EmailAlreadyRegisteredError} from "../errors/EmailAlreadyRegisteredError"; -import {UserNotFoundError} from "../errors/UserNotFoundError"; -import globals from "../globals"; -import {InternalEvents} from "../InternalEvents"; -import {QueryHelper} from "../QueryHelper"; -import {ChatMessage} from "./ChatMessage"; -import {Chatroom} from "./Chatroom"; -import {Post} from "./Post"; -import {Profile} from "./Profile"; -import {Request} from "./Request"; -import {User} from "./User"; - -const config = globals.config; -const tableCreationFile = __dirname + "/../../sql/create-tables.sql"; -const tableUpdateFile = __dirname + "/../../sql/update-tables.sql"; - -const dbClient: Pool = new Pool({ - database: config.database.database, - host: config.database.host, - password: config.database.password, - port: config.database.port, - user: config.database.user, -}); -export const queryHelper = new QueryHelper(dbClient, tableCreationFile, tableUpdateFile); - -/** - * Generates a new handle from the username and a base64 string of the current time. - * @param username - */ -function generateHandle(username: string) { - return `${username}.${Buffer.from(Date.now().toString()).toString("base64")}`; -} - -/** - * Namespace with functions to fetch initial data for wrapping. - */ -namespace dataaccess { - - export const pool: Pool = dbClient; - - /** - * Initializes everything that needs to be initialized asynchronous. - */ - export async function init() { - try { - await queryHelper.init(); - } catch (err) { - globals.logger.error(err.message); - globals.logger.debug(err.stack); - } - } - - /** - * Returns the user by handle. - * @param userHandle - */ - export async function getUserByHandle(userHandle: string): Promise { - const result = await queryHelper.first({ - text: "SELECT * FROM users WHERE users.handle = $1", - values: [userHandle], - }); - if (result) { - return new User(result.id, result); - } else { - throw new UserNotFoundError(userHandle); - } - } - - /** - * Returns the user by email and password - * @param email - * @param password - */ - export async function getUserByLogin(email: string, password: string): Promise { - const result = await queryHelper.first({ - text: "SELECT * FROM users WHERE email = $1 AND password = $2", - values: [email, password], - }); - if (result) { - return new Profile(result.id, result); - } else { - throw new UserNotFoundError(email); - } - } - - /** - * Registers a user with a username and password returning a user - * @param username - * @param email - * @param password - */ - export async function registerUser(username: string, email: string, password: string) { - const existResult = await queryHelper.first({ - text: "SELECT email FROM users WHERE email = $1;", - values: [email], - }); - if (!existResult || !existResult.email) { - const result = await queryHelper.first({ - text: "INSERT INTO users (name, handle, password, email) VALUES ($1, $2, $3, $4) RETURNING *", - values: [username, generateHandle(username), password, email], - }); - return new Profile(result.id, result); - } else { - throw new EmailAlreadyRegisteredError(email); - } - } - - /** - * Returns a post for a given postId.s - * @param postId - */ - export async function getPost(postId: number): Promise { - const result = await queryHelper.first({ - text: "SELECT * FROM posts WHERE id = $1", - values: [postId], - }); - if (result) { - return new Post(result.id, result); - } else { - return null; - } - } - - /** - * Returns all posts sorted by new or top with pagination. - * @param first - * @param offset - * @param sort - */ - export async function getPosts(first: number, offset: number, sort: SortType) { - if (sort === SortType.NEW) { - const results = await queryHelper.all({ - cache: true, - text: "SELECT * FROM posts ORDER BY created_at DESC LIMIT $1 OFFSET $2", - values: [first, offset], - }); - const posts = []; - for (const row of results) { - posts.push(new Post(row.id, row)); - } - return posts; - } else { - const results = await queryHelper.all({ - cache: true, - text: ` - SELECT * FROM ( - SELECT *, - (SELECT count(*) FROM votes WHERE vote_type = 'UPVOTE' AND item_id = posts.id) AS upvotes , - (SELECT count(*) FROM votes WHERE vote_type = 'DOWNVOTE' AND item_id = posts.id) AS downvotes - FROM posts) AS a ORDER BY (a.upvotes - a.downvotes) DESC LIMIT $1 OFFSET $2; - `, - values: [first, offset], - }); - const posts = []; - for (const row of results) { - posts.push(new Post(row.id, row)); - } - return posts; - } - } - - /** - * Creates a post - * @param content - * @param authorId - * @param type - */ - export async function createPost(content: string, authorId: number, type?: string): Promise { - type = type || "MISC"; - const result = await queryHelper.first({ - text: "INSERT INTO posts (content, author, type) VALUES ($1, $2, $3) RETURNING *", - values: [content, authorId, type], - }); - const post = new Post(result.id, result); - globals.internalEmitter.emit(InternalEvents.POSTCREATE, post); - return post; - } - - /** - * Deletes a post - * @param postId - */ - export async function deletePost(postId: number): Promise { - const result = await queryHelper.first({ - text: "DELETE FROM posts WHERE posts.id = $1", - values: [postId], - }); - return true; - } - - /** - * Creates a chatroom containing two users - * @param members - */ - export async function createChat(...members: number[]): Promise { - const idResult = await queryHelper.first({ - text: "INSERT INTO chats (id) values (default) RETURNING *;", - }); - const id = idResult.id; - const transaction = await queryHelper.createTransaction(); - try { - await transaction.begin(); - for (const member of members) { - await transaction.query({ - name: "chat-member-insert", - text: "INSERT INTO chat_members (chat, member) VALUES ($1, $2);", - values: [id, member], - }); - } - await transaction.commit(); - } catch (err) { - globals.logger.warn(`Failed to insert chatmember into database: ${err.message}`); - globals.logger.debug(err.stack); - await transaction.rollback(); - } finally { - transaction.release(); - } - const chat = new Chatroom(id); - globals.internalEmitter.emit(InternalEvents.CHATCREATE, chat); - return chat; - } - - /** - * Sends a message into a chat. - * @param authorId - * @param chatId - * @param content - */ - export async function sendChatMessage(authorId: number, chatId: number, content: string) { - const chat = new Chatroom(chatId); - if ((await chat.exists())) { - const result = await queryHelper.first({ - text: "INSERT INTO chat_messages (chat, author, content) values ($1, $2, $3) RETURNING *", - values: [chatId, authorId, content], - }); - const message = new ChatMessage(new User(result.author), chat, result.created_at, result.content); - globals.internalEmitter.emit(InternalEvents.CHATMESSAGE, message); - return message; - } else { - throw new ChatNotFoundError(chatId); - } - } - - /** - * Returns all chats. - */ - export async function getAllChats(): Promise { - const result = await queryHelper.all({ - text: "SELECT id FROM chats;", - }); - const chats = []; - for (const row of result) { - chats.push(new Chatroom(row.id)); - } - return chats; - } - - /** - * Sends a request to a user. - * @param sender - * @param receiver - * @param type - */ - export async function createRequest(sender: number, receiver: number, type?: RequestType) { - type = type || RequestType.FRIENDREQUEST; - - const result = await queryHelper.first({ - text: "INSERT INTO requests (sender, receiver, type) VALUES ($1, $2, $3) RETURNING *", - values: [sender, receiver, type], - }); - const request = new Request(new User(result.sender), new User(result.receiver), result.type); - globals.internalEmitter.emit(InternalEvents.REQUESTCREATE, Request); - return request; - } - - /** - * Enum representing the types of votes that can be performed on a post. - */ - export enum VoteType { - UPVOTE = "UPVOTE", - DOWNVOTE = "DOWNVOTE", - } - - /** - * Enum representing the types of request that can be created. - */ - export enum RequestType { - FRIENDREQUEST = "FRIENDREQUEST", - GROUPINVITE = "GROUPINVITE", - EVENTINVITE = "EVENTINVITE", - } - - /** - * Enum representing the types of sorting in the feed. - */ - export enum SortType { - TOP = "TOP", - NEW = "NEW", - } -} - -export default dataaccess; diff --git a/src/lib/errors/graphqlErrors.ts b/src/lib/errors/graphqlErrors.ts index 9784712..b33a883 100644 --- a/src/lib/errors/graphqlErrors.ts +++ b/src/lib/errors/graphqlErrors.ts @@ -1,9 +1,13 @@ import {GraphQLError} from "graphql"; -import {BaseError} from "./BaseError"; export class NotLoggedInGqlError extends GraphQLError { - constructor() { super("Not logged in"); } } + +export class PostNotFoundGqlError extends GraphQLError { + constructor(postId: number) { + super(`Post '${postId}' not found!`); + } +} diff --git a/src/lib/globals.ts b/src/lib/globals.ts index cea8e3c..6fe2899 100644 --- a/src/lib/globals.ts +++ b/src/lib/globals.ts @@ -9,7 +9,6 @@ import {EventEmitter} from "events"; import * as fsx from "fs-extra"; import * as yaml from "js-yaml"; import * as winston from "winston"; -import {MemoryCache} from "./MemoryCache"; const configPath = "config.yaml"; const defaultConfig = __dirname + "/../default-config.yaml"; @@ -28,14 +27,13 @@ if (!(fsx.pathExistsSync(configPath))) { */ namespace globals { export const config = yaml.safeLoad(fsx.readFileSync("config.yaml", "utf-8")); - export const cache = new MemoryCache(1200); export const logger = winston.createLogger({ transports: [ new winston.transports.Console({ format: winston.format.combine( winston.format.timestamp(), winston.format.colorize(), - winston.format.printf(({ level, message, timestamp }) => { + winston.format.printf(({level, message, timestamp}) => { return `${timestamp} ${level}: ${message}`; }), ), @@ -44,9 +42,6 @@ namespace globals { ], }); export const internalEmitter = new EventEmitter(); - cache.on("set", (key) => logger.debug(`Caching '${key}'.`)); - cache.on("miss", (key) => logger.debug(`Cache miss for '${key}'`)); - cache.on("hit", (key) => logger.debug(`Cache hit for '${key}'`)); } export default globals; diff --git a/src/lib/models/ChatMember.ts b/src/lib/models/ChatMember.ts new file mode 100644 index 0000000..f2b7aad --- /dev/null +++ b/src/lib/models/ChatMember.ts @@ -0,0 +1,16 @@ +import {Column, ForeignKey, Model, NotNull, Table,} from "sequelize-typescript"; +import {ChatRoom} from "./ChatRoom"; +import {User} from "./User"; + +@Table({underscored: true}) +export class ChatMember extends Model { + @ForeignKey(() => User) + @NotNull + @Column({allowNull: false}) + public userId: number; + + @ForeignKey(() => ChatRoom) + @NotNull + @Column({allowNull: false}) + public chatId: number; +} diff --git a/src/lib/models/ChatMessage.ts b/src/lib/models/ChatMessage.ts new file mode 100644 index 0000000..3b7c357 --- /dev/null +++ b/src/lib/models/ChatMessage.ts @@ -0,0 +1,44 @@ +import * as sqz from "sequelize"; +import {BelongsTo, Column, CreatedAt, ForeignKey, Model, NotNull, Table,} from "sequelize-typescript"; +import markdown from "../markdown"; +import {ChatRoom} from "./ChatRoom"; +import {User} from "./User"; + +@Table({underscored: true}) +export class ChatMessage extends Model { + + @NotNull + @Column({type: sqz.STRING(512), allowNull: false}) + public content: string; + + @ForeignKey(() => ChatRoom) + @NotNull + @Column({allowNull: false}) + public chatId: number; + + @ForeignKey(() => User) + @NotNull + @Column({allowNull: false}) + public authorId: number; + + @BelongsTo(() => ChatRoom, "chatId") + public rChat: ChatRoom; + + @BelongsTo(() => User, "authorId") + public rAuthor: User; + + @CreatedAt + public createdAt: Date; + + public async chat(): Promise { + return await this.$get("rChat") as ChatRoom; + } + + public async author(): Promise { + return await this.$get("rAuthor") as User; + } + + public get htmlContent(): string { + return markdown.renderInline(this.getDataValue("content")); + } +} diff --git a/src/lib/models/ChatRoom.ts b/src/lib/models/ChatRoom.ts new file mode 100644 index 0000000..386da0f --- /dev/null +++ b/src/lib/models/ChatRoom.ts @@ -0,0 +1,28 @@ +import {BelongsToMany, CreatedAt, HasMany, Model, Table,} from "sequelize-typescript"; +import {ChatMember} from "./ChatMember"; +import {ChatMessage} from "./ChatMessage"; +import {User} from "./User"; + +@Table({underscored: true}) +export class ChatRoom extends Model { + @BelongsToMany(() => User, () => ChatMember) + public rMembers: User[]; + + @HasMany(() => ChatMessage, "chatId") + public rMessages: ChatMessage[]; + + @CreatedAt + public readonly createdAt!: Date; + + public async members(): Promise { + return await this.$get("rMembers") as User[]; + } + + public async messages(): Promise { + return await this.$get("rMessages") as ChatMessage[]; + } + + public get namespace(): string { + return "/chats/" + this.getDataValue("id"); + } +} diff --git a/src/lib/models/Friendship.ts b/src/lib/models/Friendship.ts new file mode 100644 index 0000000..4d7773b --- /dev/null +++ b/src/lib/models/Friendship.ts @@ -0,0 +1,16 @@ +import {Column, ForeignKey, Model, NotNull, Table} from "sequelize-typescript"; +import {User} from "./User"; + +@Table({underscored: true}) +export class Friendship extends Model { + + @ForeignKey(() => User) + @NotNull + @Column({allowNull: false}) + public userId: number; + + @ForeignKey(() => User) + @NotNull + @Column({allowNull: false}) + public friendId: number; +} diff --git a/src/lib/models/Post.ts b/src/lib/models/Post.ts new file mode 100644 index 0000000..93151a3 --- /dev/null +++ b/src/lib/models/Post.ts @@ -0,0 +1,70 @@ +import * as sqz from "sequelize"; +import {BelongsTo, BelongsToMany, Column, CreatedAt, ForeignKey, Model, NotNull, Table,} from "sequelize-typescript"; +import markdown from "../markdown"; +import {PostVote, VoteType} from "./PostVote"; +import {User} from "./User"; + +@Table({underscored: true}) +export class Post extends Model { + @NotNull + @Column({type: sqz.STRING(2048), allowNull: false}) + public content: string; + + @ForeignKey(() => User) + @NotNull + @Column({allowNull: false}) + public authorId: number; + + @BelongsTo(() => User, "authorId") + public rAuthor: User; + + @BelongsToMany(() => User, () => PostVote) + public rVotes: Array; + + @CreatedAt + public readonly createdAt!: Date; + + public async author(): Promise { + return await this.$get("rAuthor") as User; + } + + public async votes(): Promise> { + return await this.$get("rVotes") as Array; + } + + public get htmlContent() { + return markdown.render(this.getDataValue("content")); + } + + public async upvotes() { + return (await this.votes()).filter((v) => v.PostVote.voteType === VoteType.UPVOTE).length; + } + + public async downvotes() { + return (await this.votes()).filter((v) => v.PostVote.voteType === VoteType.DOWNVOTE).length; + } + + public async vote(userId: number, type: VoteType): Promise { + type = type || VoteType.UPVOTE; + let votes = await this.$get("rVotes", {where: {id: userId}}) as Array; + let vote = votes[0] || null; + let created = false; + if (!vote) { + await this.$add("rVote", userId); + votes = await this.$get("rVotes", {where: {id: userId}}) as Array; + vote = votes[0] || null; + created = true; + } + if (vote) { + if (vote.PostVote.voteType === type && !created) { + await vote.PostVote.destroy(); + return null; + } else { + vote.PostVote.voteType = type; + await vote.PostVote.save(); + } + } + + return vote.PostVote.voteType; + } +} diff --git a/src/lib/models/PostVote.ts b/src/lib/models/PostVote.ts new file mode 100644 index 0000000..50517f2 --- /dev/null +++ b/src/lib/models/PostVote.ts @@ -0,0 +1,26 @@ +import * as sqz from "sequelize"; +import {Column, ForeignKey, Model, NotNull, Table,} from "sequelize-typescript"; +import {Post} from "./Post"; +import {User} from "./User"; + +export enum VoteType { + UPVOTE = "UPVOTE", + DOWNVOTE = "DOWNVOTE", +} + +@Table({underscored: true}) +export class PostVote extends Model { + @NotNull + @Column({type: sqz.ENUM, values: ["UPVOTE", "DOWNVOTE"], defaultValue: "UPVOTE", allowNull: false}) + public voteType: VoteType; + + @ForeignKey(() => User) + @NotNull + @Column({allowNull: false}) + public userId: number; + + @ForeignKey(() => Post) + @NotNull + @Column({allowNull: false}) + public postId: number; +} diff --git a/src/lib/models/Request.ts b/src/lib/models/Request.ts new file mode 100644 index 0000000..87f6f85 --- /dev/null +++ b/src/lib/models/Request.ts @@ -0,0 +1,41 @@ +import * as sqz from "sequelize"; +import {BelongsTo, Column, ForeignKey, Model, NotNull, Table,} from "sequelize-typescript"; +import {User} from "./User"; + +export enum RequestType { + FRIENDREQUEST = "FRIENDREQUEST", + GROUPINVITE = "GROUPINVITE", + EVENTINVITE = "EVENTINVITE", +} + +@Table({underscored: true}) +export class Request extends Model { + @NotNull + @Column({type: sqz.ENUM, values: ["FRIENDREQUEST", "GROUPINVITE", "EVENTINVITE"], + defaultValue: "FRIENDREQUEST", allowNull: false}) + public requestType: RequestType; + + @ForeignKey(() => User) + @NotNull + @Column({allowNull: false}) + public senderId: number; + + @BelongsTo(() => User, "senderId") + public rSender: User; + + @ForeignKey(() => User) + @NotNull + @Column({allowNull: false}) + public receiverId: number; + + @BelongsTo(() => User, "receiverId") + public rReceiver: User; + + public async receiver(): Promise { + return await this.$get("rReceiver") as User; + } + + public async sender(): Promise { + return await this.$get("rSender") as User; + } +} diff --git a/src/lib/models/User.ts b/src/lib/models/User.ts new file mode 100644 index 0000000..1c4d567 --- /dev/null +++ b/src/lib/models/User.ts @@ -0,0 +1,122 @@ +import * as sqz from "sequelize"; +import { + BelongsToMany, + Column, + CreatedAt, + HasMany, + Model, + NotNull, + Table, + Unique, + UpdatedAt, +} from "sequelize-typescript"; +import {RequestNotFoundError} from "../errors/RequestNotFoundError"; +import {ChatMember} from "./ChatMember"; +import {ChatMessage} from "./ChatMessage"; +import {ChatRoom} from "./ChatRoom"; +import {Friendship} from "./Friendship"; +import {Post} from "./Post"; +import {PostVote} from "./PostVote"; +import {Request, RequestType} from "./Request"; + +@Table({underscored: true}) +export class User extends Model { + @NotNull + @Column({type: sqz.STRING(128), allowNull: false}) + public username: string; + + @NotNull + @Unique + @Column({type: sqz.STRING(128), allowNull: false, unique: true}) + public handle: string; + + @Unique + @NotNull + @Column({type: sqz.STRING(128), allowNull: false, unique: true}) + public email: string; + + @NotNull + @Column({type: sqz.STRING(128), allowNull: false}) + public password: string; + + @NotNull + @Column({defaultValue: 0, allowNull: false}) + public rankpoints: number; + + @BelongsToMany(() => User, () => Friendship) + public friends: User[]; + + @BelongsToMany(() => Post, () => PostVote) + public votes: Array; + + @BelongsToMany(() => ChatRoom, () => ChatMember) + public rChats: ChatRoom[]; + + @HasMany(() => Post, "authorId") + public rPosts: Post[]; + + @HasMany(() => Request, "receiverId") + public rSentRequests: Request[]; + + @HasMany(() => Request, "receiverId") + public rReceivedRequests: Request[]; + + @HasMany(() => ChatMessage, "authorId") + public messages: ChatMessage[]; + + @CreatedAt + public readonly createdAt!: Date; + + @UpdatedAt + public readonly updatedAt!: Date; + + public get name(): string { + return this.getDataValue("username"); + } + + public get joinedAt(): Date { + return this.getDataValue("createdAt"); + } + + public async chats(): Promise { + return await this.$get("rChats") as ChatRoom[]; + } + + public async sentRequests(): Promise { + return await this.$get("rSentRequests") as Request[]; + } + + public async receivedRequests(): Promise { + return await this.$get("rReceivedRequests") as Request[]; + } + + public async posts({first, offset}: {first: number, offset: number}): Promise { + return await this.$get("rPosts", {limit: first, offset}) as Post[]; + } + + public async numberOfPosts(): Promise { + return this.$count("rPosts"); + } + + public async denyRequest(sender: number, type: RequestType) { + const request = await this.$get("rReceivedRequests", + {where: {senderId: sender, requestType: type}}) as Request[]; + if (request[0]) { + await request[0].destroy(); + } + } + + public async acceptRequest(sender: number, type: RequestType) { + const requests = await this.$get("rReceivedRequests", + {where: {senderId: sender, requestType: type}}) as Request[]; + if (requests.length > 0) { + const request = requests[0]; + if (request.requestType === RequestType.FRIENDREQUEST) { + await this.$add("friends", sender); + await request.destroy(); + } + } else { + throw new RequestNotFoundError(sender, this.id, type); + } + } +} diff --git a/src/lib/models/index.ts b/src/lib/models/index.ts new file mode 100644 index 0000000..9e47059 --- /dev/null +++ b/src/lib/models/index.ts @@ -0,0 +1,8 @@ +export {ChatMember} from "./ChatMember"; +export {ChatMessage} from "./ChatMessage"; +export {ChatRoom} from "./ChatRoom"; +export {Friendship} from "./Friendship"; +export {Post} from "./Post"; +export {PostVote} from "./PostVote"; +export {Request} from "./Request"; +export {User} from "./User"; diff --git a/src/routes/home.ts b/src/routes/home.ts index 726b890..102b88f 100644 --- a/src/routes/home.ts +++ b/src/routes/home.ts @@ -1,10 +1,7 @@ import {Router} from "express"; import {Namespace, Server} from "socket.io"; import dataaccess from "../lib/dataaccess"; -import {ChatMessage} from "../lib/dataaccess/ChatMessage"; -import {Chatroom} from "../lib/dataaccess/Chatroom"; -import {Post} from "../lib/dataaccess/Post"; -import {Request} from "../lib/dataaccess/Request"; +import {ChatMessage, ChatRoom, Post, Request, User} from "../lib/models"; import globals from "../lib/globals"; import {InternalEvents} from "../lib/InternalEvents"; import Route from "../lib/Route"; @@ -37,18 +34,18 @@ class HomeRoute extends Route { socket.on("postCreate", async (content) => { if (socket.handshake.session.userId) { const post = await dataaccess.createPost(content, socket.handshake.session.userId); - io.emit("post", await post.resolvedData()); + io.emit("post", Object.assign(post, {htmlContent: post.htmlContent})); } else { socket.emit("error", "Not logged in!"); } }); - globals.internalEmitter.on(InternalEvents.REQUESTCREATE, (request: Request) => { - if (request.receiver.id === socket.handshake.session.userId) { - socket.emit("request", request.resolvedData()); + globals.internalEmitter.on(InternalEvents.REQUESTCREATE, async (request: Request) => { + if ((await request.$get("sender") as User).id === socket.handshake.session.userId) { + socket.emit("request", request); } }); globals.internalEmitter.on(InternalEvents.GQLPOSTCREATE, async (post: Post) => { - socket.emit("post", await post.resolvedData()); + socket.emit("post", Object.assign(post, {htmlContent: post.htmlContent})); }); }); @@ -56,7 +53,7 @@ class HomeRoute extends Route { for (const chat of chats) { chatRooms[chat.id] = this.getChatSocketNamespace(chat.id); } - globals.internalEmitter.on(InternalEvents.CHATCREATE, (chat: Chatroom) => { + globals.internalEmitter.on(InternalEvents.CHATCREATE, (chat: ChatRoom) => { chatRooms[chat.id] = this.getChatSocketNamespace(chat.id); }); } @@ -82,15 +79,15 @@ class HomeRoute extends Route { if (socket.handshake.session.userId) { const userId = socket.handshake.session.userId; const message = await dataaccess.sendChatMessage(userId, chatId, content); - socket.broadcast.emit("chatMessage", message.resolvedContent()); - socket.emit("chatMessageSent", message.resolvedContent()); + socket.broadcast.emit("chatMessage", Object.assign(message, {htmlContent: message.htmlContent})); + socket.emit("chatMessageSent", Object.assign(message, {htmlContent: message.htmlContent})); } else { socket.emit("error", "Not logged in!"); } }); - globals.internalEmitter.on(InternalEvents.GQLCHATMESSAGE, (message: ChatMessage) => { - if (message.chat.id === chatId) { - socket.emit("chatMessage", message.resolvedContent()); + globals.internalEmitter.on(InternalEvents.GQLCHATMESSAGE, async (message: ChatMessage) => { + if ((await message.$get("chat") as ChatRoom).id === chatId) { + socket.emit("chatMessage", Object.assign(message, {htmlContent: message.htmlContent})); } }); }); diff --git a/src/sql/create-tables.sql b/src/sql/create-tables.sql deleted file mode 100644 index 70e49e2..0000000 --- a/src/sql/create-tables.sql +++ /dev/null @@ -1,137 +0,0 @@ ---create functions -DO $$BEGIN - - IF NOT EXISTS(SELECT 1 from pg_proc WHERE proname = 'function_exists') THEN - CREATE FUNCTION function_exists(text) RETURNS boolean LANGUAGE plpgsql AS $BODY$ - BEGIN - RETURN EXISTS(SELECT 1 from pg_proc WHERE proname = $1); - END $BODY$; - END IF; - - IF NOT function_exists('type_exists') THEN - CREATE FUNCTION type_exists(text) RETURNS boolean LANGUAGE plpgsql AS $BODY$ - BEGIN - RETURN EXISTS (SELECT 1 FROM pg_type WHERE typname = $1); - END $BODY$; - END IF; - -END$$; - ---create types -DO $$ BEGIN - - IF NOT type_exists('votetype') THEN - CREATE TYPE votetype AS enum ('DOWNVOTE', 'UPVOTE'); - END IF; - - IF NOT type_exists('posttype') THEN - CREATE TYPE posttype AS enum ('MISC', 'ACTION', 'IMAGE', 'TEXT'); - END IF; - - IF NOT type_exists('requesttype') THEN - CREATE TYPE requesttype AS enum ('FRIENDREQUEST'); - END IF; - -END$$; - --- create functions relying on types - -DO $$ BEGIN - - IF NOT function_exists('cast_to_votetype') THEN - CREATE FUNCTION cast_to_votetype(text) RETURNS votetype LANGUAGE plpgsql AS $BODY$ - BEGIN - RETURN CASE WHEN $1::votetype IS NULL THEN 'UPVOTE' ELSE $1::votetype END; - END $BODY$; - END IF; - - IF NOT function_exists('cast_to_posttype') THEN - CREATE FUNCTION cast_to_posttype(text) RETURNS posttype LANGUAGE plpgsql AS $BODY$ - BEGIN - RETURN CASE WHEN $1::posttype IS NULL THEN 'MISC' ELSE $1::posttype END; - END $BODY$; - END IF; - -END$$; - --- create tables -DO $$ BEGIN - - CREATE TABLE IF NOT EXISTS "user_sessions" ( - "sid" varchar NOT NULL, - "sess" json NOT NULL, - "expire" timestamp(6) NOT NULL, - PRIMARY KEY ("sid") NOT DEFERRABLE INITIALLY IMMEDIATE - ) WITH (OIDS=FALSE); - - CREATE TABLE IF NOT EXISTS users ( - id SERIAL PRIMARY KEY, - name varchar(128) NOT NULL, - handle varchar(128) UNIQUE NOT NULL, - password varchar(1024) NOT NULL, - email varchar(128) UNIQUE NOT NULL, - greenpoints INTEGER DEFAULT 0, - joined_at TIMESTAMP DEFAULT now() - ); - - CREATE TABLE IF NOT EXISTS posts ( - id BIGSERIAL PRIMARY KEY, - upvotes INTEGER DEFAULT 0, - downvotes INTEGER DEFAULT 0, - created_at TIMESTAMP DEFAULT now(), - content text, - author SERIAL REFERENCES users (id) ON DELETE CASCADE, - type posttype NOT NULL DEFAULT 'MISC' - ); - - CREATE TABLE IF NOT EXISTS votes ( - user_id SERIAL REFERENCES users (id) ON DELETE CASCADE, - item_id BIGSERIAL REFERENCES posts (id) ON DELETE CASCADE, - vote_type votetype DEFAULT 'DOWNVOTE', - PRIMARY KEY (user_id, item_id) - ); - - CREATE TABLE IF NOT EXISTS events ( - id BIGSERIAL PRIMARY KEY, - time TIMESTAMP, - owner SERIAL REFERENCES users (id) - ); - - CREATE TABLE IF NOT EXISTS event_members ( - event BIGSERIAL REFERENCES events (id), - member SERIAL REFERENCES users (id), - PRIMARY KEY (event, member) - ); - - CREATE TABLE IF NOT EXISTS chats ( - id BIGSERIAL PRIMARY KEY - ); - - CREATE TABLE IF NOT EXISTS chat_messages ( - chat BIGSERIAL REFERENCES chats (id) ON DELETE CASCADE, - author SERIAL REFERENCES users (id) ON DELETE SET NULL, - content VARCHAR(1024) NOT NULL, - created_at TIMESTAMP DEFAULT now(), - PRIMARY KEY (chat, author, created_at) - ); - - CREATE TABLE IF NOT EXISTS chat_members ( - chat BIGSERIAL REFERENCES chats (id) ON DELETE CASCADE, - member SERIAL REFERENCES users (id) ON DELETE CASCADE, - PRIMARY KEY (chat, member) - ); - - CREATE TABLE IF NOT EXISTS user_friends ( - user_id SERIAL REFERENCES users (id) ON DELETE CASCADE, - friend_id SERIAL REFERENCES users (id) ON DELETE CASCADE, - PRIMARY KEY (user_id, friend_id) - ); - - CREATE TABLE IF NOT EXISTS requests ( - sender SERIAL REFERENCES users (id) ON DELETE CASCADE, - receiver SERIAL REFERENCES users (id) ON DELETE CASCADE, - type requesttype DEFAULT 'FRIENDREQUEST', - PRIMARY KEY (sender, receiver, type) - ); - -END $$; diff --git a/src/sql/update-tables.sql b/src/sql/update-tables.sql deleted file mode 100644 index 858a214..0000000 --- a/src/sql/update-tables.sql +++ /dev/null @@ -1,19 +0,0 @@ -DO $$ BEGIN - - ALTER TABLE IF EXISTS votes - ADD COLUMN IF NOT EXISTS vote_type votetype DEFAULT 'UPVOTE', - ALTER COLUMN vote_type TYPE votetype USING cast_to_votetype(vote_type::text), - ALTER COLUMN vote_type DROP DEFAULT, - ALTER COLUMN vote_type SET DEFAULT 'UPVOTE'; - - ALTER TABLE IF EXISTS posts - ALTER COLUMN type TYPE posttype USING cast_to_posttype(type::text), - ALTER COLUMN type DROP DEFAULT, - ALTER COLUMN type SET DEFAULT 'MISC', - DROP COLUMN IF EXISTS upvotes, - DROP COLUMN IF EXISTS downvotes; - - ALTER TABLE requests - ADD COLUMN IF NOT EXISTS type requesttype DEFAULT 'FRIENDREQUEST'; - -END $$; diff --git a/tsconfig.json b/tsconfig.json index 9ef65e8..f8deaa4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,12 +4,15 @@ "noImplicitAny": true, "removeComments": true, "preserveConstEnums": true, + "allowSyntheticDefaultImports": true, "outDir": "./dist", "sourceMap": true, "target": "es2018", "allowJs": true, "moduleResolution": "node", - "module": "commonjs" + "module": "commonjs", + "experimentalDecorators": true, + "emitDecoratorMetadata": true }, "include": [ "src/**/*" @@ -18,4 +21,4 @@ "node_modules", "**/*.spec.ts" ] -} \ No newline at end of file +} diff --git a/tslint.json b/tslint.json index 78800e0..642f353 100644 --- a/tslint.json +++ b/tslint.json @@ -21,7 +21,8 @@ }, "no-namespace": false, "no-internal-module": false, - "max-classes-per-file": false + "max-classes-per-file": false, + "no-var-requires": false }, "jsRules": { "max-line-length": {