From 57338355a7ba0e35927fe07bfe7a36c5e8259ab6 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Sat, 12 Oct 2019 23:32:56 +0200 Subject: [PATCH 1/2] Added Sequelize - added sequelize for database management - added sequelize models under dataaccess/datamodels - removed postgres-only stuff --- CHANGELOG.md | 3 +- package-lock.json | 624 +++++++++++++++++------- package.json | 6 +- src/app.ts | 23 +- src/default-config.yaml | 6 +- src/graphql/resolvers.ts | 18 +- src/graphql/schema.graphql | 3 + src/lib/QueryHelper.ts | 212 -------- src/lib/Route.ts | 1 + src/lib/dataaccess/ChatMessage.ts | 37 +- src/lib/dataaccess/Chatroom.ts | 60 +-- src/lib/dataaccess/DataObject.ts | 40 -- src/lib/dataaccess/Post.ts | 134 ++--- src/lib/dataaccess/Profile.ts | 168 ++++--- src/lib/dataaccess/Request.ts | 24 - src/lib/dataaccess/User.ts | 123 +---- src/lib/dataaccess/datamodels/index.ts | 12 + src/lib/dataaccess/datamodels/models.ts | 279 +++++++++++ src/lib/dataaccess/index.ts | 179 ++----- src/lib/dataaccess/wrappers.ts | 5 + src/lib/errors/graphqlErrors.ts | 1 - src/lib/globals.ts | 2 +- src/routes/home.ts | 22 +- tsconfig.json | 3 +- tslint.json | 3 +- 25 files changed, 1022 insertions(+), 966 deletions(-) delete mode 100644 src/lib/QueryHelper.ts delete mode 100644 src/lib/dataaccess/DataObject.ts delete mode 100644 src/lib/dataaccess/Request.ts create mode 100644 src/lib/dataaccess/datamodels/index.ts create mode 100644 src/lib/dataaccess/datamodels/models.ts create mode 100644 src/lib/dataaccess/wrappers.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 01a9eb2..a76df00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,10 +8,9 @@ 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 modules and integration diff --git a/package-lock.json b/package-lock.json index 44c83f2..2249f82 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", @@ -204,8 +225,7 @@ "@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 + "integrity": "sha512-FMdVn84tJJdV+xe+53sYiZS4R5yn1mAIxfj+DVoNiQjTYz1+OYmjwEZr1ev9nU0axXwda0QDbYl06QHanRVH3A==" }, "@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", @@ -1443,11 +1485,34 @@ "pg": "^7.4.3" } }, + "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": { + "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 +1664,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 +1687,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 +1732,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 +1786,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 +1821,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 +1847,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 +1910,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 +2285,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 +2361,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 +2378,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 +2533,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 +2574,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 +2637,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 +2659,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -2592,14 +2680,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 +2700,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 +2827,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -2757,7 +2839,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -2772,7 +2853,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -2780,14 +2860,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 +2884,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -2887,8 +2964,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -2900,7 +2976,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -2986,8 +3061,7 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -3023,7 +3097,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 +3116,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -3087,14 +3159,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 } } }, @@ -3124,7 +3194,6 @@ "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 +3209,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 +3246,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 +3254,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 +3639,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 +3711,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 +3783,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 +3807,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 +3835,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 +3857,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 +3906,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 +3926,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 +3956,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 +4021,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 +4099,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 +4148,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 +4184,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 +4205,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 +4219,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 +4624,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 +4631,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 +4682,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 +4689,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 +4720,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 +4768,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 +4832,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 +4979,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 +5007,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 +5055,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 +5152,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 +5223,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 +5237,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 +5330,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 +5371,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 +5558,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 +5698,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 +5726,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", @@ -5671,6 +5866,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 +5994,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 +6073,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 +6093,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 +6128,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 +6157,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 +6195,53 @@ } } }, + "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==" + }, "serve-static": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", @@ -5996,8 +6256,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 +6275,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 +6633,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 +6747,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 +6757,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 +6806,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 +7081,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 +7098,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 +7218,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 +7225,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 +7432,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 +7461,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 +7482,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 +7502,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 +7645,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 +7717,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 +7755,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..c47a635 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "@types/markdown-it": "0.0.9", "@types/node": "^12.7.8", "@types/pg": "^7.11.0", + "@types/sequelize": "^4.28.5", "@types/socket.io": "^2.1.2", "@types/winston": "^2.4.4", "delete": "^1.1.0", @@ -49,7 +50,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 +58,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 +66,9 @@ "markdown-it-emoji": "^1.4.0", "pg": "^7.12.1", "pug": "^2.0.4", + "sequelize": "^5.19.6", "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..dabc2ec 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,5 +1,4 @@ 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"; @@ -10,33 +9,35 @@ import {buildSchema} from "graphql"; import {importSchema} from "graphql-import"; import * as http from "http"; import * as path from "path"; +import {Sequelize} from "sequelize"; 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"; +import * as fsx from "fs-extra"; +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..58a8ecc 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://:memory:" # http server configuration server: diff --git a/src/graphql/resolvers.ts b/src/graphql/resolvers.ts index 7b35d4b..23745e3 100644 --- a/src/graphql/resolvers.ts +++ b/src/graphql/resolvers.ts @@ -1,7 +1,9 @@ import {GraphQLError} from "graphql"; import * as status from "http-status"; +import {Sequelize} from "sequelize"; import dataaccess from "../lib/dataaccess"; import {Chatroom} from "../lib/dataaccess/Chatroom"; +import * as models from "../lib/dataaccess/datamodels"; import {Post} from "../lib/dataaccess/Post"; import {Profile} from "../lib/dataaccess/Profile"; import {User} from "../lib/dataaccess/User"; @@ -17,9 +19,10 @@ 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); + const user = await models.SqUser.findByPk(req.session.userId); + return user.profile; } else { res.status(status.UNAUTHORIZED); return new NotLoggedInGqlError(); @@ -29,7 +32,8 @@ export function resolver(req: any, res: any): any { if (handle) { return await dataaccess.getUserByHandle(handle); } else if (userId) { - return new User(userId); + const user = await models.SqUser.findByPk(userId); + return user.user; } else { res.status(status.BAD_REQUEST); return new GraphQLError("No userId or handle provided."); @@ -45,7 +49,8 @@ export function resolver(req: any, res: any): any { }, async getChat({chatId}: { chatId: number }) { if (chatId) { - return new Chatroom(chatId); + const chat = await models.SqChat.findByPk(chatId); + return new Chatroom(chat); } else { res.status(status.BAD_REQUEST); return new GraphQLError("No chatId given."); @@ -105,7 +110,8 @@ 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.SqPost.findByPk(postId); + return await (post.post).vote(req.session.userId, type); } else { res.status(status.UNAUTHORIZED); return new NotLoggedInGqlError(); @@ -132,7 +138,7 @@ export function resolver(req: any, res: any): any { }, async deletePost({postId}: { postId: number }) { if (postId) { - const post = new Post(postId); + const post = (await models.SqPost.findByPk(postId)).post; if ((await post.author()).id === req.session.userId) { return await dataaccess.deletePost(post.id); } else { 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/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/ChatMessage.ts b/src/lib/dataaccess/ChatMessage.ts index 4c91a16..1e42a05 100644 --- a/src/lib/dataaccess/ChatMessage.ts +++ b/src/lib/dataaccess/ChatMessage.ts @@ -1,31 +1,38 @@ import markdown from "../markdown"; import {Chatroom} from "./Chatroom"; +import * as models from "./datamodels/models"; import {User} from "./User"; export class ChatMessage { - constructor( - public readonly author: User, - public readonly chat: Chatroom, - public readonly createdAt: number, - public readonly content: string) {} + + public id: number; + public content: string; + public createdAt: Date; + + constructor(private message: models.ChatMessage) { + this.id = message.id; + this.content = message.content; + this.createdAt = message.createdAt; + } + + /** + * returns the author of the chat message. + */ + public async author(): Promise { + return new User(await this.message.getAuthor()); + } /** - * The content rendered by markdown-it. + * Returns the rendered html content of the chat message. */ public htmlContent(): string { return markdown.renderInline(this.content); } /** - * Returns resolved and rendered content of the chat message. + * returns the chatroom for the chatmessage. */ - public resolvedContent() { - return { - author: this.author.id, - chat: this.chat.id, - content: this.content, - createdAt: this.createdAt, - htmlContent: this.htmlContent(), - }; + public async chat(): Promise { + return (await this.message.getChat()).chatroom; } } diff --git a/src/lib/dataaccess/Chatroom.ts b/src/lib/dataaccess/Chatroom.ts index e541f06..3e08389 100644 --- a/src/lib/dataaccess/Chatroom.ts +++ b/src/lib/dataaccess/Chatroom.ts @@ -1,44 +1,22 @@ -import globals from "../globals"; -import {ChatMessage} from "./ChatMessage"; -import {queryHelper} from "./index"; +import {SqChat} from "./datamodels"; import {User} from "./User"; export class Chatroom { + public readonly id: number; 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; + constructor(private chat: SqChat) { + this.id = chat.id; + this.namespace = `/chat/${chat.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; + const members = await this.chat.getMembers(); + return members.map((m) => new User(m)); } /** @@ -47,30 +25,14 @@ export class Chatroom { * @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}) { + 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)); - } + const messages = await this.chat.getMessages({limit: lim, offset: offs}); if (containing) { - return messages.filter((x) => x.content.includes(containing)); + return messages.filter((x) => x.content.includes(containing)).map((m) => m.message); } else { - return messages; + return messages.map((m) => m.message); } } } 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 index e4912c7..1105499 100644 --- a/src/lib/dataaccess/Post.ts +++ b/src/lib/dataaccess/Post.ts @@ -1,106 +1,68 @@ import markdown from "../markdown"; -import {DataObject} from "./DataObject"; -import {queryHelper} from "./index"; +import {SqPost, SqPostVotes} from "./datamodels"; +import {PostVotes} from "./datamodels/models"; import dataaccess from "./index"; import {User} from "./User"; -export class Post extends DataObject { +export class Post { public readonly id: number; - private $createdAt: string; - private $content: string; - private $author: number; - private $type: string; + public createdAt: Date; + public content: string; + public 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, - }; + private post: SqPost; + + constructor(post: SqPost) { + this.id = post.id; + this.createdAt = post.createdAt; + this.post = post; + this.type = ""; + this.content = post.content; } + /** * 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; + return PostVotes.count({where: {voteType: dataaccess.VoteType.UPVOTE, post_id: this.id}}); } /** * 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; + return PostVotes.count({where: {voteType: dataaccess.VoteType.DOWNVOTE, post_id: this.id}}); } /** * 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; + return markdown.render(this.content); } /** * The autor of the post. */ public async author(): Promise { - await this.loadDataIfNotExists(); - return new User(this.$author); + return new User(await this.post.getUser()); } /** * Deletes the post. */ public async delete(): Promise { - const query = await queryHelper.first({ - text: "DELETE FROM posts WHERE id = $1", - values: [this.id], - }); + await this.post.destroy(); } /** * 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; + const votes = await this.post.getVotes({where: {userId}}); + + if (votes.length >= 1) { + return votes[0].voteType; } else { return null; } @@ -112,48 +74,10 @@ export class Post extends DataObject { * @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; - } + const [vote, _] = await SqPostVotes + .findOrCreate({where: {userId}, defaults: {voteType: type, postId: this.post.id}}); + vote.voteType = type; + await vote.save(); + return vote.voteType; } } diff --git a/src/lib/dataaccess/Profile.ts b/src/lib/dataaccess/Profile.ts index 090e6da..a9eaba2 100644 --- a/src/lib/dataaccess/Profile.ts +++ b/src/lib/dataaccess/Profile.ts @@ -1,10 +1,61 @@ import {RequestNotFoundError} from "../errors/RequestNotFoundError"; import {Chatroom} from "./Chatroom"; -import dataaccess, {queryHelper} from "./index"; -import {User} from "./User"; -import {Request} from "./Request"; +import {SqUser} from "./datamodels"; +import dataaccess from "./index"; +import * as wrappers from "./wrappers"; -export class Profile extends User { +export class Profile { + + public id: number; + public name: string; + public handle: string; + public email: string; + public greenpoints: number; + public joinedAt: Date; + + protected user: SqUser; + + constructor(user: SqUser) { + this.name = user.username; + this.handle = user.handle; + this.email = user.email; + this.greenpoints = user.rankpoints; + this.joinedAt = user.joinedAt; + this.id = user.id; + this.user = user; + } + + /** + * Returns the number of posts the user created + */ + public async numberOfPosts(): Promise { + return this.user.countPosts(); + } + + /** + * Returns all friends of the user. + */ + public async friends(): Promise { + const result = await this.user.getFriends(); + const userFriends = []; + for (const friend of result) { + userFriends.push(new wrappers.User(friend)); + } + return userFriends; + } + + /** + * Returns all posts for a user. + */ + public async posts({first, offset}: { first: number, offset: number }): Promise { + const postRes = await this.user.getPosts(); + const posts = []; + + for (const post of postRes) { + posts.push(new wrappers.Post(post)); + } + return posts; + } /** * Returns all chatrooms (with pagination). @@ -12,19 +63,14 @@ export class Profile extends User { * @param first * @param offset */ - public async chats({first, offset}: {first: number, offset?: number}): Promise { - if (!(await this.exists())) { - return []; - } + public async chats({first, offset}: { first: number, offset?: number }): Promise { 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], - }); + const result = await this.user.getChats(); + if (result) { - return result.map((row) => new Chatroom(row.chat)); + return result.map((chat) => new Chatroom(chat)); } else { return []; } @@ -34,24 +80,14 @@ export class Profile extends User { * 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); + return this.user.getSentRequests(); } /** * 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); + return this.user.getReceivedRequests(); } /** @@ -59,11 +95,9 @@ export class Profile extends 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; + this.user.rankpoints = points; + await this.user.save(); + return this.user.rankpoints; } /** @@ -71,22 +105,18 @@ export class Profile extends 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; + this.user.email = email; + await this.user.save(); + return this.user.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; + this.user.handle = handle; + await this.user.save(); + return this.user.handle; } /** @@ -94,11 +124,9 @@ export class Profile extends 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; + this.user.username = name; + await this.user.save(); + return this.user.username; } /** @@ -107,10 +135,10 @@ export class Profile extends User { * @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], - }); + const request = await this.user.getReceivedRequests({where: {senderId: sender, requestType: type}}); + if (request[0]) { + await request[0].destroy(); + } } /** @@ -119,45 +147,15 @@ export class Profile extends User { * @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], - }); + const requests = await this.user.getReceivedRequests({where: {senderId: sender, requestType: type}}); + if (requests.length > 0) { + const request = requests[0]; + if (request.requestType === dataaccess.RequestType.FRIENDREQUEST) { + await this.user.addFriend(sender); + await request.destroy(); } } 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 index b3501cc..8d0b19e 100644 --- a/src/lib/dataaccess/User.ts +++ b/src/lib/dataaccess/User.ts @@ -1,84 +1,39 @@ -import globals from "../globals"; -import {DataObject} from "./DataObject"; -import {queryHelper} from "./index"; -import {Post} from "./Post"; +import {SqUser} from "./datamodels"; +import * as wrappers from "./wrappers"; -export class User extends DataObject { - private $name: string; - private $handle: string; - private $email: string; - private $greenpoints: number; - private $joinedAt: string; - private $exists: boolean; +export class User { + public id: number; + public name: string; + public handle: string; + public greenpoints: number; + public joinedAt: Date; - /** - * 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; - } + protected user: SqUser; - /** - * 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; + constructor(user: SqUser) { + this.id = user.id; + this.name = user.username; + this.handle = user.handle; + this.greenpoints = user.rankpoints; + this.joinedAt = user.joinedAt; + this.user = user; } /** * 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); + return this.user.countPosts(); } /** * 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 result = await this.user.getFriends(); 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)); - } + for (const friend of result) { + userFriends.push(new User(friend)); } return userFriends; } @@ -86,43 +41,13 @@ export class User extends DataObject { /** * 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], - }); + public async posts({first, offset}: { first: number, offset: number }): Promise { + const postRes = await this.user.getPosts(); const posts = []; - for (const row of result) { - posts.push(new Post(row.id, row)); + for (const post of postRes) { + posts.push(new wrappers.Post(post)); } 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/datamodels/index.ts b/src/lib/dataaccess/datamodels/index.ts new file mode 100644 index 0000000..9d57bf6 --- /dev/null +++ b/src/lib/dataaccess/datamodels/index.ts @@ -0,0 +1,12 @@ +export { + init as datainit, + User as SqUser, + Post as SqPost, + Chat as SqChat, + Request as SqRequest, + PostVotes as SqPostVotes, + ChatMessage as SqChatMessage, + ChatMembers as SqChatMembers, + RequestType as SqRequestType, + UserFriends as SqUserFriends, +} from "./models"; diff --git a/src/lib/dataaccess/datamodels/models.ts b/src/lib/dataaccess/datamodels/models.ts new file mode 100644 index 0000000..1780124 --- /dev/null +++ b/src/lib/dataaccess/datamodels/models.ts @@ -0,0 +1,279 @@ +// tslint:disable:object-literal-sort-keys + +import * as sqz from "sequelize"; +import { + Association, + BelongsToGetAssociationMixin, + BelongsToManyAddAssociationMixin, + BelongsToManyCountAssociationsMixin, + BelongsToManyCreateAssociationMixin, + BelongsToManyGetAssociationsMixin, + BelongsToManyHasAssociationMixin, + DataTypes, + HasManyAddAssociationMixin, + HasManyCountAssociationsMixin, + HasManyCreateAssociationMixin, + HasManyGetAssociationsMixin, + HasManyHasAssociationMixin, + HasOneGetAssociationMixin, + Model, + Sequelize, +} from "sequelize"; +import * as wrappers from "../wrappers"; + +const underscored = true; + +enum VoteType { + UPVOTE = "UPVOTE", + DOWNVOTE = "DOWNVOTE", +} + +export enum RequestType { + FRIENDREQUEST = "FRIENDREQUEST", + GROUPINVITE = "GROUPINVITE", + EVENTINVITE = "EVENTINVITE", +} + +export class User extends Model { + + public static associations: { + friends: Association; + posts: Association; + votes: Association; + requests: Association; + }; + + public id!: number; + public username!: string; + public handle!: string; + public email!: string; + public password!: string; + public rankpoints!: number; + + public readonly createdAt!: Date; + public readonly updatedAt!: Date; + + public getFriends!: HasManyGetAssociationsMixin; + public addFriend!: HasManyAddAssociationMixin; + public hasFriend!: HasManyHasAssociationMixin; + public countFriends!: HasManyCountAssociationsMixin; + + public getPosts!: HasManyGetAssociationsMixin; + public addPost!: HasManyAddAssociationMixin; + public hasPost!: HasManyHasAssociationMixin; + public countPosts!: HasManyCountAssociationsMixin; + public createPost!: HasManyCreateAssociationMixin; + + public getReceivedRequests!: HasManyGetAssociationsMixin; + public addReceivedRequest!: HasManyAddAssociationMixin; + public hasReceivedRequest!: HasManyHasAssociationMixin; + public countReceivedRequests!: HasManyCountAssociationsMixin; + public createReceivedRequest!: HasManyCreateAssociationMixin; + + + public getSentRequests!: HasManyGetAssociationsMixin; + public addSentRequest!: HasManyAddAssociationMixin; + public hasSentRequest!: HasManyHasAssociationMixin; + public countSentRequests!: HasManyCountAssociationsMixin; + public createSentRequest!: HasManyCreateAssociationMixin; + + public getChats!: BelongsToManyGetAssociationsMixin; + public addChat!: BelongsToManyAddAssociationMixin; + public hasChat!: BelongsToManyHasAssociationMixin; + public countChats!: BelongsToManyCountAssociationsMixin; + public createChat!: BelongsToManyCreateAssociationMixin; + + /** + * Getter for joined at as the date the entry was created. + */ + public get joinedAt(): Date { + // @ts-ignore + return this.getDataValue("createdAt"); + } + + /** + * Wraps itself into a user + */ + public get user(): wrappers.User { + return new wrappers.User(this); + } + + /** + * returns the username. + */ + public get name(): string { + return this.getDataValue("username"); + } + + /** + * Wraps itself into a profile. + */ + public get profile(): wrappers.Profile { + return new wrappers.Profile(this); + } +} + +export class UserFriends extends Model { +} + +export class Post extends Model { + + public static associations: { + author: Association, + votes: Association, + }; + + public id!: number; + public content!: string; + + public readonly createdAt!: Date; + public readonly updatedAt!: Date; + + public getUser!: BelongsToGetAssociationMixin; + + public getVotes!: HasManyGetAssociationsMixin; + public addVote!: HasManyAddAssociationMixin; + public hasVote!: HasManyHasAssociationMixin; + public countVotes!: HasManyCountAssociationsMixin; + public createVote!: HasManyCreateAssociationMixin; + + /** + * Wraps itself into a Post instance. + */ + public get post(): wrappers.Post { + return new wrappers.Post(this); + } +} + +export class PostVotes extends Model { + public voteType: VoteType; +} + +export class Request extends Model { + public id!: number; + public requestType!: RequestType; + + public getSender!: HasOneGetAssociationMixin; + public getReceiver!: HasOneGetAssociationMixin; +} + +export class Chat extends Model { + public static associations: { + members: Association, + messages: Association, + }; + + public id!: number; + + public readonly createdAt!: Date; + public readonly updatedAt!: Date; + + public getMembers!: BelongsToManyGetAssociationsMixin; + public addMember!: BelongsToManyAddAssociationMixin; + public hasMember!: BelongsToManyHasAssociationMixin; + public countMembers!: BelongsToManyCountAssociationsMixin; + + public getMessages!: HasManyGetAssociationsMixin; + public addMessage!: HasManyAddAssociationMixin; + public hasMessage!: HasManyHasAssociationMixin; + public countMessages!: HasManyCountAssociationsMixin; + public createMessage!: HasManyCreateAssociationMixin; + + /** + * wraps itself into a chatroom. + */ + public get chatroom(): wrappers.Chatroom { + return new wrappers.Chatroom(this); + } +} + +export class ChatMembers extends Model { +} + +export class ChatMessage extends Model { + public id: number; + public content!: string; + public readonly createdAt!: Date; + public readonly updatedAt!: Date; + + public getAuthor!: BelongsToGetAssociationMixin; + public getChat!: BelongsToGetAssociationMixin; + + public get message(): wrappers.ChatMessage { + return new wrappers.ChatMessage(this); + } +} + +export function init(sequelize: Sequelize) { + User.init({ + username: { + allowNull: false, + type: sqz.STRING(128), + }, + handle: { + allowNull: false, + type: sqz.STRING(128), + unique: true, + }, + email: { + allowNull: false, + type: sqz.STRING(128), + unique: true, + }, + password: { + allowNull: false, + type: sqz.STRING(128), + }, + rankpoints: { + allowNull: false, + type: DataTypes.INTEGER, + defaultValue: 0, + }, + }, {sequelize, underscored}); + + UserFriends.init({}, {sequelize, underscored}); + + Post.init({ + content: DataTypes.TEXT, + }, {sequelize, underscored}); + + PostVotes.init({ + voteType: { + type: DataTypes.ENUM, + values: ["UPVOTE", "DOWNVOTE"], + }, + }, {sequelize, underscored}); + + Request.init({ + requestType: { + type: DataTypes.ENUM, + values: ["FRIENDREQUEST", "GROUPINVITE", "EVENTINVITE"], + }, + }, {sequelize, underscored}); + + Chat.init({}, {sequelize, underscored}); + + ChatMembers.init({}, {sequelize, underscored}); + + ChatMessage.init({ + content: { + type: DataTypes.TEXT, + allowNull: false, + }, + }, {sequelize, underscored}); + + User.belongsToMany(User, {through: UserFriends, as: "friends"}); + Post.belongsTo(User, {foreignKey: "userId"}); + User.hasMany(Post, {as: "posts", foreignKey: "userId"}); + Post.belongsToMany(User, {through: PostVotes, as: "votes"}); + User.belongsToMany(Post, {through: PostVotes, as: "votes"}); + User.hasMany(Request, {as: "sentRequests"}); + User.hasMany(Request, {as: "receivedRequests"}); + User.belongsToMany(Chat, {through: ChatMembers}); + Chat.belongsToMany(User, {through: ChatMembers, as: "members"}); + Chat.hasMany(ChatMessage, {as: "messages"}); + ChatMessage.belongsTo(Chat); + ChatMessage.belongsTo(User, {as: "author", foreignKey: "userId"}); + User.hasMany(ChatMessage, {foreignKey: "userId"}); +} + diff --git a/src/lib/dataaccess/index.ts b/src/lib/dataaccess/index.ts index 3e6628d..9fc6359 100644 --- a/src/lib/dataaccess/index.ts +++ b/src/lib/dataaccess/index.ts @@ -1,30 +1,19 @@ -import {Pool} from "pg"; +import {Sequelize} from "sequelize"; 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 * as models from "./datamodels"; 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 @@ -38,14 +27,15 @@ function generateHandle(username: string) { */ namespace dataaccess { - export const pool: Pool = dbClient; + let sequelize: Sequelize; /** * Initializes everything that needs to be initialized asynchronous. */ - export async function init() { + export async function init(seq: Sequelize) { + sequelize = seq; try { - await queryHelper.init(); + await models.datainit(sequelize); } catch (err) { globals.logger.error(err.message); globals.logger.debug(err.stack); @@ -57,12 +47,9 @@ namespace dataaccess { * @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); + const user = await models.SqUser.findOne({where: {handle: userHandle}}); + if (user) { + return new User(user); } else { throw new UserNotFoundError(userHandle); } @@ -74,12 +61,9 @@ namespace dataaccess { * @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); + const user = await models.SqUser.findOne({where: {email, password}}); + if (user) { + return new Profile(user); } else { throw new UserNotFoundError(email); } @@ -92,16 +76,11 @@ namespace dataaccess { * @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); + const existResult = !!(await models.SqUser.findOne({where: {username, email, password}})); + const handle = generateHandle(username); + if (!existResult) { + const user = await models.SqUser.create({username, email, password, handle}); + return new Profile(user); } else { throw new EmailAlreadyRegisteredError(email); } @@ -112,12 +91,9 @@ namespace dataaccess { * @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); + const post = await models.SqPost.findByPk(postId); + if (post) { + return new Post(post); } else { return null; } @@ -131,33 +107,18 @@ namespace dataaccess { */ 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; + const posts = await models.SqPost.findAll({order: [["createdAt", "DESC"]], limit: first, offset}); + return posts.map((p) => new Post(p)); } 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; + const results: models.SqPost[] = await sequelize.query( + `SELECT id 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 ? OFFSET ?`, + {replacements: [first, offset], mapToModel: true, model: models.SqPost}); + + return results.map((p) => new Post(p)); } } @@ -169,11 +130,8 @@ namespace dataaccess { */ 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); + const sqPost = await models.SqPost.create({content, userId: authorId}); + const post = new Post(sqPost); globals.internalEmitter.emit(InternalEvents.POSTCREATE, post); return post; } @@ -183,10 +141,7 @@ namespace dataaccess { * @param postId */ export async function deletePost(postId: number): Promise { - const result = await queryHelper.first({ - text: "DELETE FROM posts WHERE posts.id = $1", - values: [postId], - }); + await (await models.SqPost.findByPk(postId)).destroy(); return true; } @@ -195,31 +150,15 @@ namespace dataaccess { * @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(); + return sequelize.transaction(async (t) => { + const chat = await models.SqChat.create({}, {transaction: t}); 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 chat.addMember(Number(member), {transaction: t}); } - 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; + const chatroom = new Chatroom(chat); + globals.internalEmitter.emit(InternalEvents.CHATCREATE, chatroom); + return chatroom; + }); } /** @@ -229,15 +168,11 @@ namespace dataaccess { * @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; + const chat = await models.SqChat.findByPk(chatId); + if (chat) { + const message = await chat.createMessage({content, userId: authorId}); + globals.internalEmitter.emit(InternalEvents.CHATMESSAGE, message.message); + return message.message; } else { throw new ChatNotFoundError(chatId); } @@ -247,30 +182,20 @@ namespace dataaccess { * 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; + const chats = await models.SqChat.findAll(); + return chats.map((c) => new Chatroom(c)); } /** * Sends a request to a user. * @param sender * @param receiver - * @param type + * @param requestType */ - export async function createRequest(sender: number, receiver: number, type?: RequestType) { - type = type || RequestType.FRIENDREQUEST; + export async function createRequest(sender: number, receiver: number, requestType?: RequestType) { + requestType = requestType || 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); + const request = await models.SqRequest.create({senderId: sender, receiverId: receiver, requestType}); globals.internalEmitter.emit(InternalEvents.REQUESTCREATE, Request); return request; } diff --git a/src/lib/dataaccess/wrappers.ts b/src/lib/dataaccess/wrappers.ts new file mode 100644 index 0000000..052fb95 --- /dev/null +++ b/src/lib/dataaccess/wrappers.ts @@ -0,0 +1,5 @@ +export {User} from "./User"; +export {Chatroom} from "./Chatroom"; +export {Post} from "./Post"; +export {Profile} from "./Profile"; +export {ChatMessage} from "./ChatMessage"; diff --git a/src/lib/errors/graphqlErrors.ts b/src/lib/errors/graphqlErrors.ts index 9784712..9e0b1b5 100644 --- a/src/lib/errors/graphqlErrors.ts +++ b/src/lib/errors/graphqlErrors.ts @@ -1,5 +1,4 @@ import {GraphQLError} from "graphql"; -import {BaseError} from "./BaseError"; export class NotLoggedInGqlError extends GraphQLError { diff --git a/src/lib/globals.ts b/src/lib/globals.ts index cea8e3c..0a6669a 100644 --- a/src/lib/globals.ts +++ b/src/lib/globals.ts @@ -35,7 +35,7 @@ namespace globals { 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}`; }), ), diff --git a/src/routes/home.ts b/src/routes/home.ts index 726b890..4fb7f79 100644 --- a/src/routes/home.ts +++ b/src/routes/home.ts @@ -3,8 +3,8 @@ import {Namespace, Server} from "socket.io"; import dataaccess from "../lib/dataaccess"; import {ChatMessage} from "../lib/dataaccess/ChatMessage"; import {Chatroom} from "../lib/dataaccess/Chatroom"; +import {Request} from "../lib/dataaccess/datamodels/models"; import {Post} from "../lib/dataaccess/Post"; -import {Request} from "../lib/dataaccess/Request"; import globals from "../lib/globals"; import {InternalEvents} from "../lib/InternalEvents"; import Route from "../lib/Route"; @@ -37,18 +37,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.getSender()).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()})); }); }); @@ -82,15 +82,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.chat()).id === chatId) { + socket.emit("chatMessage", Object.assign(message, {htmlContent: message.htmlContent()})); } }); }); diff --git a/tsconfig.json b/tsconfig.json index 9ef65e8..6fedd9d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,6 +4,7 @@ "noImplicitAny": true, "removeComments": true, "preserveConstEnums": true, + "allowSyntheticDefaultImports": true, "outDir": "./dist", "sourceMap": true, "target": "es2018", @@ -18,4 +19,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": { From 511a446c719b1abfdc0794e5f1aecf55dcd6d935 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Sun, 13 Oct 2019 00:20:46 +0200 Subject: [PATCH 2/2] Fixed user voting --- src/graphql/resolvers.ts | 2 +- src/lib/dataaccess/Post.ts | 20 ++++++++++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/graphql/resolvers.ts b/src/graphql/resolvers.ts index 23745e3..fe4e76a 100644 --- a/src/graphql/resolvers.ts +++ b/src/graphql/resolvers.ts @@ -111,7 +111,7 @@ export function resolver(req: any, res: any): any { if (postId && type) { if (req.session.userId) { const post = await models.SqPost.findByPk(postId); - return await (post.post).vote(req.session.userId, type); + return await post.post.vote(req.session.userId, type); } else { res.status(status.UNAUTHORIZED); return new NotLoggedInGqlError(); diff --git a/src/lib/dataaccess/Post.ts b/src/lib/dataaccess/Post.ts index 1105499..db6690c 100644 --- a/src/lib/dataaccess/Post.ts +++ b/src/lib/dataaccess/Post.ts @@ -74,10 +74,22 @@ export class Post { * @param type */ public async vote(userId: number, type: dataaccess.VoteType): Promise { - const [vote, _] = await SqPostVotes - .findOrCreate({where: {userId}, defaults: {voteType: type, postId: this.post.id}}); - vote.voteType = type; - await vote.save(); + type = type || dataaccess.VoteType.UPVOTE; + let vote = await SqPostVotes.findOne({where: {user_id: userId, post_id: this.id}}); + if (!vote) { + await this.post.addVote(userId); + vote = await SqPostVotes.findOne({where: {user_id: userId, post_id: this.id}}); + } + if (vote) { + if (vote.voteType === type) { + await vote.destroy(); + return null; + } else { + vote.voteType = type; + await vote.save(); + } + } + return vote.voteType; } }