From 38f3d2673465bd05c8c691c1fbcd34d720cb0c83 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Wed, 21 Aug 2019 14:40:37 +0200 Subject: [PATCH 01/40] added dependencies --- CHANGELOG.md | 9 + package-lock.json | 6901 +++++++++++++++++++++++++++++++++++++++++++++ package.json | 54 + src/app.ts | 0 src/index.ts | 0 5 files changed, 6964 insertions(+) create mode 100644 CHANGELOG.md create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/app.ts create mode 100644 src/index.ts diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..c2e86b8 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,9 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..b623936 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6901 @@ +{ + "name": "greenvironment-server", + "version": "0.1.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@types/body-parser": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.17.1.tgz", + "integrity": "sha512-RoX2EZjMiFMjZh9lmYrwgoP9RTpAjSHiJxdp4oidAQVO02T7HER3xj9UKue5534ULWeqVEkujhWcyvUce+d68w==", + "dev": true, + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "@types/connect": { + "version": "3.4.32", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.32.tgz", + "integrity": "sha512-4r8qa0quOvh7lGD0pre62CAb1oni1OO6ecJLGCezTmhQ8Fz50Arx9RUszryR8KlgK6avuSXvviL6yWyViQABOg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/connect-pg-simple": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/connect-pg-simple/-/connect-pg-simple-4.2.0.tgz", + "integrity": "sha512-Y+ptWW6q6Ll92Y0Zbqb+YiPIUd5ldNsovZJ22Oy5wc4tyl0QVqXPx+ksYCrMgUINXeBDQxJi7HcZ5iATZ62x8A==", + "dev": true, + "requires": { + "@types/express": "*", + "@types/express-session": "*", + "@types/pg": "*" + } + }, + "@types/cookie-parser": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.2.tgz", + "integrity": "sha512-uwcY8m6SDQqciHsqcKDGbo10GdasYsPCYkH3hVegj9qAah6pX5HivOnOuI3WYmyQMnOATV39zv/Ybs0bC/6iVg==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, + "@types/express": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.1.tgz", + "integrity": "sha512-VfH/XCP0QbQk5B5puLqTLEeFgR8lfCJHZJKkInZ9mkYd+u8byX0kztXEQxEk4wZXJs8HI+7km2ALXjn4YKcX9w==", + "dev": true, + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "*", + "@types/serve-static": "*" + } + }, + "@types/express-graphql": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@types/express-graphql/-/express-graphql-0.8.0.tgz", + "integrity": "sha512-Gzhx6v15CRLnWbD7C9nmQTPd+QXRuJhOovQVyQjw15e2LjmVYI06BhUdAlGtf7sg/BMIeVW6M8RtNww+8HowuA==", + "dev": true, + "requires": { + "@types/express": "*", + "@types/graphql": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.16.9", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.16.9.tgz", + "integrity": "sha512-GqpaVWR0DM8FnRUJYKlWgyARoBUAVfRIeVDZQKOttLFp5SmhhF9YFIYeTPwMd/AXfxlP7xVO2dj1fGu0Q+krKQ==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/range-parser": "*" + } + }, + "@types/express-session": { + "version": "1.15.14", + "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.15.14.tgz", + "integrity": "sha512-7kVzFTT0Jy0zmUYDt9ik76XbcqyS9NalV4gn4eLwhk1nGQn+lS/HjPODhG3Oi/GBR2w1LQHUdkz/5KICYMACiw==", + "dev": true, + "requires": { + "@types/express": "*", + "@types/node": "*" + } + }, + "@types/express-socket.io-session": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/express-socket.io-session/-/express-socket.io-session-1.3.2.tgz", + "integrity": "sha512-zUdB2M6zrmjkekZplB+52EM5vw62lGLQPRfSGxMwnw7viG6pffJdLgICjlqaggleOLjg8/onmqaD60eRTI5j0Q==", + "dev": true, + "requires": { + "@types/express": "*", + "@types/socket.io": "*" + } + }, + "@types/fs-extra": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.0.0.tgz", + "integrity": "sha512-bCtL5v9zdbQW86yexOlXWTEGvLNqWxMFyi7gQA7Gcthbezr2cPSOb8SkESVKA937QD5cIwOFLDFt0MQoXOEr9Q==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/graphql": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@types/graphql/-/graphql-14.2.3.tgz", + "integrity": "sha512-UoCovaxbJIxagCvVfalfK7YaNhmxj3BQFRQ2RHQKLiu+9wNXhJnlbspsLHt/YQM99IaLUUFJNzCwzc6W0ypMeQ==", + "dev": true + }, + "@types/mime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz", + "integrity": "sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==", + "dev": true + }, + "@types/node": { + "version": "12.7.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.2.tgz", + "integrity": "sha512-dyYO+f6ihZEtNPDcWNR1fkoTDf3zAK3lAABDze3mz6POyIercH0lEUawUFXlG8xaQZmm1yEBON/4TsYv/laDYg==", + "dev": true + }, + "@types/pg": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-7.11.0.tgz", + "integrity": "sha512-wXduaNIDQp7w9ediwIRAH+FpdgtOlOwFVlYe9DtBPDczxcHgfb0blLNR7yYVNUMhspC0xOLykOvMDHavbO0Sxg==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/pg-types": "*" + } + }, + "@types/pg-types": { + "version": "1.11.4", + "resolved": "https://registry.npmjs.org/@types/pg-types/-/pg-types-1.11.4.tgz", + "integrity": "sha512-WdIiQmE347LGc1Vq3Ki8sk3iyCuLgnccqVzgxek6gEHp2H0p3MQ3jniIHt+bRODXKju4kNQ+mp53lmP5+/9moQ==", + "dev": true, + "requires": { + "moment": ">=2.14.0" + } + }, + "@types/range-parser": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", + "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==", + "dev": true + }, + "@types/serve-static": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.3.tgz", + "integrity": "sha512-oprSwp094zOglVrXdlo/4bAHtKTAxX6VT8FOZlBKrmyLbNvE1zxZyJ6yikMVtHIvwP45+ZQGJn+FdXGKTozq0g==", + "dev": true, + "requires": { + "@types/express-serve-static-core": "*", + "@types/mime": "*" + } + }, + "@types/socket.io": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@types/socket.io/-/socket.io-2.1.2.tgz", + "integrity": "sha512-Ind+4qMNfQ62llyB4IMs1D8znMEBsMKohZBPqfBUIXqLQ9bdtWIbNTBWwtdcBWJKnokMZGcmWOOKslatni5vtA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/winston": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/winston/-/winston-2.4.4.tgz", + "integrity": "sha512-BVGCztsypW8EYwJ+Hq+QNYiT/MUyCif0ouBH+flrY66O5W+KIXAMML6E/0fJpm7VjIzgangahl5S03bJJQGrZw==", + "requires": { + "winston": "*" + } + }, + "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 + }, + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "after": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", + "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=" + }, + "ajv": { + "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", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", + "dev": true + }, + "ansi-colors": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", + "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==", + "dev": true + }, + "ansi-cyan": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-cyan/-/ansi-cyan-0.1.1.tgz", + "integrity": "sha1-U4rlKK+JgvKK4w2G8vF0VtJgmHM=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-gray": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", + "integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-red": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-red/-/ansi-red-0.1.1.tgz", + "integrity": "sha1-jGOPnRCAgAo1PJwoyKgcpHBdlGw=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "ansi-wrap": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", + "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=", + "dev": true + }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "append-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz", + "integrity": "sha1-2CIM9GYIFSXv6lBhTz3mUU36WPE=", + "dev": true, + "requires": { + "buffer-equal": "^1.0.0" + } + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "dev": true + }, + "archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "dev": true + }, + "are-we-there-yet": { + "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" + }, + "dependencies": { + "readable-stream": { + "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", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "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" + } + } + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "arr-filter": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/arr-filter/-/arr-filter-1.1.2.tgz", + "integrity": "sha1-Q/3d0JHo7xGqTEXZzcGOLf8XEe4=", + "dev": true, + "requires": { + "make-iterator": "^1.0.0" + } + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "arr-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/arr-map/-/arr-map-2.0.2.tgz", + "integrity": "sha1-Onc0X/wc814qkYJWAfnljy4kysQ=", + "dev": true, + "requires": { + "make-iterator": "^1.0.0" + } + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true + }, + "array-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", + "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=", + "dev": true + }, + "array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", + "dev": true + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "array-initial": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-initial/-/array-initial-1.1.0.tgz", + "integrity": "sha1-L6dLJnOTccOUe9enrcc74zSz15U=", + "dev": true, + "requires": { + "array-slice": "^1.0.0", + "is-number": "^4.0.0" + }, + "dependencies": { + "array-slice": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", + "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", + "dev": true + }, + "is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true + } + } + }, + "array-last": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/array-last/-/array-last-1.3.0.tgz", + "integrity": "sha512-eOCut5rXlI6aCOS7Z7kCplKRKyiFQ6dHFBem4PwlwKeNFk2/XxTrhRh5T9PyaEWGy/NHTZWbY+nsZlNFJu9rYg==", + "dev": true, + "requires": { + "is-number": "^4.0.0" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true + } + } + }, + "array-slice": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", + "integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=", + "dev": true + }, + "array-sort": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-sort/-/array-sort-1.0.0.tgz", + "integrity": "sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg==", + "dev": true, + "requires": { + "default-compare": "^1.0.0", + "get-value": "^2.0.6", + "kind-of": "^5.0.2" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "arraybuffer.slice": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", + "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==" + }, + "asn1": { + "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" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true + }, + "async": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "requires": { + "lodash": "^4.17.14" + } + }, + "async-array-reduce": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/async-array-reduce/-/async-array-reduce-0.2.1.tgz", + "integrity": "sha1-yL4BCitc0A3qlsgRFgNGk9/dgtE=", + "dev": true + }, + "async-done": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/async-done/-/async-done-1.3.2.tgz", + "integrity": "sha512-uYkTP8dw2og1tu1nmza1n1CMW0qb8gWWlwqMmLb7MhBVs4BXrFziT6HXUd+/RlRA/i4H9AkofYloUbs1fwMqlw==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.2", + "process-nextick-args": "^2.0.0", + "stream-exhaust": "^1.0.1" + } + }, + "async-each": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", + "dev": true + }, + "async-foreach": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz", + "integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=", + "dev": true + }, + "async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" + }, + "async-settle": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-1.0.0.tgz", + "integrity": "sha1-HQqRS7Aldb7IqPOnTlCA9yssDGs=", + "dev": true, + "requires": { + "async-done": "^1.2.2" + } + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true + }, + "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 + }, + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", + "dev": true + }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "strip-ansi": { + "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" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "bach": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", + "integrity": "sha1-Szzpa/JxNPeaG0FKUcFONMO9mIA=", + "dev": true, + "requires": { + "arr-filter": "^1.1.1", + "arr-flatten": "^1.0.1", + "arr-map": "^2.0.0", + "array-each": "^1.0.0", + "array-initial": "^1.0.0", + "array-last": "^1.1.1", + "async-done": "^1.2.2", + "async-settle": "^1.0.0", + "now-and-later": "^2.0.0" + } + }, + "backo2": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", + "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=" + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, + "base64-arraybuffer": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", + "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=" + }, + "base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" + }, + "base64id": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz", + "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=" + }, + "bcrypt-pbkdf": { + "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" + } + }, + "better-assert": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", + "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", + "requires": { + "callsite": "1.0.0" + } + }, + "binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "dev": true + }, + "blob": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", + "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==" + }, + "block-stream": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", + "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", + "dev": true, + "requires": { + "inherits": "~2.0.0" + } + }, + "body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "requires": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + }, + "dependencies": { + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + } + } + }, + "brace-expansion": { + "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" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + } + }, + "buffer": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.4.0.tgz", + "integrity": "sha512-Xpgy0IwHK2N01ncykXTy6FpCWuM+CJSHoPVBLyNqyrWxsedpLvwsYUhf0ME3WRFNUhos0dMamz9cOS/xRDtU5g==", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + }, + "buffer-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz", + "integrity": "sha1-WWFrSYME1Var1GaWayLu2j7KX74=", + "dev": true + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "buffer-writer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", + "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==" + }, + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "callsite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", + "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=" + }, + "camelcase": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", + "dev": true + }, + "camelcase-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "dev": true, + "requires": { + "camelcase": "^2.0.0", + "map-obj": "^1.0.0" + } + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "chokidar": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.6.tgz", + "integrity": "sha512-V2jUo67OKkc6ySiRpJrjlpJKl9kDuG+Xb8VgsGzb+aEouhgS1D0weyPU4lEzdAcsCAvrih2J2BqyXqHWvVLw5g==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + }, + "dependencies": { + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + } + } + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + }, + "dependencies": { + "strip-ansi": { + "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" + } + } + } + }, + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "dev": true + }, + "clone-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", + "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", + "dev": true + }, + "clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", + "dev": true + }, + "cloneable-readable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", + "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "process-nextick-args": "^2.0.0", + "readable-stream": "^2.3.5" + }, + "dependencies": { + "readable-stream": { + "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", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "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" + } + } + } + }, + "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 + }, + "collection-map": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-map/-/collection-map-1.0.0.tgz", + "integrity": "sha1-rqDwb40mx4DCt1SUOFVEsiVa8Yw=", + "dev": true, + "requires": { + "arr-map": "^2.0.2", + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + } + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz", + "integrity": "sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w==", + "requires": { + "color-convert": "^1.9.1", + "color-string": "^1.5.2" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "color-string": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz", + "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==", + "requires": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true + }, + "colornames": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/colornames/-/colornames-1.1.1.tgz", + "integrity": "sha1-+IiQMGhcfE/54qVZ9Qd+t2qBb5Y=" + }, + "colors": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.3.tgz", + "integrity": "sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg==" + }, + "colorspace": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.2.tgz", + "integrity": "sha512-vt+OoIP2d76xLhjwbBaucYlNSpPsrJWPlBTtwCpQKIu6/CSMutyzX93O/Do0qzpH3YoHEes8YEFXyZ797rEhzQ==", + "requires": { + "color": "3.0.x", + "text-hex": "1.0.x" + } + }, + "combined-stream": { + "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" + } + }, + "commander": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", + "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", + "dev": true + }, + "component-bind": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", + "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=" + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, + "component-inherit": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", + "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=" + }, + "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 + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + }, + "dependencies": { + "readable-stream": { + "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", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "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" + } + } + } + }, + "connect-pg-simple": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/connect-pg-simple/-/connect-pg-simple-6.0.0.tgz", + "integrity": "sha512-6pQnRSGFyswyHMdKQp5C+g78fjU/1/6eY05VeixXwMixw5KYhAcoOCXyf8TdPE1IzRLNDBMQi64vojXK/HMXVw==", + "requires": { + "pg": "^7.4.3" + } + }, + "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 + }, + "content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "requires": { + "safe-buffer": "5.1.2" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "convert-source-map": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", + "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } + } + }, + "cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" + }, + "cookie-parser": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.4.tgz", + "integrity": "sha512-lo13tqF3JEtFO7FyA49CqbhaFkskRJ0u/UAiINgrIXeRCY41c88/zxtrECl8AKH3B0hj9q10+h3Kt8I7KlW4tw==", + "requires": { + "cookie": "0.3.1", + "cookie-signature": "1.0.6" + }, + "dependencies": { + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" + } + } + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, + "copy-props": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-2.0.4.tgz", + "integrity": "sha512-7cjuUME+p+S3HZlbllgsn2CDwS+5eCCX16qBgNC4jgSTf49qR1VKy/Zhl400m0IQXl/bPGEVqncgUUMjrr4s8A==", + "dev": true, + "requires": { + "each-props": "^1.3.0", + "is-plain-object": "^2.0.1" + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "crc": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", + "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==", + "requires": { + "buffer": "^5.1.0" + } + }, + "cross-spawn": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz", + "integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "which": "^1.2.9" + } + }, + "currently-unhandled": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", + "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "dev": true, + "requires": { + "array-find-index": "^1.0.1" + } + }, + "d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "dev": true, + "requires": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "dashdash": { + "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" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true + }, + "default-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/default-compare/-/default-compare-1.0.0.tgz", + "integrity": "sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ==", + "dev": true, + "requires": { + "kind-of": "^5.0.2" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "default-resolution": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/default-resolution/-/default-resolution-2.0.0.tgz", + "integrity": "sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ=", + "dev": true + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, + "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 + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "dev": true + }, + "delete": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/delete/-/delete-1.1.0.tgz", + "integrity": "sha512-bdhJatRNYsJnOhSRx9Eej3ABBtxQQw/uz2RprpYL5R3jCC2XMYVBcQWwvQLl+iNDk4LCLEKhdIP3uZSqRWi/tw==", + "dev": true, + "requires": { + "async-each": "^1.0.1", + "extend-shallow": "^2.0.1", + "matched": "^1.0.2", + "rimraf": "^2.6.1" + } + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "detect-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", + "dev": true + }, + "diagnostics": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/diagnostics/-/diagnostics-1.1.1.tgz", + "integrity": "sha512-8wn1PmdunLJ9Tqbx+Fx/ZEuHfJf4NKSN2ZBj7SJC/OWRWha843+WsTjqMe1B5E3p28jqBlp+mJ2fPVxPyNgYKQ==", + "requires": { + "colorspace": "1.1.x", + "enabled": "1.0.x", + "kuler": "1.0.x" + } + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "dev": true, + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + }, + "dependencies": { + "readable-stream": { + "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", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "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" + } + } + } + }, + "each-props": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/each-props/-/each-props-1.3.2.tgz", + "integrity": "sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.1", + "object.defaults": "^1.1.0" + } + }, + "ecc-jsbn": { + "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" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "enabled": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-1.0.2.tgz", + "integrity": "sha1-ll9lE9LC0cX0ZStkouM5ZGf8L5M=", + "requires": { + "env-variable": "0.0.x" + } + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, + "end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "engine.io": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.3.2.tgz", + "integrity": "sha512-AsaA9KG7cWPXWHp5FvHdDWY3AMWeZ8x+2pUVLcn71qE5AtAzgGbxuclOytygskw8XGmiQafTmnI9Bix3uihu2w==", + "requires": { + "accepts": "~1.3.4", + "base64id": "1.0.0", + "cookie": "0.3.1", + "debug": "~3.1.0", + "engine.io-parser": "~2.1.0", + "ws": "~6.1.0" + }, + "dependencies": { + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + } + } + }, + "engine.io-client": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.3.2.tgz", + "integrity": "sha512-y0CPINnhMvPuwtqXfsGuWE8BB66+B6wTtCofQDRecMQPYX3MYUZXFNKDhdrSe3EVjgOu4V3rxdeqN/Tr91IgbQ==", + "requires": { + "component-emitter": "1.2.1", + "component-inherit": "0.0.3", + "debug": "~3.1.0", + "engine.io-parser": "~2.1.1", + "has-cors": "1.1.0", + "indexof": "0.0.1", + "parseqs": "0.0.5", + "parseuri": "0.0.5", + "ws": "~6.1.0", + "xmlhttprequest-ssl": "~1.5.4", + "yeast": "0.1.2" + }, + "dependencies": { + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + } + } + }, + "engine.io-parser": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.1.3.tgz", + "integrity": "sha512-6HXPre2O4Houl7c4g7Ic/XzPnHBvaEmN90vtRO9uLmwtRqQmTOw0QMevL1TOfL2Cpu1VzsaTmMotQgMdkzGkVA==", + "requires": { + "after": "0.8.2", + "arraybuffer.slice": "~0.0.7", + "base64-arraybuffer": "0.1.5", + "blob": "0.0.5", + "has-binary2": "~1.0.2" + } + }, + "env-variable": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/env-variable/-/env-variable-0.0.5.tgz", + "integrity": "sha512-zoB603vQReOFvTg5xMl9I1P2PnHsHQQKTEowsKKD7nseUfJq6UWzK+4YtlWUO1nhiQUxe6XMkk+JleSZD1NZFA==" + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es5-ext": { + "version": "0.10.50", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.50.tgz", + "integrity": "sha512-KMzZTPBkeQV/JcSQhI5/z6d9VWJ3EnQ194USTUwIYZ2ZbpN8+SGXQKt1h68EX44+qt+Fzr8DO17vnxrw7c3agw==", + "dev": true, + "requires": { + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.1", + "next-tick": "^1.0.0" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "es6-symbol": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", + "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "es6-weak-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", + "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.1" + } + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", + "dev": true, + "requires": { + "homedir-polyfill": "^1.0.1" + } + }, + "express": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "requires": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", + "content-type": "~1.0.4", + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.1.2", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + }, + "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==" + } + } + }, + "express-graphql": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/express-graphql/-/express-graphql-0.9.0.tgz", + "integrity": "sha512-wccd9Lb6oeJ8yHpUs/8LcnGjFUUQYmOG9A5BNLybRdCzGw0PeUrtBxsIR8bfiur6uSW4OvPkVDoYH06z6/N9+w==", + "requires": { + "accepts": "^1.3.7", + "content-type": "^1.0.4", + "http-errors": "^1.7.3", + "raw-body": "^2.4.1" + }, + "dependencies": { + "http-errors": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", + "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "raw-body": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.1.tgz", + "integrity": "sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA==", + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.3", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + } + } + }, + "express-session": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.16.2.tgz", + "integrity": "sha512-oy0sRsdw6n93E9wpCNWKRnSsxYnSDX9Dnr9mhZgqUEEorzcq5nshGYSZ4ZReHFhKQ80WI5iVUUSPW7u3GaKauw==", + "requires": { + "cookie": "0.3.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-headers": "~1.0.2", + "parseurl": "~1.3.3", + "safe-buffer": "5.1.2", + "uid-safe": "~2.1.5" + }, + "dependencies": { + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "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==" + } + } + }, + "express-socket.io-session": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/express-socket.io-session/-/express-socket.io-session-1.3.5.tgz", + "integrity": "sha512-ila9jN7Pu9OuNIDzkuW+ZChR2Y0TzyyFITT7xiOWCjuGCDUWioD382zqxI7HOaa8kIhfs3wTLOZMU9h6buuOFw==", + "requires": { + "cookie-parser": "~1.3.3", + "crc": "^3.3.0", + "debug": "~2.6.0" + }, + "dependencies": { + "cookie": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.1.3.tgz", + "integrity": "sha1-5zSlwUF/zkctWu+Cw4HKu2TRpDU=" + }, + "cookie-parser": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.3.5.tgz", + "integrity": "sha1-nXVVcPtdF4kHcSJ6AjFNm+fPg1Y=", + "requires": { + "cookie": "0.1.3", + "cookie-signature": "1.0.6" + } + } + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, + "fancy-log": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz", + "integrity": "sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==", + "dev": true, + "requires": { + "ansi-gray": "^0.1.1", + "color-support": "^1.1.3", + "parse-node-version": "^1.0.0", + "time-stamp": "^1.0.0" + } + }, + "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 + }, + "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 + }, + "fast-safe-stringify": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.6.tgz", + "integrity": "sha512-q8BZ89jjc+mz08rSxROs8VsrBBcn1SIw1kq9NjolL509tkABRk9io01RAjSaEv1Xb2uFLt8VtRiZbGp5H8iDtg==" + }, + "fecha": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-2.3.3.tgz", + "integrity": "sha512-lUGBnIamTAwk4znq5BcqsDaxSmZ9nDVJaij6NvRt/Tg4R69gERA+otPKbS86ROw9nxVMw2/mp1fnaiWqbs6Sdg==" + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + } + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + } + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "findup-sync": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", + "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", + "dev": true, + "requires": { + "detect-file": "^1.0.0", + "is-glob": "^4.0.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" + }, + "dependencies": { + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + } + } + }, + "fined": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz", + "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==", + "dev": true, + "requires": { + "expand-tilde": "^2.0.2", + "is-plain-object": "^2.0.3", + "object.defaults": "^1.1.0", + "object.pick": "^1.2.0", + "parse-filepath": "^1.0.1" + } + }, + "flagged-respawn": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", + "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==", + "dev": true + }, + "flush-write-stream": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", + "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "readable-stream": "^2.3.6" + }, + "dependencies": { + "readable-stream": { + "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", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "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" + } + } + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", + "dev": true, + "requires": { + "for-in": "^1.0.1" + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "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", + "mime-types": "^2.1.12" + } + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "requires": { + "map-cache": "^0.2.2" + } + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "fs-mkdirp-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz", + "integrity": "sha1-C3gV/DIBxqaeFNuYzgmMFpNSWes=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "through2": "^2.0.3" + }, + "dependencies": { + "readable-stream": { + "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", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "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" + } + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + } + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz", + "integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==", + "dev": true, + "optional": true, + "requires": { + "nan": "^2.12.1", + "node-pre-gyp": "^0.12.0" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "debug": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ms": "^2.1.1" + } + }, + "deep-extend": { + "version": "0.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "iconv-lite": { + "version": "0.4.24", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true, + "optional": true + }, + "minipass": { + "version": "2.3.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "needle": { + "version": "2.3.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "debug": "^4.1.0", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.12.0", + "bundled": true, + "dev": true, + "optional": true, + "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" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true + }, + "npm-packlist": { + "version": "1.4.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "rimraf": { + "version": "2.6.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "bundled": true, + "dev": true, + "optional": true + }, + "semver": { + "version": "5.7.0", + "bundled": true, + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "tar": { + "version": "4.4.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.4", + "minizlib": "^1.1.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "wide-align": { + "version": "1.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "yallist": { + "version": "3.0.3", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "fstream": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", + "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "dev": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + }, + "dependencies": { + "strip-ansi": { + "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" + } + } + } + }, + "gaze": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", + "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", + "dev": true, + "requires": { + "globule": "^1.0.0" + } + }, + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + }, + "get-stdin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", + "dev": true + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true + }, + "getpass": { + "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" + } + }, + "glob": { + "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", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + } + }, + "glob-stream": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-6.1.0.tgz", + "integrity": "sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ=", + "dev": true, + "requires": { + "extend": "^3.0.0", + "glob": "^7.1.1", + "glob-parent": "^3.1.0", + "is-negated-glob": "^1.0.0", + "ordered-read-streams": "^1.0.0", + "pumpify": "^1.3.5", + "readable-stream": "^2.1.5", + "remove-trailing-separator": "^1.0.1", + "to-absolute-glob": "^2.0.0", + "unique-stream": "^2.0.2" + }, + "dependencies": { + "readable-stream": { + "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", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "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" + } + } + } + }, + "glob-watcher": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.3.tgz", + "integrity": "sha512-8tWsULNEPHKQ2MR4zXuzSmqbdyV5PtwwCaWSGQ1WwHsJ07ilNeN1JB8ntxhckbnpSHaf9dXFUHzIWvm1I13dsg==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-done": "^1.2.0", + "chokidar": "^2.0.0", + "is-negated-glob": "^1.0.0", + "just-debounce": "^1.0.0", + "object.defaults": "^1.1.0" + } + }, + "global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "dev": true, + "requires": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + } + }, + "global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", + "dev": true, + "requires": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + } + }, + "globule": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.1.tgz", + "integrity": "sha512-g7QtgWF4uYSL5/dn71WxubOrS7JVGCnFPEnoeChJmBnyR9Mw8nGoEwOgJL/RC2Te0WhbsEUCejfH8SZNJ+adYQ==", + "dev": true, + "requires": { + "glob": "~7.1.1", + "lodash": "~4.17.10", + "minimatch": "~3.0.2" + } + }, + "glogg": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.2.tgz", + "integrity": "sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA==", + "dev": true, + "requires": { + "sparkles": "^1.0.0" + } + }, + "graceful-fs": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz", + "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==" + }, + "graphql": { + "version": "14.4.2", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-14.4.2.tgz", + "integrity": "sha512-6uQadiRgnpnSS56hdZUSvFrVcQ6OF9y6wkxJfKquFtHlnl7+KSuWwSJsdwiK1vybm1HgcdbpGkCpvhvsVQ0UZQ==", + "requires": { + "iterall": "^1.2.2" + } + }, + "graphql-import": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/graphql-import/-/graphql-import-0.7.1.tgz", + "integrity": "sha512-YpwpaPjRUVlw2SN3OPljpWbVRWAhMAyfSba5U47qGMOSsPLi2gYeJtngGpymjm9nk57RFWEpjqwh4+dpYuFAPw==", + "requires": { + "lodash": "^4.17.4", + "resolve-from": "^4.0.0" + } + }, + "gulp": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz", + "integrity": "sha512-dvEs27SCZt2ibF29xYgmnwwCYZxdxhQ/+LFWlbAW8y7jt68L/65402Lz3+CKy0Ov4rOs+NERmDq7YlZaDqUIfA==", + "dev": true, + "requires": { + "glob-watcher": "^5.0.3", + "gulp-cli": "^2.2.0", + "undertaker": "^1.2.1", + "vinyl-fs": "^3.0.0" + }, + "dependencies": { + "ansi-colors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", + "dev": true, + "requires": { + "ansi-wrap": "^0.1.0" + } + }, + "gulp-cli": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-2.2.0.tgz", + "integrity": "sha512-rGs3bVYHdyJpLqR0TUBnlcZ1O5O++Zs4bA0ajm+zr3WFCfiSLjGwoCBqFs18wzN+ZxahT9DkOK5nDf26iDsWjA==", + "dev": true, + "requires": { + "ansi-colors": "^1.0.1", + "archy": "^1.0.0", + "array-sort": "^1.0.0", + "color-support": "^1.1.3", + "concat-stream": "^1.6.0", + "copy-props": "^2.0.1", + "fancy-log": "^1.3.2", + "gulplog": "^1.0.0", + "interpret": "^1.1.0", + "isobject": "^3.0.1", + "liftoff": "^3.1.0", + "matchdep": "^2.0.0", + "mute-stdout": "^1.0.0", + "pretty-hrtime": "^1.0.0", + "replace-homedir": "^1.0.0", + "semver-greatest-satisfied-range": "^1.1.0", + "v8flags": "^3.0.1", + "yargs": "^7.1.0" + } + } + } + }, + "gulp-minify": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/gulp-minify/-/gulp-minify-3.1.0.tgz", + "integrity": "sha512-ixF41aYg+NQikI8hpoHdEclYcQkbGdXQu1CBdHaU7Epg8H6e8d2jWXw1+rBPgYwl/XpKgjHj7NI6gkhoSNSSAg==", + "dev": true, + "requires": { + "ansi-colors": "^1.0.1", + "minimatch": "^3.0.2", + "plugin-error": "^0.1.2", + "terser": "^3.7.6", + "through2": "^2.0.3", + "vinyl": "^2.1.0" + }, + "dependencies": { + "ansi-colors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", + "dev": true, + "requires": { + "ansi-wrap": "^0.1.0" + } + }, + "arr-diff": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz", + "integrity": "sha1-aHwydYFjWI/vfeezb6vklesaOZo=", + "dev": true, + "requires": { + "arr-flatten": "^1.0.1", + "array-slice": "^0.2.3" + } + }, + "arr-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-2.1.0.tgz", + "integrity": "sha1-IPnqtexw9cfSFbEHexw5Fh0pLH0=", + "dev": true + }, + "extend-shallow": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-1.1.4.tgz", + "integrity": "sha1-Gda/lN/AnXa6cR85uHLSH/TdkHE=", + "dev": true, + "requires": { + "kind-of": "^1.1.0" + } + }, + "plugin-error": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-0.1.2.tgz", + "integrity": "sha1-O5uzM1zPAPQl4HQ34ZJ2ln2kes4=", + "dev": true, + "requires": { + "ansi-cyan": "^0.1.1", + "ansi-red": "^0.1.1", + "arr-diff": "^1.0.1", + "arr-union": "^2.0.1", + "extend-shallow": "^1.1.2" + } + }, + "readable-stream": { + "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", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "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" + } + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + } + } + }, + "gulp-sass": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/gulp-sass/-/gulp-sass-4.0.2.tgz", + "integrity": "sha512-q8psj4+aDrblJMMtRxihNBdovfzGrXJp1l4JU0Sz4b/Mhsi2DPrKFYCGDwjIWRENs04ELVHxdOJQ7Vs98OFohg==", + "dev": true, + "requires": { + "chalk": "^2.3.0", + "lodash.clonedeep": "^4.3.2", + "node-sass": "^4.8.3", + "plugin-error": "^1.0.1", + "replace-ext": "^1.0.0", + "strip-ansi": "^4.0.0", + "through2": "^2.0.0", + "vinyl-sourcemaps-apply": "^0.2.0" + }, + "dependencies": { + "readable-stream": { + "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", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "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" + } + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + } + } + }, + "gulp-typescript": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/gulp-typescript/-/gulp-typescript-5.0.1.tgz", + "integrity": "sha512-YuMMlylyJtUSHG1/wuSVTrZp60k1dMEFKYOvDf7OvbAJWrDtxxD4oZon4ancdWwzjj30ztiidhe4VXJniF0pIQ==", + "dev": true, + "requires": { + "ansi-colors": "^3.0.5", + "plugin-error": "^1.0.1", + "source-map": "^0.7.3", + "through2": "^3.0.0", + "vinyl": "^2.1.0", + "vinyl-fs": "^3.0.3" + } + }, + "gulplog": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", + "integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=", + "dev": true, + "requires": { + "glogg": "^1.0.0" + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, + "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" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-binary2": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", + "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", + "requires": { + "isarray": "2.0.1" + }, + "dependencies": { + "isarray": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" + } + } + }, + "has-cors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", + "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=" + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-glob/-/has-glob-1.0.0.tgz", + "integrity": "sha1-mqqe7b/7G6OZCnsAEPtnjuAIEgc=", + "dev": true, + "requires": { + "is-glob": "^3.0.0" + } + }, + "has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "dev": true + }, + "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 + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "dev": true, + "requires": { + "parse-passwd": "^1.0.0" + } + }, + "hosted-git-info": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.4.tgz", + "integrity": "sha512-pzXIvANXEFrc5oFFXRMkbLPQ2rXRoDERwDLyrcUxGhaZhgP54BBSl9Oheh7Vv0T090cszWBxPjkQQ5Sq1PbBRQ==", + "dev": true + }, + "http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + } + } + }, + "http-signature": { + "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", + "sshpk": "^1.7.0" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, + "in-publish": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.0.tgz", + "integrity": "sha1-4g/146KvwmkDILbcVSaCqcf631E=", + "dev": true + }, + "indent-string": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "dev": true, + "requires": { + "repeating": "^2.0.0" + } + }, + "indexof": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", + "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=" + }, + "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" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "dev": true + }, + "interpret": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz", + "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==", + "dev": true + }, + "invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", + "dev": true + }, + "ipaddr.js": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz", + "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==" + }, + "is-absolute": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", + "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", + "dev": true, + "requires": { + "is-relative": "^1.0.0", + "is-windows": "^1.0.1" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-finite": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", + "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-fullwidth-code-point": { + "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" + } + }, + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + }, + "is-negated-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", + "integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=", + "dev": true + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-relative": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", + "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", + "dev": true, + "requires": { + "is-unc-path": "^1.0.0" + } + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "is-unc-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", + "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", + "dev": true, + "requires": { + "unc-path-regex": "^0.1.2" + } + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "is-valid-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz", + "integrity": "sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao=", + "dev": true + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "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 + }, + "iterall": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.2.2.tgz", + "integrity": "sha512-yynBb1g+RFUPY64fTrFv7nsjRrENBQJaX2UL+2Szc9REFrSNm1rpSXHGzhmAy7a9uv3vlvgBlXnf9RqmPH1/DA==" + }, + "js-base64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.1.tgz", + "integrity": "sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw==", + "dev": true + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "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 + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "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 + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "jsprim": { + "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", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "just-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.0.0.tgz", + "integrity": "sha1-h/zPrv/AtozRnVX2cilD+SnqNeo=", + "dev": true + }, + "kind-of": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", + "integrity": "sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ=", + "dev": true + }, + "kuler": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-1.0.1.tgz", + "integrity": "sha512-J9nVUucG1p/skKul6DU3PUZrhs0LPulNaeUOox0IyXDi8S4CztTHs1gQphhuZmzXG7VOQSf6NJfKuzteQLv9gQ==", + "requires": { + "colornames": "^1.1.1" + } + }, + "last-run": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/last-run/-/last-run-1.1.1.tgz", + "integrity": "sha1-RblpQsF7HHnHchmCWbqUO+v4yls=", + "dev": true, + "requires": { + "default-resolution": "^2.0.0", + "es6-weak-map": "^2.0.1" + } + }, + "lazystream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", + "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", + "dev": true, + "requires": { + "readable-stream": "^2.0.5" + }, + "dependencies": { + "readable-stream": { + "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", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "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" + } + } + } + }, + "lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "dev": true, + "requires": { + "invert-kv": "^1.0.0" + } + }, + "lead": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lead/-/lead-1.0.0.tgz", + "integrity": "sha1-bxT5mje+Op3XhPVJVpDlkDRm7kI=", + "dev": true, + "requires": { + "flush-write-stream": "^1.0.2" + } + }, + "liftoff": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-3.1.0.tgz", + "integrity": "sha512-DlIPlJUkCV0Ips2zf2pJP0unEoT1kwYhiiPUGF3s/jtxTCjziNLoiVVh+jqWOWeFi6mmwQ5fNxvAUyPad4Dfog==", + "dev": true, + "requires": { + "extend": "^3.0.0", + "findup-sync": "^3.0.0", + "fined": "^1.0.1", + "flagged-respawn": "^1.0.0", + "is-plain-object": "^2.0.4", + "object.map": "^1.0.0", + "rechoir": "^0.6.2", + "resolve": "^1.1.7" + } + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "dev": true + }, + "logform": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.1.2.tgz", + "integrity": "sha512-+lZh4OpERDBLqjiwDLpAWNQu6KMjnlXH2ByZwCuSqVPJletw0kTWJf5CgSNAUKn1KUkv3m2cUz/LK8zyEy7wzQ==", + "requires": { + "colors": "^1.2.1", + "fast-safe-stringify": "^2.0.4", + "fecha": "^2.3.3", + "ms": "^2.1.1", + "triple-beam": "^1.3.0" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "loud-rejection": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "dev": true, + "requires": { + "currently-unhandled": "^0.4.1", + "signal-exit": "^3.0.0" + } + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "make-iterator": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", + "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", + "dev": true, + "requires": { + "kind-of": "^6.0.2" + }, + "dependencies": { + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true + }, + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "requires": { + "object-visit": "^1.0.0" + } + }, + "matchdep": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz", + "integrity": "sha1-xvNINKDY28OzfCfui7yyfHd1WC4=", + "dev": true, + "requires": { + "findup-sync": "^2.0.0", + "micromatch": "^3.0.4", + "resolve": "^1.4.0", + "stack-trace": "0.0.10" + }, + "dependencies": { + "findup-sync": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", + "integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=", + "dev": true, + "requires": { + "detect-file": "^1.0.0", + "is-glob": "^3.1.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" + } + } + } + }, + "matched": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/matched/-/matched-1.0.2.tgz", + "integrity": "sha512-7ivM1jFZVTOOS77QsR+TtYHH0ecdLclMkqbf5qiJdX2RorqfhsL65QHySPZgDE0ZjHoh+mQUNHTanNXIlzXd0Q==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "async-array-reduce": "^0.2.1", + "glob": "^7.1.2", + "has-glob": "^1.0.0", + "is-valid-glob": "^1.0.0", + "resolve-dir": "^1.0.0" + } + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "meow": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "dev": true, + "requires": { + "camelcase-keys": "^2.0.0", + "decamelize": "^1.1.2", + "loud-rejection": "^1.0.0", + "map-obj": "^1.0.1", + "minimist": "^1.1.3", + "normalize-package-data": "^2.3.4", + "object-assign": "^4.0.1", + "read-pkg-up": "^1.0.1", + "redent": "^1.0.0", + "trim-newlines": "^1.0.0" + } + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "dependencies": { + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + }, + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" + }, + "mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "requires": { + "mime-db": "1.40.0" + } + }, + "minimatch": { + "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" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "mkdirp": { + "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" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + } + } + }, + "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 + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "mute-stdout": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-1.0.1.tgz", + "integrity": "sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg==", + "dev": true + }, + "nan": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", + "dev": true + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + }, + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + }, + "next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", + "dev": true + }, + "node-gyp": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz", + "integrity": "sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==", + "dev": true, + "requires": { + "fstream": "^1.0.0", + "glob": "^7.0.3", + "graceful-fs": "^4.1.2", + "mkdirp": "^0.5.0", + "nopt": "2 || 3", + "npmlog": "0 || 1 || 2 || 3 || 4", + "osenv": "0", + "request": "^2.87.0", + "rimraf": "2", + "semver": "~5.3.0", + "tar": "^2.0.0", + "which": "1" + }, + "dependencies": { + "semver": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", + "dev": true + } + } + }, + "node-sass": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.12.0.tgz", + "integrity": "sha512-A1Iv4oN+Iel6EPv77/HddXErL2a+gZ4uBeZUy+a8O35CFYTXhgA8MgLCWBtwpGZdCvTvQ9d+bQxX/QC36GDPpQ==", + "dev": true, + "requires": { + "async-foreach": "^0.1.3", + "chalk": "^1.1.1", + "cross-spawn": "^3.0.0", + "gaze": "^1.0.0", + "get-stdin": "^4.0.1", + "glob": "^7.0.3", + "in-publish": "^2.0.0", + "lodash": "^4.17.11", + "meow": "^3.7.0", + "mkdirp": "^0.5.1", + "nan": "^2.13.2", + "node-gyp": "^3.8.0", + "npmlog": "^4.0.0", + "request": "^2.88.0", + "sass-graph": "^2.2.4", + "stdout-stream": "^1.4.0", + "true-case-path": "^1.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "strip-ansi": { + "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" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "dev": true, + "requires": { + "abbrev": "1" + } + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + }, + "now-and-later": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-2.0.1.tgz", + "integrity": "sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ==", + "dev": true, + "requires": { + "once": "^1.3.2" + } + }, + "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", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "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 + }, + "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 + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "object-component": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", + "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=" + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "requires": { + "isobject": "^3.0.0" + } + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "object.defaults": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", + "integrity": "sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8=", + "dev": true, + "requires": { + "array-each": "^1.0.1", + "array-slice": "^1.0.0", + "for-own": "^1.0.0", + "isobject": "^3.0.0" + }, + "dependencies": { + "array-slice": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", + "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", + "dev": true + } + } + }, + "object.map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", + "integrity": "sha1-z4Plncj8wK1fQlDh94s7gb2AHTc=", + "dev": true, + "requires": { + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "object.reduce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.reduce/-/object.reduce-1.0.1.tgz", + "integrity": "sha1-b+NI8qx/oPlcpiEiZZkJaCW7A60=", + "dev": true, + "requires": { + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + } + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "one-time": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-0.0.4.tgz", + "integrity": "sha1-+M33eISCb+Tf+T46nMN7HkSAdC4=" + }, + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "dev": true, + "requires": { + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" + }, + "dependencies": { + "minimist": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", + "dev": true + } + } + }, + "ordered-read-streams": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", + "integrity": "sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4=", + "dev": true, + "requires": { + "readable-stream": "^2.0.1" + }, + "dependencies": { + "readable-stream": { + "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", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "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" + } + } + } + }, + "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 + }, + "os-locale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "dev": true, + "requires": { + "lcid": "^1.0.0" + } + }, + "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 + }, + "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" + } + }, + "packet-reader": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", + "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" + }, + "parse-filepath": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", + "integrity": "sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE=", + "dev": true, + "requires": { + "is-absolute": "^1.0.0", + "map-cache": "^0.2.0", + "path-root": "^0.1.1" + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "^1.2.0" + } + }, + "parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "dev": true + }, + "parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", + "dev": true + }, + "parseqs": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", + "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", + "requires": { + "better-assert": "~1.0.0" + } + }, + "parseuri": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", + "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", + "requires": { + "better-assert": "~1.0.0" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "^2.0.0" + } + }, + "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 + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "path-root": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", + "integrity": "sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc=", + "dev": true, + "requires": { + "path-root-regex": "^0.1.0" + } + }, + "path-root-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", + "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=", + "dev": true + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "pg": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/pg/-/pg-7.12.1.tgz", + "integrity": "sha512-l1UuyfEvoswYfcUe6k+JaxiN+5vkOgYcVSbSuw3FvdLqDbaoa2RJo1zfJKfPsSYPFVERd4GHvX3s2PjG1asSDA==", + "requires": { + "buffer-writer": "2.0.0", + "packet-reader": "1.0.0", + "pg-connection-string": "0.1.3", + "pg-pool": "^2.0.4", + "pg-types": "^2.1.0", + "pgpass": "1.x", + "semver": "4.3.2" + }, + "dependencies": { + "semver": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.2.tgz", + "integrity": "sha1-x6BxWKgL7dBSNVt3DYLWZA+AO+c=" + } + } + }, + "pg-connection-string": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-0.1.3.tgz", + "integrity": "sha1-2hhHsglA5C7hSSvq9l1J2RskXfc=" + }, + "pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==" + }, + "pg-pool": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-2.0.7.tgz", + "integrity": "sha512-UiJyO5B9zZpu32GSlP0tXy8J2NsJ9EFGFfz5v6PSbdz/1hBLX1rNiiy5+mAm5iJJYwfCv4A0EBcQLGWwjbpzZw==" + }, + "pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "requires": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + } + }, + "pgpass": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.2.tgz", + "integrity": "sha1-Knu0G2BltnkH6R2hsHwYR8h3swY=", + "requires": { + "split": "^1.0.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "^2.0.0" + } + }, + "plugin-error": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", + "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==", + "dev": true, + "requires": { + "ansi-colors": "^1.0.1", + "arr-diff": "^4.0.0", + "arr-union": "^3.1.0", + "extend-shallow": "^3.0.2" + }, + "dependencies": { + "ansi-colors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", + "dev": true, + "requires": { + "ansi-wrap": "^0.1.0" + } + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + }, + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true + }, + "postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==" + }, + "postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha1-AntTPAqokOJtFy1Hz5zOzFIazTU=" + }, + "postgres-date": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.4.tgz", + "integrity": "sha512-bESRvKVuTrjoBluEcpv2346+6kgB7UlnqWZsnbnCccTNq/pqfj1j6oBaN5+b/NrDXepYUT/HKadqv3iS9lJuVA==" + }, + "postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "requires": { + "xtend": "^4.0.0" + } + }, + "pretty-hrtime": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", + "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "proxy-addr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz", + "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==", + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.9.0" + } + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, + "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 + }, + "pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "dev": true, + "requires": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + } + }, + "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 + }, + "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 + }, + "random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dev": true, + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dev": true, + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + } + }, + "readable-stream": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", + "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + }, + "dependencies": { + "readable-stream": { + "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", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "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" + } + } + } + }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, + "requires": { + "resolve": "^1.1.6" + } + }, + "redent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", + "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "dev": true, + "requires": { + "indent-string": "^2.1.0", + "strip-indent": "^1.0.1" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + }, + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "remove-bom-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz", + "integrity": "sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5", + "is-utf8": "^0.2.1" + } + }, + "remove-bom-stream": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz", + "integrity": "sha1-BfGlk/FuQuH7kOv1nejlaVJflSM=", + "dev": true, + "requires": { + "remove-bom-buffer": "^3.0.0", + "safe-buffer": "^5.1.0", + "through2": "^2.0.3" + }, + "dependencies": { + "readable-stream": { + "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", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } + } + }, + "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" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } + } + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + } + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "repeating": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "dev": true, + "requires": { + "is-finite": "^1.0.0" + } + }, + "replace-ext": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", + "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", + "dev": true + }, + "replace-homedir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-homedir/-/replace-homedir-1.0.0.tgz", + "integrity": "sha1-6H9tUTuSjd6AgmDBK+f+xv9ueYw=", + "dev": true, + "requires": { + "homedir-polyfill": "^1.0.1", + "is-absolute": "^1.0.0", + "remove-trailing-separator": "^1.1.0" + } + }, + "request": { + "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", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, + "resolve": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", + "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", + "dev": true, + "requires": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" + }, + "resolve-options": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-1.1.0.tgz", + "integrity": "sha1-MrueOcBtZzONyTeMDW1gdFZq0TE=", + "dev": true, + "requires": { + "value-or-function": "^3.0.0" + } + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true + }, + "rimraf": { + "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" + } + }, + "safe-buffer": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "~0.1.10" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "sass-graph": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.4.tgz", + "integrity": "sha1-E/vWPNHK8JCLn9k0dq1DpR0eC0k=", + "dev": true, + "requires": { + "glob": "^7.0.0", + "lodash": "^4.0.0", + "scss-tokenizer": "^0.2.3", + "yargs": "^7.0.0" + } + }, + "scss-tokenizer": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz", + "integrity": "sha1-jrBtualyMzOCTT9VMGQRSYR85dE=", + "dev": true, + "requires": { + "js-base64": "^2.1.8", + "source-map": "^0.4.2" + }, + "dependencies": { + "source-map": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "dev": true, + "requires": { + "amdefine": ">=0.0.4" + } + } + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "semver-greatest-satisfied-range": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz", + "integrity": "sha1-E+jCZYq5aRywzXEJMkAoDTb3els=", + "dev": true, + "requires": { + "sver-compat": "^1.5.0" + } + }, + "send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "dependencies": { + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, + "serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + } + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", + "requires": { + "is-arrayish": "^0.3.1" + }, + "dependencies": { + "is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + } + } + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "requires": { + "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "socket.io": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.2.0.tgz", + "integrity": "sha512-wxXrIuZ8AILcn+f1B4ez4hJTPG24iNgxBBDaJfT6MsyOhVYiTXWexGoPkd87ktJG8kQEcL/NBvRi64+9k4Kc0w==", + "requires": { + "debug": "~4.1.0", + "engine.io": "~3.3.1", + "has-binary2": "~1.0.2", + "socket.io-adapter": "~1.1.0", + "socket.io-client": "2.2.0", + "socket.io-parser": "~3.3.0" + }, + "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==" + } + } + }, + "socket.io-adapter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.1.tgz", + "integrity": "sha1-KoBeihTWNyEk3ZFZrUUC+MsH8Gs=" + }, + "socket.io-client": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.2.0.tgz", + "integrity": "sha512-56ZrkTDbdTLmBIyfFYesgOxsjcLnwAKoN4CiPyTVkMQj3zTUh0QAx3GbvIvLpFEOvQWu92yyWICxB0u7wkVbYA==", + "requires": { + "backo2": "1.0.2", + "base64-arraybuffer": "0.1.5", + "component-bind": "1.0.0", + "component-emitter": "1.2.1", + "debug": "~3.1.0", + "engine.io-client": "~3.3.1", + "has-binary2": "~1.0.2", + "has-cors": "1.1.0", + "indexof": "0.0.1", + "object-component": "0.0.3", + "parseqs": "0.0.5", + "parseuri": "0.0.5", + "socket.io-parser": "~3.3.0", + "to-array": "0.1.4" + }, + "dependencies": { + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + } + } + }, + "socket.io-parser": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.0.tgz", + "integrity": "sha512-hczmV6bDgdaEbVqhAeVMM/jfUfzuEZHsQg6eOmLgJht6G3mPKMxYm75w2+qhAQZ+4X+1+ATZ+QFKeOZD5riHng==", + "requires": { + "component-emitter": "1.2.1", + "debug": "~3.1.0", + "isarray": "2.0.1" + }, + "dependencies": { + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "isarray": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" + } + } + }, + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true + }, + "source-map-resolve": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", + "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "dev": true, + "requires": { + "atob": "^2.1.1", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "dev": true + }, + "sparkles": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", + "integrity": "sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==", + "dev": true + }, + "spdx-correct": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", + "dev": true + }, + "split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "requires": { + "through": "2" + } + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.0" + }, + "dependencies": { + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + }, + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "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", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, + "stdout-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.1.tgz", + "integrity": "sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==", + "dev": true, + "requires": { + "readable-stream": "^2.0.1" + }, + "dependencies": { + "readable-stream": { + "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", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "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" + } + } + } + }, + "stream-exhaust": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", + "integrity": "sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==", + "dev": true + }, + "stream-shift": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", + "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", + "dev": true + }, + "string-width": { + "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", + "strip-ansi": "^3.0.0" + }, + "dependencies": { + "strip-ansi": { + "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" + } + } + } + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + } + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "requires": { + "is-utf8": "^0.2.0" + } + }, + "strip-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", + "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "dev": true, + "requires": { + "get-stdin": "^4.0.1" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "sver-compat": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/sver-compat/-/sver-compat-1.5.0.tgz", + "integrity": "sha1-PPh9/rTQe0o/FIJ7wYaz/QxkXNg=", + "dev": true, + "requires": { + "es6-iterator": "^2.0.1", + "es6-symbol": "^3.1.1" + } + }, + "tar": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz", + "integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==", + "dev": true, + "requires": { + "block-stream": "*", + "fstream": "^1.0.12", + "inherits": "2" + } + }, + "terser": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-3.17.0.tgz", + "integrity": "sha512-/FQzzPJmCpjAH9Xvk2paiWrFq+5M6aVOf+2KRbwhByISDX/EujxsK+BAvrhb6H+2rtrLCHK9N01wO014vrIwVQ==", + "dev": true, + "requires": { + "commander": "^2.19.0", + "source-map": "~0.6.1", + "source-map-support": "~0.5.10" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + }, + "through2": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz", + "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==", + "dev": true, + "requires": { + "readable-stream": "2 || 3" + } + }, + "through2-filter": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz", + "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==", + "dev": true, + "requires": { + "through2": "~2.0.0", + "xtend": "~4.0.0" + }, + "dependencies": { + "readable-stream": { + "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", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "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" + } + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + } + } + }, + "time-stamp": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", + "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=", + "dev": true + }, + "to-absolute-glob": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", + "integrity": "sha1-GGX0PZ50sIItufFFt4z/fQ98hJs=", + "dev": true, + "requires": { + "is-absolute": "^1.0.0", + "is-negated-glob": "^1.0.0" + } + }, + "to-array": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", + "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=" + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + }, + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "to-through": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-through/-/to-through-2.0.0.tgz", + "integrity": "sha1-/JKtq6ByZHvAtn1rA2ZKoZUJOvY=", + "dev": true, + "requires": { + "through2": "^2.0.3" + }, + "dependencies": { + "readable-stream": { + "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", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "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" + } + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + } + } + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + }, + "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" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + } + } + }, + "trim-newlines": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "dev": true + }, + "triple-beam": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", + "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" + }, + "true-case-path": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.3.tgz", + "integrity": "sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew==", + "dev": true, + "requires": { + "glob": "^7.1.2" + } + }, + "ts-lint": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/ts-lint/-/ts-lint-4.5.1.tgz", + "integrity": "sha1-nCK3t7hitnMk3RvSE6hFwDp/uMA=", + "dev": true, + "requires": { + "babel-code-frame": "^6.20.0", + "colors": "^1.1.2", + "diff": "^3.0.1", + "findup-sync": "~0.3.0", + "glob": "^7.1.1", + "optimist": "~0.6.0", + "resolve": "^1.1.7", + "tsutils": "^1.1.0" + }, + "dependencies": { + "findup-sync": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.3.0.tgz", + "integrity": "sha1-N5MKpdgWt3fANEXhlmzGeQpMCxY=", + "dev": true, + "requires": { + "glob": "~5.0.0" + }, + "dependencies": { + "glob": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", + "dev": true, + "requires": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + } + } + }, + "tsutils": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-1.9.1.tgz", + "integrity": "sha1-ufmrROVa+WgYMdXyjQrur1x1DLA=", + "dev": true + }, + "tunnel-agent": { + "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" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + }, + "type": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/type/-/type-1.0.3.tgz", + "integrity": "sha512-51IMtNfVcee8+9GJvj0spSuFcZHe9vSib6Xtgsny1Km9ugyz2mbS08I3rsUIRYgJohFRFU1160sgRodYz378Hg==", + "dev": true + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "requires": { + "random-bytes": "~1.0.0" + } + }, + "unc-path-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", + "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=", + "dev": true + }, + "undertaker": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-1.2.1.tgz", + "integrity": "sha512-71WxIzDkgYk9ZS+spIB8iZXchFhAdEo2YU8xYqBYJ39DIUIqziK78ftm26eecoIY49X0J2MLhG4hr18Yp6/CMA==", + "dev": true, + "requires": { + "arr-flatten": "^1.0.1", + "arr-map": "^2.0.0", + "bach": "^1.0.0", + "collection-map": "^1.0.0", + "es6-weak-map": "^2.0.1", + "last-run": "^1.1.0", + "object.defaults": "^1.0.0", + "object.reduce": "^1.0.0", + "undertaker-registry": "^1.0.0" + } + }, + "undertaker-registry": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/undertaker-registry/-/undertaker-registry-1.0.1.tgz", + "integrity": "sha1-XkvaMI5KiirlhPm5pDWaSZglzFA=", + "dev": true + }, + "union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + } + }, + "unique-stream": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", + "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==", + "dev": true, + "requires": { + "json-stable-stringify-without-jsonify": "^1.0.1", + "through2-filter": "^3.0.0" + } + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true + } + } + }, + "upath": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.2.tgz", + "integrity": "sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q==", + "dev": true + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, + "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 + }, + "v8flags": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.1.3.tgz", + "integrity": "sha512-amh9CCg3ZxkzQ48Mhcb8iX7xpAfYJgePHxWMQCBWECpOSqJUXgY26ncA61UTV0BkPqfhcy6mzwCIoP4ygxpW8w==", + "dev": true, + "requires": { + "homedir-polyfill": "^1.0.1" + } + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "value-or-function": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz", + "integrity": "sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM=", + "dev": true + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "vinyl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.0.tgz", + "integrity": "sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg==", + "dev": true, + "requires": { + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" + } + }, + "vinyl-fs": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-3.0.3.tgz", + "integrity": "sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==", + "dev": true, + "requires": { + "fs-mkdirp-stream": "^1.0.0", + "glob-stream": "^6.1.0", + "graceful-fs": "^4.0.0", + "is-valid-glob": "^1.0.0", + "lazystream": "^1.0.0", + "lead": "^1.0.0", + "object.assign": "^4.0.4", + "pumpify": "^1.3.5", + "readable-stream": "^2.3.3", + "remove-bom-buffer": "^3.0.0", + "remove-bom-stream": "^1.2.0", + "resolve-options": "^1.1.0", + "through2": "^2.0.0", + "to-through": "^2.0.0", + "value-or-function": "^3.0.0", + "vinyl": "^2.0.0", + "vinyl-sourcemap": "^1.1.0" + }, + "dependencies": { + "readable-stream": { + "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", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "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" + } + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + } + } + }, + "vinyl-sourcemap": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz", + "integrity": "sha1-kqgAWTo4cDqM2xHYswCtS+Y7PhY=", + "dev": true, + "requires": { + "append-buffer": "^1.0.2", + "convert-source-map": "^1.5.0", + "graceful-fs": "^4.1.6", + "normalize-path": "^2.1.1", + "now-and-later": "^2.0.0", + "remove-bom-buffer": "^3.0.0", + "vinyl": "^2.0.0" + } + }, + "vinyl-sourcemaps-apply": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz", + "integrity": "sha1-q2VJ1h0XLCsbh75cUI0jnI74dwU=", + "dev": true, + "requires": { + "source-map": "^0.5.1" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", + "dev": true + }, + "wide-align": { + "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" + } + }, + "winston": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.2.1.tgz", + "integrity": "sha512-zU6vgnS9dAWCEKg/QYigd6cgMVVNwyTzKs81XZtTFuRwJOcDdBg7AU0mXVyNbs7O5RH2zdv+BdNZUlx7mXPuOw==", + "requires": { + "async": "^2.6.1", + "diagnostics": "^1.1.1", + "is-stream": "^1.1.0", + "logform": "^2.1.1", + "one-time": "0.0.4", + "readable-stream": "^3.1.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.3.0" + } + }, + "winston-transport": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.3.0.tgz", + "integrity": "sha512-B2wPuwUi3vhzn/51Uukcao4dIduEiPOcOt9HJ3QeaXgkJ5Z7UwpBzxS4ZGNHtrxrUvTwemsQiSys0ihOf8Mp1A==", + "requires": { + "readable-stream": "^2.3.6", + "triple-beam": "^1.2.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "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==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "dev": true + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "strip-ansi": { + "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" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "ws": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.4.tgz", + "integrity": "sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA==", + "requires": { + "async-limiter": "~1.0.0" + } + }, + "xmlhttprequest-ssl": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", + "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=" + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + }, + "y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "dev": true + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + }, + "yargs": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz", + "integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=", + "dev": true, + "requires": { + "camelcase": "^3.0.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^1.4.0", + "read-pkg-up": "^1.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^1.0.2", + "which-module": "^1.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^5.0.0" + }, + "dependencies": { + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "dev": true + } + } + }, + "yargs-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz", + "integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=", + "dev": true, + "requires": { + "camelcase": "^3.0.0" + }, + "dependencies": { + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "dev": true + } + } + }, + "yeast": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", + "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..f130a49 --- /dev/null +++ b/package.json @@ -0,0 +1,54 @@ +{ + "name": "greenvironment-server", + "version": "0.1.0", + "description": "Server for greenvironment network", + "main": "./dist/index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "https://git.trivernis.net/Software_Engineering_I/greenvironment-server.git" + }, + "keywords": [ + "server", + "nodejs", + "express" + ], + "author": "SoftEngI", + "license": "ISC", + "devDependencies": { + "@types/connect-pg-simple": "^4.2.0", + "@types/cookie-parser": "^1.4.2", + "@types/express": "^4.17.1", + "@types/express-graphql": "^0.8.0", + "@types/express-session": "^1.15.14", + "@types/express-socket.io-session": "^1.3.2", + "@types/fs-extra": "^8.0.0", + "@types/graphql": "^14.2.3", + "@types/node": "^12.7.2", + "@types/pg": "^7.11.0", + "@types/socket.io": "^2.1.2", + "delete": "^1.1.0", + "gulp": "^4.0.2", + "gulp-minify": "^3.1.0", + "gulp-sass": "^4.0.2", + "gulp-typescript": "^5.0.1", + "ts-lint": "^4.5.1" + }, + "dependencies": { + "@types/winston": "^2.4.4", + "connect-pg-simple": "^6.0.0", + "cookie-parser": "^1.4.4", + "express": "^4.17.1", + "express-graphql": "^0.9.0", + "express-session": "^1.16.2", + "express-socket.io-session": "^1.3.5", + "fs-extra": "^8.1.0", + "graphql": "^14.4.2", + "graphql-import": "^0.7.1", + "pg": "^7.12.1", + "socket.io": "^2.2.0", + "winston": "^3.2.1" + } +} diff --git a/src/app.ts b/src/app.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..e69de29 From c931359ebd8d997b0c20e996ab4825638e68a42a Mon Sep 17 00:00:00 2001 From: Trivernis Date: Wed, 21 Aug 2019 15:06:05 +0200 Subject: [PATCH 02/40] Added files - tslint- changelog - dependencies - index.ts - folders - public files --- package-lock.json | 115 +++++++++++++++++++++++++ package.json | 5 +- src/app.ts | 17 ++++ src/index.ts | 5 ++ src/public/graphql/schema.graphql | 0 src/public/javascripts/main.js | 0 src/public/stylesheets/sass/style.sass | 0 src/routes/home.ts | 0 src/views/home.pug | 0 tslint.json | 28 ++++++ 10 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 src/public/graphql/schema.graphql create mode 100644 src/public/javascripts/main.js create mode 100644 src/public/stylesheets/sass/style.sass create mode 100644 src/routes/home.ts create mode 100644 src/views/home.pug create mode 100644 tslint.json diff --git a/package-lock.json b/package-lock.json index b623936..ae10b13 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,34 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@babel/code-frame": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", + "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/highlight": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz", + "integrity": "sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + } + } + }, "@types/body-parser": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.17.1.tgz", @@ -338,6 +366,15 @@ } } }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, "arr-diff": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", @@ -831,6 +868,12 @@ "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==" }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, "bytes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", @@ -1730,6 +1773,12 @@ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "dev": true }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, "esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -3762,6 +3811,16 @@ "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", "dev": true }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", @@ -5851,6 +5910,12 @@ } } }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, "sshpk": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", @@ -6357,6 +6422,50 @@ } } }, + "tsc": { + "version": "1.20150623.0", + "resolved": "https://registry.npmjs.org/tsc/-/tsc-1.20150623.0.tgz", + "integrity": "sha1-Trw8d04WkUjLx2inNCUz8ILHpuU=", + "dev": true + }, + "tslib": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", + "dev": true + }, + "tslint": { + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.19.0.tgz", + "integrity": "sha512-1LwwtBxfRJZnUvoS9c0uj8XQtAnyhWr9KlNvDIdB+oXyT+VpsOAaEhEgKi1HrZ8rq0ki/AAnbGSv4KM6/AfVZw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^3.2.0", + "glob": "^7.1.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.8.0", + "tsutils": "^2.29.0" + }, + "dependencies": { + "tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + } + } + }, "tsutils": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-1.9.1.tgz", @@ -6399,6 +6508,12 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", "dev": true }, + "typescript": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.3.tgz", + "integrity": "sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g==", + "dev": true + }, "uid-safe": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", diff --git a/package.json b/package.json index f130a49..2041d5a 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,10 @@ "gulp-minify": "^3.1.0", "gulp-sass": "^4.0.2", "gulp-typescript": "^5.0.1", - "ts-lint": "^4.5.1" + "ts-lint": "^4.5.1", + "tsc": "^1.20150623.0", + "tslint": "^5.19.0", + "typescript": "^3.5.3" }, "dependencies": { "@types/winston": "^2.4.4", diff --git a/src/app.ts b/src/app.ts index e69de29..bdb3fa3 100644 --- a/src/app.ts +++ b/src/app.ts @@ -0,0 +1,17 @@ +import * as express from "express"; +import * as http from "http"; +import * as socketIo from "socket.io"; + +class App { + public app: express.Application; + public io: socketIo.Server; + public server: http.Server; + + constructor() { + this.app = express(); + this.server = new http.Server(this.app); + this.io = socketIo(this.server); + } +} + +export default App; diff --git a/src/index.ts b/src/index.ts index e69de29..bd59c0e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -0,0 +1,5 @@ +import App from "./app"; + +const app = new App(); + +// TODO: init and start diff --git a/src/public/graphql/schema.graphql b/src/public/graphql/schema.graphql new file mode 100644 index 0000000..e69de29 diff --git a/src/public/javascripts/main.js b/src/public/javascripts/main.js new file mode 100644 index 0000000..e69de29 diff --git a/src/public/stylesheets/sass/style.sass b/src/public/stylesheets/sass/style.sass new file mode 100644 index 0000000..e69de29 diff --git a/src/routes/home.ts b/src/routes/home.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/views/home.pug b/src/views/home.pug new file mode 100644 index 0000000..e69de29 diff --git a/tslint.json b/tslint.json new file mode 100644 index 0000000..21b8db5 --- /dev/null +++ b/tslint.json @@ -0,0 +1,28 @@ +{ + "extends": "tslint:recommended", + "rulesDirectory": [], + "rules": { + "max-line-length": { + "options": [120] + }, + "new-parens": true, + "no-arg": true, + "no-bitwise": true, + "no-conditional-assignment": true, + "no-consecutive-blank-lines": false, + "cyclomatic-complexity": true, + "brace-style": "1tbs", + "semicolon": true, + "indent": [true, "spaces", 4], + "no-shadowed-variable": true, + "no-console": { + "severity": "warning", + "options": ["debug", "info", "log", "time", "timeEnd", "trace"] + } + }, + "jsRules": { + "max-line-length": { + "options": [120] + } + } +} \ No newline at end of file From 92146cc2a4e79959a41712721703d969f1c899d8 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Thu, 29 Aug 2019 11:02:27 +0200 Subject: [PATCH 03/40] added sql table creation and config --- src/config.yaml | 6 +++++ src/index.ts | 2 -- src/sql/create-tables.sql | 49 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 src/config.yaml create mode 100644 src/sql/create-tables.sql diff --git a/src/config.yaml b/src/config.yaml new file mode 100644 index 0000000..536f82f --- /dev/null +++ b/src/config.yaml @@ -0,0 +1,6 @@ +database: + url: localhost + port: 54332 + user: greenvironment + password: greendev + database: greenvironment diff --git a/src/index.ts b/src/index.ts index bd59c0e..b4265bb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,3 @@ import App from "./app"; const app = new App(); - -// TODO: init and start diff --git a/src/sql/create-tables.sql b/src/sql/create-tables.sql new file mode 100644 index 0000000..046541f --- /dev/null +++ b/src/sql/create-tables.sql @@ -0,0 +1,49 @@ +CREATE TABLE IF NOT EXISTS users ( + id SERIAL PRIMARY KEY, + name varchar(128) NOT NULL, + password varchar(1024) NOT NULL, + email varchar(128) UNIQUE NOT NULL, + greenpoints INTEGER DEFAULT 0 +); + +CREATE TABLE IF NOT EXISTS feed_items ( + id BIGSERIAL PRIMARY KEY, + upvotes INTEGER DEFAULT 0, + downvotes INTEGER DEFAULT 0, + created_at TIMESTAMP DEFAULT now(), + content text, + author SERIAL REFERENCES users (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS votes ( + user_id SERIAL REFERENCES users (id) ON DELETE CASCADE, + item_id BIGSERIAL REFERENCES feed_items (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS events ( + id BIGSERIAL PRIMARY KEY, + time TIMESTAMP, + owner SERIAL REFERENCES users (id) +); + +CREATE TABLE IF NOT EXISTS event_members ( + event BIGSERIAL REFERENCES events (id), + member SERIAL REFERENCES users (id) +); + +CREATE TABLE IF NOT EXISTS chats ( + id BIGSERIAL PRIMARY KEY +); + +CREATE TABLE IF NOT EXISTS chat_messages ( + chat BIGSERIAL REFERENCES chats (id) ON DELETE CASCADE, + author SERIAL REFERENCES users (id) ON DELETE SET NULL, + content VARCHAR(1024) NOT NULL, + created_at TIMESTAMP DEFAULT now(), + PRIMARY KEY (chat, author, created_at) +); + +CREATE TABLE IF NOT EXISTS chat_members ( + chat BIGSERIAL REFERENCES chats (id) ON DELETE CASCADE, + member SERIAL REFERENCES users (id) ON DELETE CASCADE +); From 6fb002e79be492d1e889565393a3fe52c4760d78 Mon Sep 17 00:00:00 2001 From: RandomUser27 Date: Thu, 29 Aug 2019 12:30:17 +0200 Subject: [PATCH 04/40] GraphQl --- src/public/graphql/schema.graphql | 121 ++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) diff --git a/src/public/graphql/schema.graphql b/src/public/graphql/schema.graphql index e69de29..35862f3 100644 --- a/src/public/graphql/schema.graphql +++ b/src/public/graphql/schema.graphql @@ -0,0 +1,121 @@ +type Query { + getUser(userId: Id): User + getPost(postId: Id): Post + getChat(chatId: Id): Chat + getRequest(requestId: Id): Request +} + +type Mutation { + #_____Post_____ + #"Upvote/downvote a Post" + vote(postId: id!, type: [VoteType!]!): Bool + #"Report the post" + report(postId: id!): Bool + #_____Request_____ + #"if u accept a request" + acceptRequest(requestId: id!): bool + #_____ChatRoom_____ + #"send a assage in a Chatroom" + sendMassage(chatId: Id!, massage: String!): bool +} + +type User { + #"ProfilPicture of the User" + profilPicture: String! + #"name from the User" + name: String! + #"unique name from the User" + handle: String! + #"Id from the User" + userId: ID! + #"Counted number of al Posts he posted" + numberOfPosts: int + #"show the posts of the User" + getAllPosts(first: int, offset: int): [Post] + #"Date when he/she Created the account" + JoinedDate: Date! + #"all chats for the mainsite(left)" + pinnedChats: [Chats] + #"all friends of the user" + friends: [User] + #"all request for groupChats/friends/events" + friends: [Request] +} + +type Post { + #"PicturePath" + picture: String + #"posted Text" + text: String + #"upvotes from Post" + upvotes: Int! + #"downvotes from Post" + downvotes: Int! + #"The AuthorID of the Post" + author: User! + #"Creationdate of the Post" + creationDate: Date! + #"Upvoteted/Downvoted the User already (NoVote(Null),Upvote,Downvote)" + alreadyVoted: [VoteType] + #"with tags u can find the post in a research" + tags: [String] +} + +type Search { + #"u can find posts with a name(text) or posted time" + findPost(first: int, offset: int, text: String!, postedDate: Date): [Post] + #"u can find a User with his Name or handle" + findUser(first: int, offset: int, name: String!, handle: string!): [User] +} + +type Request { + #"RequestId" + requestId: Id! + #"RequestType" + requestType: [RequestType!]! +} + +type ChatRoom { + #"Every User in the room" + members: [Users!] + #"Get the Chat" + getMessages(first: int, offset: int): [String] + #"chatId" + chatId: Id! +} + +enum VoteType { + UPDATE + DOWNVOTE +} + +enum RequestType { + FRIENDREQUEST + GROUPINVITE + EVENTINVITE +} + +#type Event { +#} + +#type Comment { +# #"CommentId" +# commentId: Id! +# #"text from the Comment" +# text: String +# #"PostId" +# postId: Id +# #"Upvotes of the comment" +# upvotes: int! +# #"Downvotes of the comment" +# downvotes: int! +# #"Upvoteted/Downvoted the User already (NoVote(Null),Upvote,Downvote)" +# alreadyVoted: [VoteType] +# #"Creationdate of the Post" +# creationDate: Date! +#Comment +# #"Upvote/downvote a Comment" +# vote(CommentId: id!, type: [VoteType!]!): Bool +# #"Report the Comment" +# report(CommentId: id!): Bool +#} From b7d10454ab23e286827ed63d2081c8972fc461f3 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Thu, 29 Aug 2019 13:21:11 +0200 Subject: [PATCH 05/40] Changed tables and graphql schema --- src/public/graphql/schema.graphql | 149 +++++++++++++----------------- src/sql/create-tables.sql | 16 +++- 2 files changed, 76 insertions(+), 89 deletions(-) diff --git a/src/public/graphql/schema.graphql b/src/public/graphql/schema.graphql index 35862f3..f5feb67 100644 --- a/src/public/graphql/schema.graphql +++ b/src/public/graphql/schema.graphql @@ -1,121 +1,100 @@ type Query { - getUser(userId: Id): User - getPost(postId: Id): Post - getChat(chatId: Id): Chat - getRequest(requestId: Id): Request + "returns the user object for a given user id" + getUser(userId: ID): User + "returns the post object for a post id" + getPost(postId: ID): Post + "returns the chat object for a chat id" + getChat(chatId: ID): ChatRoom + "returns the request object for a request id" + getRequest(requestId: ID): Request + "find a post by the posted date or content" + findPost(first: Int, offset: Int, text: String!, postedDate: String): [Post] + "find a user by user name or handle" + findUser(first: Int, offset: Int, name: String!, handle: String!): [User] } type Mutation { - #_____Post_____ - #"Upvote/downvote a Post" - vote(postId: id!, type: [VoteType!]!): Bool - #"Report the post" - report(postId: id!): Bool - #_____Request_____ - #"if u accept a request" - acceptRequest(requestId: id!): bool - #_____ChatRoom_____ - #"send a assage in a Chatroom" - sendMassage(chatId: Id!, massage: String!): bool + "Upvote/downvote a Post" + vote(postId: ID!, type: [VoteType!]!): Boolean + "Report the post" + report(postId: ID!): Boolean + "lets you accept a request for a given request id" + acceptRequest(requestId: ID!): Boolean + "send a message in a Chatroom" + sendMessage(chatId: ID!, content: String!): Boolean } +"represents a single user account" type User { - #"ProfilPicture of the User" - profilPicture: String! - #"name from the User" + "url for the Profile picture of the User" + profilePicture: String! + "name of the User" name: String! - #"unique name from the User" + "unique identifier name from the User" handle: String! - #"Id from the User" - userId: ID! - #"Counted number of al Posts he posted" - numberOfPosts: int - #"show the posts of the User" - getAllPosts(first: int, offset: int): [Post] - #"Date when he/she Created the account" - JoinedDate: Date! - #"all chats for the mainsite(left)" - pinnedChats: [Chats] - #"all friends of the user" + "Id of the User" + id: ID! + "the total number of posts the user posted" + numberOfPosts: Int + "returns a given number of posts of a user" + getAllPosts(first: Int=10, offset: Int): [Post] + "creation date of the user account" + joinedDate: String! + "returns chats the user pinned" + pinnedChats: [ChatRoom] + "returns all friends of the user" friends: [User] - #"all request for groupChats/friends/events" - friends: [Request] + "all request for groupChats/friends/events" + requests: [Request] } +"represents a single user post" type Post { - #"PicturePath" + "returns the path to the posts picture if it has one" picture: String - #"posted Text" + "returns the text of the post" text: String - #"upvotes from Post" + "upvotes of the Post" upvotes: Int! - #"downvotes from Post" + "downvotes of the Post" downvotes: Int! - #"The AuthorID of the Post" + "the user that is the author of the Post" author: User! - #"Creationdate of the Post" - creationDate: Date! - #"Upvoteted/Downvoted the User already (NoVote(Null),Upvote,Downvote)" - alreadyVoted: [VoteType] - #"with tags u can find the post in a research" + "date the post was created" + creationDate: String! + "returns the type of vote the user performed on the post" + alreadyVoted: VoteType + "returns the tags of the post" tags: [String] } -type Search { - #"u can find posts with a name(text) or posted time" - findPost(first: int, offset: int, text: String!, postedDate: Date): [Post] - #"u can find a User with his Name or handle" - findUser(first: int, offset: int, name: String!, handle: string!): [User] -} - +"represents a request of any type" type Request { - #"RequestId" - requestId: Id! - #"RequestType" - requestType: [RequestType!]! + "id of the request" + id: ID! + "type of the request" + requestType: RequestType! } +"represents a chatroom" type ChatRoom { - #"Every User in the room" - members: [Users!] - #"Get the Chat" - getMessages(first: int, offset: int): [String] - #"chatId" - chatId: Id! + "the members of the chatroom" + members: [User!] + "return a specfic range of messages posted in the chat" + getMessages(first: Int, offset: Int): [String] + "id of the chat" + id: ID! } +"represents the type of vote performed on a post" enum VoteType { - UPDATE + UPVOTE DOWNVOTE } +"represents the type of request that the user has received" enum RequestType { FRIENDREQUEST GROUPINVITE EVENTINVITE } - -#type Event { -#} - -#type Comment { -# #"CommentId" -# commentId: Id! -# #"text from the Comment" -# text: String -# #"PostId" -# postId: Id -# #"Upvotes of the comment" -# upvotes: int! -# #"Downvotes of the comment" -# downvotes: int! -# #"Upvoteted/Downvoted the User already (NoVote(Null),Upvote,Downvote)" -# alreadyVoted: [VoteType] -# #"Creationdate of the Post" -# creationDate: Date! -#Comment -# #"Upvote/downvote a Comment" -# vote(CommentId: id!, type: [VoteType!]!): Bool -# #"Report the Comment" -# report(CommentId: id!): Bool -#} diff --git a/src/sql/create-tables.sql b/src/sql/create-tables.sql index 046541f..0d02b2f 100644 --- a/src/sql/create-tables.sql +++ b/src/sql/create-tables.sql @@ -1,23 +1,26 @@ CREATE TABLE IF NOT EXISTS users ( id SERIAL PRIMARY KEY, name varchar(128) NOT NULL, + handle varchar(128) UNIQUE NOT NULL, password varchar(1024) NOT NULL, email varchar(128) UNIQUE NOT NULL, - greenpoints INTEGER DEFAULT 0 + greenpoints INTEGER DEFAULT 0, + joined_at TIMESTAMP DEFAULT now() ); -CREATE TABLE IF NOT EXISTS feed_items ( +CREATE TABLE IF NOT EXISTS posts ( id BIGSERIAL PRIMARY KEY, upvotes INTEGER DEFAULT 0, downvotes INTEGER DEFAULT 0, created_at TIMESTAMP DEFAULT now(), content text, - author SERIAL REFERENCES users (id) ON DELETE CASCADE + author SERIAL REFERENCES users (id) ON DELETE CASCADE, + type varchar(16) NOT NULL ); CREATE TABLE IF NOT EXISTS votes ( user_id SERIAL REFERENCES users (id) ON DELETE CASCADE, - item_id BIGSERIAL REFERENCES feed_items (id) ON DELETE CASCADE + item_id BIGSERIAL REFERENCES posts (id) ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS events ( @@ -47,3 +50,8 @@ CREATE TABLE IF NOT EXISTS chat_members ( chat BIGSERIAL REFERENCES chats (id) ON DELETE CASCADE, member SERIAL REFERENCES users (id) ON DELETE CASCADE ); + +CREATE TABLE IF NOT EXISTS user_friends ( + user_id SERIAL REFERENCES users (id) ON DELETE CASCADE, + friend_id SERIAL REFERENCES users (id) ON DELETE CASCADE +); From 0dcd590b809053cfe7b972fd870520b5e0ab95e5 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Mon, 2 Sep 2019 09:41:59 +0200 Subject: [PATCH 06/40] Database Connection - added QueryHelper - added DAO - added `default-config.yaml` - added auto-copying of the `default-config.yaml` to a `config.yaml` on startup if no config exists --- .gitignore | 3 +- CHANGELOG.md | 4 ++ gulpfile.js | 9 +++- package-lock.json | 13 ++--- package.json | 2 + src/config.yaml | 6 --- src/default-config.yaml | 11 ++++ src/index.ts | 15 +++++- src/lib/DAO.ts | 19 +++++++ src/lib/QueryHelper.ts | 116 ++++++++++++++++++++++++++++++++++++++++ src/lib/globals.ts | 25 +++++++++ tslint.json | 7 ++- 12 files changed, 212 insertions(+), 18 deletions(-) delete mode 100644 src/config.yaml create mode 100644 src/default-config.yaml create mode 100644 src/lib/DAO.ts create mode 100644 src/lib/QueryHelper.ts create mode 100644 src/lib/globals.ts diff --git a/.gitignore b/.gitignore index 331e819..f043384 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ node_modules/ npm-debug.log test/*.log dist -.idea \ No newline at end of file +.idea +config.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index c2e86b8..ca6704a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,3 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added + +- Connection to Postgres Database +- Graphql Schema +- default-config file and generation of config file on startup diff --git a/gulpfile.js b/gulpfile.js index e474b8c..c1abd43 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -34,9 +34,14 @@ function compileSass() { .pipe(dest('dist/public/stylesheets')); } -task('default', series(clearDist, compileTypescript, minifyJs, compileSass)); +function moveRemaining() { + return src(['src/**/*', '!src/**/*.ts', '!src/**/*.sass', '!src/**/*.js']) + .pipe(dest('dist')); +} + +task('default', series(clearDist, compileTypescript, minifyJs, compileSass, moveRemaining)); task('watch', () => { watch('src/public/stylesheets/sass/**/*.sass', compileSass); watch('**/*.js', minifyJs); watch('**/*.ts', compileTypescript); -}); \ No newline at end of file +}); diff --git a/package-lock.json b/package-lock.json index ae10b13..212f9d3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -137,6 +137,11 @@ "integrity": "sha512-UoCovaxbJIxagCvVfalfK7YaNhmxj3BQFRQ2RHQKLiu+9wNXhJnlbspsLHt/YQM99IaLUUFJNzCwzc6W0ypMeQ==", "dev": true }, + "@types/js-yaml": { + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-3.12.1.tgz", + "integrity": "sha512-SGGAhXLHDx+PK4YLNcNGa6goPf9XRWQNAUUbffkwVGGXIxmDKWyGGL4inzq2sPmExu431Ekb9aEMn9BkPqEYFA==" + }, "@types/mime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz", @@ -370,7 +375,6 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, "requires": { "sprintf-js": "~1.0.2" } @@ -1776,8 +1780,7 @@ "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" }, "esutils": { "version": "2.0.3", @@ -3815,7 +3818,6 @@ "version": "3.13.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "dev": true, "requires": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -5913,8 +5915,7 @@ "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, "sshpk": { "version": "1.16.1", diff --git a/package.json b/package.json index 2041d5a..ae3c9f0 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "typescript": "^3.5.3" }, "dependencies": { + "@types/js-yaml": "^3.12.1", "@types/winston": "^2.4.4", "connect-pg-simple": "^6.0.0", "cookie-parser": "^1.4.4", @@ -50,6 +51,7 @@ "fs-extra": "^8.1.0", "graphql": "^14.4.2", "graphql-import": "^0.7.1", + "js-yaml": "^3.13.1", "pg": "^7.12.1", "socket.io": "^2.2.0", "winston": "^3.2.1" diff --git a/src/config.yaml b/src/config.yaml deleted file mode 100644 index 536f82f..0000000 --- a/src/config.yaml +++ /dev/null @@ -1,6 +0,0 @@ -database: - url: localhost - port: 54332 - user: greenvironment - password: greendev - database: greenvironment diff --git a/src/default-config.yaml b/src/default-config.yaml new file mode 100644 index 0000000..6b7f17d --- /dev/null +++ b/src/default-config.yaml @@ -0,0 +1,11 @@ +# database connection info +database: + host: + port: + user: + password: + database: + +# http server configuration +server: + port: diff --git a/src/index.ts b/src/index.ts index b4265bb..7e34c95 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,16 @@ +import * as fsx from "fs-extra"; import App from "./app"; -const app = new App(); +const configPath = "config.yaml"; +const defaultConfig = __dirname + "/default-config.yaml"; + +/** + * async main function wrapper. + */ +(async () => { + if (!(await fsx.pathExists(configPath))) { + await fsx.copy(defaultConfig, configPath); + } + const app = new App(); +})(); + diff --git a/src/lib/DAO.ts b/src/lib/DAO.ts new file mode 100644 index 0000000..7d60b87 --- /dev/null +++ b/src/lib/DAO.ts @@ -0,0 +1,19 @@ +import {Pool} from "pg"; +import globals from "./globals"; +import {QueryHelper} from "./QueryHelper"; + +const config = globals.config; + +export class DAO { + private queryHelper: QueryHelper; + constructor() { + 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, + }); + this.queryHelper = new QueryHelper(dbClient); + } +} diff --git a/src/lib/QueryHelper.ts b/src/lib/QueryHelper.ts new file mode 100644 index 0000000..7f22fb5 --- /dev/null +++ b/src/lib/QueryHelper.ts @@ -0,0 +1,116 @@ +import {Pool, PoolClient, QueryConfig, QueryResult} from "pg"; +import globals from "./globals"; + +const logger = globals.logger; + +export class SqlTransaction { + 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 async release() { + this.client.release(); + } +} + +export class QueryHelper { + private pool: Pool; + + constructor(pgPool: Pool) { + this.pool = pgPool; + } + + /** + * executes the sql query with values and returns all results. + * @param query + */ + public async all(query: QueryConfig): 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: QueryConfig): 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: QueryConfig): Promise { + try { + return await this.pool.query(query); + } catch (err) { + logger.debug(`Error on query "${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/globals.ts b/src/lib/globals.ts new file mode 100644 index 0000000..ddd044a --- /dev/null +++ b/src/lib/globals.ts @@ -0,0 +1,25 @@ +import * as fsx from "fs-extra"; +import * as yaml from "js-yaml"; +import * as winston from "winston"; + +/** + * Defines global variables to be used. + */ +namespace globals { + export const config = yaml.safeLoad(fsx.readFileSync("config.yaml", "utf-8")); + export const logger = winston.createLogger({ + transports: [ + new winston.transports.Console({ + format: winston.format.combine( + winston.format.timestamp(), + winston.format.colorize(), + winston.format.printf(({ level, message, label, timestamp }) => { + return `${timestamp} [${label}] ${level}: ${message}`; + }), + ), + }), + ], + }); +} + +export default globals; diff --git a/tslint.json b/tslint.json index 21b8db5..78800e0 100644 --- a/tslint.json +++ b/tslint.json @@ -18,11 +18,14 @@ "no-console": { "severity": "warning", "options": ["debug", "info", "log", "time", "timeEnd", "trace"] - } + }, + "no-namespace": false, + "no-internal-module": false, + "max-classes-per-file": false }, "jsRules": { "max-line-length": { "options": [120] } } -} \ No newline at end of file +} From 20d719801030bc554469460a184764f700430511 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Sat, 7 Sep 2019 23:31:41 +0200 Subject: [PATCH 07/40] Database and App init - added database table creation - added init method to DAO - added init method to App - added start method to App to start listening on the configured port --- src/app.ts | 29 ++++++++++++++++++++++++++++- src/default-config.yaml | 2 +- src/index.ts | 2 ++ src/lib/DAO.ts | 10 +++++++++- src/lib/QueryHelper.ts | 14 +++++++++++++- src/lib/globals.ts | 2 +- 6 files changed, 54 insertions(+), 5 deletions(-) diff --git a/src/app.ts b/src/app.ts index bdb3fa3..3eee246 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,16 +1,43 @@ import * as express from "express"; import * as http from "http"; import * as socketIo from "socket.io"; - +import {DAO} from "./lib/DAO"; +import globals from "./lib/globals"; class App { public app: express.Application; public io: socketIo.Server; public server: http.Server; + public dao: DAO; constructor() { this.app = express(); this.server = new http.Server(this.app); this.io = socketIo(this.server); + this.dao = new DAO(); + } + + /** + * initializes everything that needs to be initialized asynchronous. + */ + public async init() { + await this.dao.init(); + this.app.all("/", (req, res) => { + res.send("WIP!"); + }); + } + + /** + * Starts the web server. + */ + public start() { + if (globals.config.server.port) { + globals.logger.info(`Starting server...`); + this.app.listen(globals.config.server.port); + globals.logger.info(`Server running on port ${globals.config.server.port}`); + } else { + globals.logger.error("No port specified in the config." + + "Please configure a port in the config.yaml."); + } } } diff --git a/src/default-config.yaml b/src/default-config.yaml index 6b7f17d..417ccda 100644 --- a/src/default-config.yaml +++ b/src/default-config.yaml @@ -8,4 +8,4 @@ database: # http server configuration server: - port: + port: 8080 diff --git a/src/index.ts b/src/index.ts index 7e34c95..4e814b5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,5 +12,7 @@ const defaultConfig = __dirname + "/default-config.yaml"; await fsx.copy(defaultConfig, configPath); } const app = new App(); + await app.init(); + app.start(); })(); diff --git a/src/lib/DAO.ts b/src/lib/DAO.ts index 7d60b87..6349fa9 100644 --- a/src/lib/DAO.ts +++ b/src/lib/DAO.ts @@ -3,6 +3,7 @@ import globals from "./globals"; import {QueryHelper} from "./QueryHelper"; const config = globals.config; +const tableCreationFile = __dirname + "/../sql/create-tables.sql"; export class DAO { private queryHelper: QueryHelper; @@ -14,6 +15,13 @@ export class DAO { port: config.database.port, user: config.database.user, }); - this.queryHelper = new QueryHelper(dbClient); + this.queryHelper = new QueryHelper(dbClient, tableCreationFile); + } + + /** + * Initializes everything that needs to be initialized asynchronous. + */ + public async init() { + await this.queryHelper.createTables(); } } diff --git a/src/lib/QueryHelper.ts b/src/lib/QueryHelper.ts index 7f22fb5..3431994 100644 --- a/src/lib/QueryHelper.ts +++ b/src/lib/QueryHelper.ts @@ -1,3 +1,4 @@ +import * as fsx from "fs-extra"; import {Pool, PoolClient, QueryConfig, QueryResult} from "pg"; import globals from "./globals"; @@ -47,10 +48,21 @@ export class SqlTransaction { export class QueryHelper { private pool: Pool; - constructor(pgPool: Pool) { + constructor(pgPool: Pool, private tableCreationFile?: string) { this.pool = pgPool; } + /** + * 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"); + await this.query({text: tableSql}); + } + } + /** * executes the sql query with values and returns all results. * @param query diff --git a/src/lib/globals.ts b/src/lib/globals.ts index ddd044a..a64aaf0 100644 --- a/src/lib/globals.ts +++ b/src/lib/globals.ts @@ -14,7 +14,7 @@ namespace globals { winston.format.timestamp(), winston.format.colorize(), winston.format.printf(({ level, message, label, timestamp }) => { - return `${timestamp} [${label}] ${level}: ${message}`; + return `${timestamp} ${level}: ${message}`; }), ), }), From 7dd351ddf317879b1bd5d28dada98635ec99e3d5 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Mon, 9 Sep 2019 10:05:35 +0200 Subject: [PATCH 08/40] Updated Readme --- README.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index fe1d0c9..79f8a5f 100644 --- a/README.md +++ b/README.md @@ -1 +1,11 @@ -greenvironment-server +# greenvironment-server + +Server of the greenvironment social network. + +## Install + +You need to install a nodejs runtime to run the greenvironment server. +Then you need to install all requirements. To do so, open a terminal in the +greenvironment project folder and execute "npm i". You can build the project by +executing "gulp" in the terminal. To run the server you need +to execute "node ./dist". From 387d9e01875239c944c3adde7a509dc8d745d569 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Fri, 13 Sep 2019 16:01:00 +0200 Subject: [PATCH 09/40] Added DTOs and included template - added routers index to manage all routes - added home view and pug view engine - added dtos for user and post --- CHANGELOG.md | 1 + package-lock.json | 437 ++++++++++++++++++++++++++++++++++++++++++-- package.json | 1 + src/app.ts | 17 +- src/lib/DAO.ts | 27 --- src/lib/DTO.ts | 248 +++++++++++++++++++++++++ src/routes/home.ts | 10 + src/routes/index.ts | 22 +++ src/views/home.pug | 5 + 9 files changed, 719 insertions(+), 49 deletions(-) delete mode 100644 src/lib/DAO.ts create mode 100644 src/lib/DTO.ts create mode 100644 src/routes/index.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index ca6704a..ddc745c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,3 +11,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Connection to Postgres Database - Graphql Schema - default-config file and generation of config file on startup +- DTOs diff --git a/package-lock.json b/package-lock.json index 212f9d3..bb5ff31 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,6 +32,19 @@ } } }, + "@types/babel-types": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/@types/babel-types/-/babel-types-7.0.7.tgz", + "integrity": "sha512-dBtBbrc+qTHy1WdfHYjBwRln4+LWqASWakLHsWHR2NWHIFkv4W3O070IGoGLEBrJBvct3r0L1BUPuvURi7kYUQ==" + }, + "@types/babylon": { + "version": "6.16.5", + "resolved": "https://registry.npmjs.org/@types/babylon/-/babylon-6.16.5.tgz", + "integrity": "sha512-xH2e58elpj1X4ynnKp9qSnWlsRTIs6n3tgLGNfwAGHwePw0mulHQllV34n0T25uYSu1k0hRKkWXF890B1yS47w==", + "requires": { + "@types/babel-types": "*" + } + }, "@types/body-parser": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.17.1.tgz", @@ -221,6 +234,26 @@ "negotiator": "0.6.2" } }, + "acorn": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=" + }, + "acorn-globals": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-3.1.0.tgz", + "integrity": "sha1-/YJw9x+7SZawBPqIDuXUZXOnMb8=", + "requires": { + "acorn": "^4.0.4" + }, + "dependencies": { + "acorn": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", + "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=" + } + } + }, "after": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", @@ -238,6 +271,26 @@ "uri-js": "^4.2.2" } }, + "align-text": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", + "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", + "requires": { + "kind-of": "^3.0.2", + "longest": "^1.0.1", + "repeat-string": "^1.5.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, "amdefine": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", @@ -509,6 +562,11 @@ "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==" }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + }, "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", @@ -653,6 +711,31 @@ } } }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "requires": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + } + }, + "babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==" + }, "bach": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", @@ -927,6 +1010,15 @@ "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", "dev": true }, + "center-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", + "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", + "requires": { + "align-text": "^0.1.3", + "lazy-cache": "^1.0.3" + } + }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -938,6 +1030,14 @@ "supports-color": "^5.3.0" } }, + "character-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz", + "integrity": "sha1-x84o821LzZdE5f/CxfzeHHMmH8A=", + "requires": { + "is-regex": "^1.0.3" + } + }, "chokidar": { "version": "2.1.6", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.6.tgz", @@ -998,6 +1098,21 @@ } } }, + "clean-css": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz", + "integrity": "sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g==", + "requires": { + "source-map": "~0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, "cliui": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", @@ -1259,6 +1374,17 @@ "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", "dev": true }, + "constantinople": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-3.1.2.tgz", + "integrity": "sha512-yePcBqEFhLOqSBtwYOGGS1exHo/s1xjekXiinh4itpNQGCu4KA1euPh1fg07N2wMITZXQkBz75Ntdt1ctGZouw==", + "requires": { + "@types/babel-types": "^7.0.0", + "@types/babylon": "^6.16.2", + "babel-types": "^6.26.0", + "babylon": "^6.18.0" + } + }, "content-disposition": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", @@ -1338,6 +1464,11 @@ "is-plain-object": "^2.0.1" } }, + "core-js": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.9.tgz", + "integrity": "sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==" + }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -1400,8 +1531,7 @@ "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, "decode-uri-component": { "version": "0.2.0", @@ -1544,6 +1674,11 @@ "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", "dev": true }, + "doctypes": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz", + "integrity": "sha1-6oCxBqh1OHdOijpKWv4pPeSJ4Kk=" + }, "duplexify": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", @@ -1785,8 +1920,7 @@ "esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" }, "etag": { "version": "1.8.1", @@ -2890,8 +3024,7 @@ "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, "gauge": { "version": "2.7.4", @@ -3359,6 +3492,14 @@ "har-schema": "^2.0.0" } }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, "has-ansi": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", @@ -3611,8 +3752,7 @@ "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" }, "is-data-descriptor": { "version": "0.1.4", @@ -3653,6 +3793,22 @@ } } }, + "is-expression": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-3.0.0.tgz", + "integrity": "sha1-Oayqa+f9HzRx3ELHQW5hwkMXrJ8=", + "requires": { + "acorn": "~4.0.2", + "object-assign": "^4.0.1" + }, + "dependencies": { + "acorn": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", + "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=" + } + } + }, "is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", @@ -3727,6 +3883,19 @@ "isobject": "^3.0.1" } }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" + }, + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "requires": { + "has": "^1.0.1" + } + }, "is-relative": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", @@ -3808,6 +3977,11 @@ "integrity": "sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw==", "dev": true }, + "js-stringify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz", + "integrity": "sha1-Fzb939lyTyijaCrcYjCufk6Weds=" + }, "js-tokens": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", @@ -3873,6 +4047,15 @@ "verror": "1.10.0" } }, + "jstransformer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz", + "integrity": "sha1-7Yvwkh4vPx7U1cGkT2hwntJHIsM=", + "requires": { + "is-promise": "^2.0.0", + "promise": "^7.0.1" + } + }, "just-debounce": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.0.0.tgz", @@ -3903,6 +4086,11 @@ "es6-weak-map": "^2.0.1" } }, + "lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=" + }, "lazystream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", @@ -4021,6 +4209,11 @@ } } }, + "longest": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=" + }, "loud-rejection": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", @@ -4506,8 +4699,7 @@ "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, "object-component": { "version": "0.0.3", @@ -4826,8 +5018,7 @@ "path-parse": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", - "dev": true + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" }, "path-root": { "version": "0.1.1", @@ -5025,6 +5216,14 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, + "promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "requires": { + "asap": "~2.0.3" + } + }, "proxy-addr": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz", @@ -5046,6 +5245,120 @@ "integrity": "sha512-avHdspHO+9rQTLbv1RO+MPYeP/SzsCoxofjVnHanETfQhTJrmB0HlDoW+EiN/R+C0BZ+gERab9NY0lPN2TxNag==", "dev": true }, + "pug": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pug/-/pug-2.0.4.tgz", + "integrity": "sha512-XhoaDlvi6NIzL49nu094R2NA6P37ijtgMDuWE+ofekDChvfKnzFal60bhSdiy8y2PBO6fmz3oMEIcfpBVRUdvw==", + "requires": { + "pug-code-gen": "^2.0.2", + "pug-filters": "^3.1.1", + "pug-lexer": "^4.1.0", + "pug-linker": "^3.0.6", + "pug-load": "^2.0.12", + "pug-parser": "^5.0.1", + "pug-runtime": "^2.0.5", + "pug-strip-comments": "^1.0.4" + } + }, + "pug-attrs": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pug-attrs/-/pug-attrs-2.0.4.tgz", + "integrity": "sha512-TaZ4Z2TWUPDJcV3wjU3RtUXMrd3kM4Wzjbe3EWnSsZPsJ3LDI0F3yCnf2/W7PPFF+edUFQ0HgDL1IoxSz5K8EQ==", + "requires": { + "constantinople": "^3.0.1", + "js-stringify": "^1.0.1", + "pug-runtime": "^2.0.5" + } + }, + "pug-code-gen": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-2.0.2.tgz", + "integrity": "sha512-kROFWv/AHx/9CRgoGJeRSm+4mLWchbgpRzTEn8XCiwwOy6Vh0gAClS8Vh5TEJ9DBjaP8wCjS3J6HKsEsYdvaCw==", + "requires": { + "constantinople": "^3.1.2", + "doctypes": "^1.1.0", + "js-stringify": "^1.0.1", + "pug-attrs": "^2.0.4", + "pug-error": "^1.3.3", + "pug-runtime": "^2.0.5", + "void-elements": "^2.0.1", + "with": "^5.0.0" + } + }, + "pug-error": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-1.3.3.tgz", + "integrity": "sha512-qE3YhESP2mRAWMFJgKdtT5D7ckThRScXRwkfo+Erqga7dyJdY3ZquspprMCj/9sJ2ijm5hXFWQE/A3l4poMWiQ==" + }, + "pug-filters": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/pug-filters/-/pug-filters-3.1.1.tgz", + "integrity": "sha512-lFfjNyGEyVWC4BwX0WyvkoWLapI5xHSM3xZJFUhx4JM4XyyRdO8Aucc6pCygnqV2uSgJFaJWW3Ft1wCWSoQkQg==", + "requires": { + "clean-css": "^4.1.11", + "constantinople": "^3.0.1", + "jstransformer": "1.0.0", + "pug-error": "^1.3.3", + "pug-walk": "^1.1.8", + "resolve": "^1.1.6", + "uglify-js": "^2.6.1" + } + }, + "pug-lexer": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/pug-lexer/-/pug-lexer-4.1.0.tgz", + "integrity": "sha512-i55yzEBtjm0mlplW4LoANq7k3S8gDdfC6+LThGEvsK4FuobcKfDAwt6V4jKPH9RtiE3a2Akfg5UpafZ1OksaPA==", + "requires": { + "character-parser": "^2.1.1", + "is-expression": "^3.0.0", + "pug-error": "^1.3.3" + } + }, + "pug-linker": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/pug-linker/-/pug-linker-3.0.6.tgz", + "integrity": "sha512-bagfuHttfQOpANGy1Y6NJ+0mNb7dD2MswFG2ZKj22s8g0wVsojpRlqveEQHmgXXcfROB2RT6oqbPYr9EN2ZWzg==", + "requires": { + "pug-error": "^1.3.3", + "pug-walk": "^1.1.8" + } + }, + "pug-load": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/pug-load/-/pug-load-2.0.12.tgz", + "integrity": "sha512-UqpgGpyyXRYgJs/X60sE6SIf8UBsmcHYKNaOccyVLEuT6OPBIMo6xMPhoJnqtB3Q3BbO4Z3Bjz5qDsUWh4rXsg==", + "requires": { + "object-assign": "^4.1.0", + "pug-walk": "^1.1.8" + } + }, + "pug-parser": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pug-parser/-/pug-parser-5.0.1.tgz", + "integrity": "sha512-nGHqK+w07p5/PsPIyzkTQfzlYfuqoiGjaoqHv1LjOv2ZLXmGX1O+4Vcvps+P4LhxZ3drYSljjq4b+Naid126wA==", + "requires": { + "pug-error": "^1.3.3", + "token-stream": "0.0.1" + } + }, + "pug-runtime": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/pug-runtime/-/pug-runtime-2.0.5.tgz", + "integrity": "sha512-P+rXKn9un4fQY77wtpcuFyvFaBww7/91f3jHa154qU26qFAnOe6SW1CbIDcxiG5lLK9HazYrMCCuDvNgDQNptw==" + }, + "pug-strip-comments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/pug-strip-comments/-/pug-strip-comments-1.0.4.tgz", + "integrity": "sha512-i5j/9CS4yFhSxHp5iKPHwigaig/VV9g+FgReLJWWHEHbvKsbqL0oP/K5ubuLco6Wu3Kan5p7u7qk8A4oLLh6vw==", + "requires": { + "pug-error": "^1.3.3" + } + }, + "pug-walk": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-1.1.8.tgz", + "integrity": "sha512-GMu3M5nUL3fju4/egXwZO0XLi6fW/K3T3VTgFQ14GxNi8btlxgT5qZL//JwZFm/2Fa64J/PNS8AZeys3wiMkVA==" + }, "pump": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", @@ -5193,6 +5506,11 @@ "strip-indent": "^1.0.1" } }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + }, "regex-not": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", @@ -5312,8 +5630,7 @@ "repeat-string": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" }, "repeating": { "version": "2.0.1", @@ -5385,7 +5702,6 @@ "version": "1.12.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", - "dev": true, "requires": { "path-parse": "^1.0.6" } @@ -5426,6 +5742,14 @@ "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", "dev": true }, + "right-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", + "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", + "requires": { + "align-text": "^0.1.1" + } + }, "rimraf": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", @@ -6224,6 +6548,11 @@ "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=" }, + "to-fast-properties": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", + "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=" + }, "to-object-path": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", @@ -6343,6 +6672,11 @@ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" }, + "token-stream": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-0.0.1.tgz", + "integrity": "sha1-zu78cXp2xDFvEm0LnbqlXX598Bo=" + }, "tough-cookie": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", @@ -6515,6 +6849,60 @@ "integrity": "sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g==", "dev": true }, + "uglify-js": { + "version": "2.8.29", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", + "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "requires": { + "source-map": "~0.5.1", + "uglify-to-browserify": "~1.0.0", + "yargs": "~3.10.0" + }, + "dependencies": { + "camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=" + }, + "cliui": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", + "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "requires": { + "center-align": "^0.1.1", + "right-align": "^0.1.1", + "wordwrap": "0.0.2" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + }, + "wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=" + }, + "yargs": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "requires": { + "camelcase": "^1.0.2", + "cliui": "^2.1.0", + "decamelize": "^1.0.0", + "window-size": "0.1.0" + } + } + } + }, + "uglify-to-browserify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", + "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", + "optional": true + }, "uid-safe": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", @@ -6821,6 +7209,11 @@ } } }, + "void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", + "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=" + }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", @@ -6845,6 +7238,11 @@ "string-width": "^1.0.2 || 2" } }, + "window-size": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", + "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=" + }, "winston": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/winston/-/winston-3.2.1.tgz", @@ -6899,6 +7297,15 @@ } } }, + "with": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/with/-/with-5.1.1.tgz", + "integrity": "sha1-+k2qktrzLE6pTtRTyB8EaGtXXf4=", + "requires": { + "acorn": "^3.1.0", + "acorn-globals": "^3.0.0" + } + }, "wordwrap": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", diff --git a/package.json b/package.json index ae3c9f0..8043e04 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "graphql-import": "^0.7.1", "js-yaml": "^3.13.1", "pg": "^7.12.1", + "pug": "^2.0.4", "socket.io": "^2.2.0", "winston": "^3.2.1" } diff --git a/src/app.ts b/src/app.ts index 3eee246..c5086c1 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,29 +1,32 @@ import * as express from "express"; import * as http from "http"; +import * as path from "path"; import * as socketIo from "socket.io"; -import {DAO} from "./lib/DAO"; +import {DTO} from "./lib/DTO"; import globals from "./lib/globals"; +import routes from "./routes"; + class App { public app: express.Application; public io: socketIo.Server; public server: http.Server; - public dao: DAO; + public dto: DTO; constructor() { this.app = express(); this.server = new http.Server(this.app); this.io = socketIo(this.server); - this.dao = new DAO(); + this.dto = new DTO(); } /** * initializes everything that needs to be initialized asynchronous. */ public async init() { - await this.dao.init(); - this.app.all("/", (req, res) => { - res.send("WIP!"); - }); + await this.dto.init(); + this.app.set("views", path.join(__dirname, "views")); + this.app.set("view engine", "pug"); + this.app.use(routes.router); } /** diff --git a/src/lib/DAO.ts b/src/lib/DAO.ts deleted file mode 100644 index 6349fa9..0000000 --- a/src/lib/DAO.ts +++ /dev/null @@ -1,27 +0,0 @@ -import {Pool} from "pg"; -import globals from "./globals"; -import {QueryHelper} from "./QueryHelper"; - -const config = globals.config; -const tableCreationFile = __dirname + "/../sql/create-tables.sql"; - -export class DAO { - private queryHelper: QueryHelper; - constructor() { - 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, - }); - this.queryHelper = new QueryHelper(dbClient, tableCreationFile); - } - - /** - * Initializes everything that needs to be initialized asynchronous. - */ - public async init() { - await this.queryHelper.createTables(); - } -} diff --git a/src/lib/DTO.ts b/src/lib/DTO.ts new file mode 100644 index 0000000..2d93484 --- /dev/null +++ b/src/lib/DTO.ts @@ -0,0 +1,248 @@ +import {Runtime} from "inspector"; +import {Pool} from "pg"; +import globals from "./globals"; +import {QueryHelper} from "./QueryHelper"; + +const config = globals.config; +const tableCreationFile = __dirname + "/../sql/create-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, +}); +const queryHelper = new QueryHelper(dbClient, tableCreationFile); + +export class DTO { + private queryHelper: QueryHelper; + + constructor() { + this.queryHelper = queryHelper; + } + + /** + * Initializes everything that needs to be initialized asynchronous. + */ + public async init() { + await this.queryHelper.createTables(); + } + + /** + * Returns the user by id + * @param userId + */ + public getUser(userId: number) { + return new User(userId); + } + + /** + * Returns the user by handle. + * @param userHandle + */ + public async getUserByHandle(userHandle: string) { + const result = await this.queryHelper.first({ + text: "SELECT * FROM users WHERE users.handle = $1", + values: [userHandle], + }); + return new User(result.id, result); + } +} + +export class User { + public readonly id: number; + private $name: string; + private $handle: string; + private $email: string; + private $greenpoints: number; + private $joinedAt: string; + private dataLoaded: boolean; + + /** + * Constructor of the user + * @param id + * @param row + */ + constructor(id: number, private row?: any) { + this.id = id; + } + + /** + * The name of the user + */ + public async name(): Promise { + if (!this.dataLoaded) { + await this.loadData(); + } + return this.$name; + } + + /** + * Sets the username of the user + * @param name + */ + public async setName(name: string): Promise { + const result = await queryHelper.first({ + text: "UPDATE TABLE users SET name = $1 WHERE id = $2", + values: [name, this.id], + }); + return result.name; + } + + /** + * The unique handle of the user. + */ + public async handle(): Promise { + if (!this.dataLoaded) { + await this.loadData(); + } + return this.$handle; + } + + /** + * Updates the handle of the user + */ + public async setHandle(handle: string): Promise { + const result = await queryHelper.first({ + text: "UPDATE TABLE users SET handle = $1 WHERE id = $2", + values: [handle, this.id], + }); + return result.handle; + } + + /** + * The email of the user + */ + public async email(): Promise { + if (!this.dataLoaded) { + await this.loadData(); + } + return this.$email; + } + + /** + * Sets the email of the user + * @param email + */ + public async setEmail(email: string): Promise { + const result = await queryHelper.first({ + text: "UPDATE TABLE users SET email = $1 WHERE users.id = $2 RETURNING email", + values: [email, this.id], + }); + return result.email; + } + + /** + * The number of greenpoints of the user + */ + public async greenpoints(): Promise { + if (!this.dataLoaded) { + await this.loadData(); + } + return this.$greenpoints; + } + + /** + * Sets the greenpoints of a user. + * @param points + */ + public async setGreenpoints(points: number): Promise { + const result = await queryHelper.first({ + text: "UPDATE users SET greenpoints = $1 WHERE id = $2 RETURNING greenpoints", + values: [points, this.id], + }); + return result.greenpoints; + } + + /** + * The date the user joined the platform + */ + public async joinedAt(): Promise { + if (!this.dataLoaded) { + await this.loadData(); + } + return new Date(this.$joinedAt); + } + + /** + * Fetches the data for the user. + */ + private async loadData(): Promise { + let result: any; + if (this.row) { + result = this.row; + } else { + result = await queryHelper.first({ + text: "SELECT * FROM users WHERE user.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; + } + } +} + +export class Post { + public readonly id: number; + private $upvotes: number; + private $downvotes: number; + private $createdAt: string; + private $content: string; + private $author: number; + private $type: string; + private dataLoaded: boolean = false; + + constructor(id: number, private row?: any) { + this.id = id; + } + + /** + * Returns the upvotes of a post. + */ + public async upvotes() { + if (!this.dataLoaded) { + await this.loadData(); + } + return this.$upvotes; + } + + /** + * Returns the downvotes of the post + */ + public async downvotes() { + if (!this.dataLoaded) { + await this.loadData(); + } + return this.$downvotes; + } + + /** + * Loads the data from the database if needed. + */ + private async loadData(): Promise { + let result: any; + if (this.row) { + result = this.row; + } else { + result = await queryHelper.first({ + text: "SELECT * FROM posts WHERE posts.id = $1", + values: [this.id], + }); + } + if (result) { + this.$author = result.author; + this.$content = result.content; + this.$downvotes = result.downvotes; + this.$upvotes = result.upvotes; + this.$createdAt = result.created_at; + this.$type = result.type; + this.dataLoaded = true; + } + } +} diff --git a/src/routes/home.ts b/src/routes/home.ts index e69de29..d1015f7 100644 --- a/src/routes/home.ts +++ b/src/routes/home.ts @@ -0,0 +1,10 @@ +import {Router} from "express"; + +const router = Router(); + +/* GET home page. */ +router.get("/", (req, res) => { + res.render("home"); +}); + +export default router; diff --git a/src/routes/index.ts b/src/routes/index.ts new file mode 100644 index 0000000..b8e9138 --- /dev/null +++ b/src/routes/index.ts @@ -0,0 +1,22 @@ +import {Router} from "express"; +import {Server} from "socket.io"; + +import homeRouter from "./home"; + +namespace routes { + export const router = Router(); + + router.use("/", homeRouter); + + export const resolvers = async (request: any, response: any): Promise => { + return { + }; + }; + + // tslint:disable-next-line:no-empty + export const ioListeners = (io: Server) => { + + }; +} + +export default routes; diff --git a/src/views/home.pug b/src/views/home.pug index e69de29..8b7aebb 100644 --- a/src/views/home.pug +++ b/src/views/home.pug @@ -0,0 +1,5 @@ +html + head + title Greenvironment Network + body + h1 Greenvironment From f7572202f97d5a537ef8744465b3c7d21aa51038 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Fri, 13 Sep 2019 23:18:59 +0200 Subject: [PATCH 10/40] Config fix, structure - fixed problem with the config file creation from the default config - changed structure of the home route - added abstract Route class from whooshy to manage all connection types --- CHANGELOG.md | 1 + package-lock.json | 11 +++----- src/app.ts | 1 + src/index.ts | 7 ------ src/lib/QueryHelper.ts | 22 ++++++++++++++++ src/lib/Route.ts | 27 ++++++++++++++++++++ src/lib/globals.ts | 15 +++++++++++ src/routes/home.ts | 57 +++++++++++++++++++++++++++++++++++++----- src/routes/index.ts | 36 ++++++++++++++++++++------ 9 files changed, 149 insertions(+), 28 deletions(-) create mode 100644 src/lib/Route.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index ddc745c..c7d9c5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,3 +12,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Graphql Schema - default-config file and generation of config file on startup - DTOs +- Home Route diff --git a/package-lock.json b/package-lock.json index bb5ff31..45b28c0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2691,14 +2691,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" @@ -2717,7 +2715,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -2897,8 +2894,7 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -3004,8 +3000,7 @@ "yallist": { "version": "3.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, diff --git a/src/app.ts b/src/app.ts index c5086c1..698a895 100644 --- a/src/app.ts +++ b/src/app.ts @@ -24,6 +24,7 @@ class App { */ public async init() { await this.dto.init(); + await routes.ioListeners(this.io); this.app.set("views", path.join(__dirname, "views")); this.app.set("view engine", "pug"); this.app.use(routes.router); diff --git a/src/index.ts b/src/index.ts index 4e814b5..e6c66b9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,16 +1,9 @@ -import * as fsx from "fs-extra"; import App from "./app"; -const configPath = "config.yaml"; -const defaultConfig = __dirname + "/default-config.yaml"; - /** * async main function wrapper. */ (async () => { - if (!(await fsx.pathExists(configPath))) { - await fsx.copy(defaultConfig, configPath); - } const app = new App(); await app.init(); app.start(); diff --git a/src/lib/QueryHelper.ts b/src/lib/QueryHelper.ts index 3431994..7586276 100644 --- a/src/lib/QueryHelper.ts +++ b/src/lib/QueryHelper.ts @@ -1,10 +1,24 @@ +/** + * @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; +/** + * Transaction class to wrap SQL transactions. + */ export class SqlTransaction { + /** + * Constructor. + * @param client + */ constructor(private client: PoolClient) { } @@ -45,9 +59,17 @@ export class SqlTransaction { } } +/** + * Query helper for easyer fetching of a specific row count. + */ export class QueryHelper { private pool: Pool; + /** + * Constructor. + * @param pgPool + * @param tableCreationFile + */ constructor(pgPool: Pool, private tableCreationFile?: string) { this.pool = pgPool; } diff --git a/src/lib/Route.ts b/src/lib/Route.ts new file mode 100644 index 0000000..63c25ac --- /dev/null +++ b/src/lib/Route.ts @@ -0,0 +1,27 @@ +/** + * @author Trivernis + * @remarks + * + * Taken from {@link https://github.com/Trivernis/whooshy} + */ + +import {Router} from "express"; +import {Namespace, Server} from "socket.io"; + +/** + * Abstract Route class to be implemented by each route. + * This class contains the socket-io Server, router and resolver + * for each route. + */ +abstract class Route { + + public router?: Router; + protected io?: Server; + protected ions?: Namespace; + + public abstract async init(...params: any): Promise; + public abstract async destroy(...params: any): Promise; + public abstract async resolver(request: any, response: any): Promise; +} + +export default Route; diff --git a/src/lib/globals.ts b/src/lib/globals.ts index a64aaf0..a85ffea 100644 --- a/src/lib/globals.ts +++ b/src/lib/globals.ts @@ -1,7 +1,22 @@ +/** + * @author Trivernis + * @remarks + * + * Partly taken from {@link https://github.com/Trivernis/whooshy} + */ + import * as fsx from "fs-extra"; import * as yaml from "js-yaml"; import * as winston from "winston"; +const configPath = "config.yaml"; +const defaultConfig = __dirname + "/../default-config.yaml"; + +// ensure that the config exists by copying the default config. +if (!(fsx.pathExistsSync(configPath))) { + fsx.copySync(defaultConfig, configPath); +} + /** * Defines global variables to be used. */ diff --git a/src/routes/home.ts b/src/routes/home.ts index d1015f7..1af3be0 100644 --- a/src/routes/home.ts +++ b/src/routes/home.ts @@ -1,10 +1,55 @@ import {Router} from "express"; +import {Server} from "socket.io"; +import Route from "../lib/Route"; -const router = Router(); +/** + * Class for the home route. + */ +class HomeRoute extends Route { + /** + * Constructor, creates new router. + */ + constructor() { + super(); + this.router = Router(); + this.configure(); + } -/* GET home page. */ -router.get("/", (req, res) => { - res.render("home"); -}); + /** + * Asynchronous init for socket.io. + * @param io - the io instance + */ + public async init(io: Server) { + this.io = io; + } -export default router; + /** + * Destroys the instance by dereferencing the router and resolver. + */ + public async destroy(): Promise { + this.router = null; + this.resolver = null; + } + + /** + * Returns the resolvers for the graphql api. + * @param req - the request object + * @param res - the response object + */ + public async resolver(req: any, res: any): Promise { + return { + // TODO: Define grapql resolvers + }; + } + + /** + * Configures the route. + */ + private configure() { + this.router.get("/", (req, res) => { + res.render("home"); + }); + } +} + +export default HomeRoute; diff --git a/src/routes/index.ts b/src/routes/index.ts index b8e9138..84cc7c5 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -1,21 +1,43 @@ +/** + * @author Trivernis + * @remarks + * + * Taken from {@link https://github.com/Trivernis/whooshy} + */ + import {Router} from "express"; import {Server} from "socket.io"; -import homeRouter from "./home"; +import HomeRoute from "./home"; + +const homeRoute = new HomeRoute(); +/** + * Namespace to manage the routes of the server. + * Allows easier assignments of graphql endpoints, socket.io connections and routers when + * used with {@link Route}. + */ namespace routes { export const router = Router(); - router.use("/", homeRouter); + router.use("/", homeRoute.router); + /** + * Asnyc function to create a graphql resolver that takes the request and response + * of express.js as arguments. + * @param request + * @param response + */ export const resolvers = async (request: any, response: any): Promise => { - return { - }; + return homeRoute.resolver(request, response); }; - // tslint:disable-next-line:no-empty - export const ioListeners = (io: Server) => { - + /** + * Assigns the io listeners or namespaces to the routes + * @param io + */ + export const ioListeners = async (io: Server) => { + await homeRoute.init(io); }; } From 0ca174b335740ecb17ec6bbb97253f3e6bdabb8f Mon Sep 17 00:00:00 2001 From: Trivernis Date: Sat, 21 Sep 2019 13:34:44 +0200 Subject: [PATCH 11/40] DataAccess changes - changed data access structure - changed graphql schema - changed create-tables sql script (added vote_type to votes table) --- src/app.ts | 6 +- src/lib/dataaccess/DataObject.ts | 14 ++ src/lib/dataaccess/Post.ts | 101 ++++++++++++++ src/lib/dataaccess/User.ts | 136 +++++++++++++++++++ src/lib/{DTO.ts => dataaccess/index.ts} | 4 +- src/public/graphql/schema.graphql | 167 ++++++++++++++---------- src/sql/create-tables.sql | 3 +- 7 files changed, 355 insertions(+), 76 deletions(-) create mode 100644 src/lib/dataaccess/DataObject.ts create mode 100644 src/lib/dataaccess/Post.ts create mode 100644 src/lib/dataaccess/User.ts rename src/lib/{DTO.ts => dataaccess/index.ts} (98%) diff --git a/src/app.ts b/src/app.ts index 698a895..3634256 100644 --- a/src/app.ts +++ b/src/app.ts @@ -2,7 +2,7 @@ import * as express from "express"; import * as http from "http"; import * as path from "path"; import * as socketIo from "socket.io"; -import {DTO} from "./lib/DTO"; +import dataaccess from "./lib/dataaccess"; import globals from "./lib/globals"; import routes from "./routes"; @@ -10,20 +10,18 @@ class App { public app: express.Application; public io: socketIo.Server; public server: http.Server; - public dto: DTO; constructor() { this.app = express(); this.server = new http.Server(this.app); this.io = socketIo(this.server); - this.dto = new DTO(); } /** * initializes everything that needs to be initialized asynchronous. */ public async init() { - await this.dto.init(); + await dataaccess.init(); await routes.ioListeners(this.io); this.app.set("views", path.join(__dirname, "views")); this.app.set("view engine", "pug"); diff --git a/src/lib/dataaccess/DataObject.ts b/src/lib/dataaccess/DataObject.ts new file mode 100644 index 0000000..afabc32 --- /dev/null +++ b/src/lib/dataaccess/DataObject.ts @@ -0,0 +1,14 @@ +abstract class DataObject { + protected dataLoaded: boolean = false; + + constructor(public id: number, protected row?: any) { + } + + protected abstract loadData(): Promise; + + protected loadDataIfNotExists() { + if (this.dataLoaded) { + this.loadData(); + } + } +} diff --git a/src/lib/dataaccess/Post.ts b/src/lib/dataaccess/Post.ts new file mode 100644 index 0000000..51c6b4c --- /dev/null +++ b/src/lib/dataaccess/Post.ts @@ -0,0 +1,101 @@ +import {queryHelper, VoteType} from "./index"; +import {User} from "./User"; + +export class Post extends DataObject { + public readonly id: number; + private $upvotes: number; + private $downvotes: number; + private $createdAt: string; + private $content: string; + private $author: number; + private $type: string; + + /** + * Returns the upvotes of a post. + */ + public async upvotes(): Promise { + this.loadDataIfNotExists(); + return this.$upvotes; + } + + /** + * Returns the downvotes of the post + */ + public async downvotes(): Promise { + this.loadDataIfNotExists(); + return this.$downvotes; + } + + /** + * The content of the post (markdown) + */ + public async content(): Promise { + this.loadDataIfNotExists(); + return this.$content; + } + + /** + * The date the post was created at. + */ + public async createdAt(): Promise { + this.loadDataIfNotExists(); + return this.$createdAt; + } + + /** + * The autor of the post. + */ + public async author(): Promise { + this.loadDataIfNotExists(); + return new User(this.$author); + } + + /** + * Deletes the post. + */ + public async delete(): Promise { + const query = await queryHelper.first({ + text: "DELETE FROM posts WHERE id = $1", + values: [this.id], + }); + } + + /** + * The type of vote the user performed on the post. + */ + public async userVote(userId: number): Promise { + const result = await queryHelper.first({ + text: "SELECT vote_type FROM votes WHERE user_id = $1 AND item_id = $2", + values: [userId, this.id], + }); + if (result) { + return result.vote_type; + } else { + return null; + } + } + + /** + * 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({ + text: "SELECT * FROM posts WHERE posts.id = $1", + values: [this.id], + }); + } + if (result) { + this.$author = result.author; + this.$content = result.content; + this.$downvotes = result.downvotes; + this.$upvotes = result.upvotes; + this.$createdAt = result.created_at; + this.$type = result.type; + this.dataLoaded = true; + } + } +} diff --git a/src/lib/dataaccess/User.ts b/src/lib/dataaccess/User.ts new file mode 100644 index 0000000..ea7c7c9 --- /dev/null +++ b/src/lib/dataaccess/User.ts @@ -0,0 +1,136 @@ +import {queryHelper} from "./index"; +import {Post} from "./Post"; + +export class User extends DataObject { + private $name: string; + private $handle: string; + private $email: string; + private $greenpoints: number; + private $joinedAt: string; + + /** + * The name of the user + */ + public async name(): Promise { + this.loadDataIfNotExists(); + return this.$name; + } + + /** + * Sets the username of the user + * @param name + */ + public async setName(name: string): Promise { + const result = await queryHelper.first({ + text: "UPDATE TABLE users SET name = $1 WHERE id = $2", + values: [name, this.id], + }); + return result.name; + } + + /** + * The unique handle of the user. + */ + public async handle(): Promise { + this.loadDataIfNotExists(); + return this.$handle; + } + + /** + * Updates the handle of the user + */ + public async setHandle(handle: string): Promise { + const result = await queryHelper.first({ + text: "UPDATE TABLE users SET handle = $1 WHERE id = $2", + values: [handle, this.id], + }); + return result.handle; + } + + /** + * The email of the user + */ + public async email(): Promise { + this.loadDataIfNotExists(); + return this.$email; + } + + /** + * Sets the email of the user + * @param email + */ + public async setEmail(email: string): Promise { + const result = await queryHelper.first({ + text: "UPDATE TABLE users SET email = $1 WHERE users.id = $2 RETURNING email", + values: [email, this.id], + }); + return result.email; + } + + /** + * The number of greenpoints of the user + */ + public async greenpoints(): Promise { + this.loadDataIfNotExists(); + return this.$greenpoints; + } + + /** + * Sets the greenpoints of a user. + * @param points + */ + public async setGreenpoints(points: number): Promise { + const result = await queryHelper.first({ + text: "UPDATE users SET greenpoints = $1 WHERE id = $2 RETURNING greenpoints", + values: [points, this.id], + }); + return result.greenpoints; + } + + /** + * The date the user joined the platform + */ + public async joinedAt(): Promise { + this.loadDataIfNotExists(); + return new Date(this.$joinedAt); + } + + /** + * Returns all posts for a user. + */ + public async posts(): Promise { + const result = await queryHelper.all({ + text: "SELECT * FROM posts WHERE author = $1", + values: [this.id], + }); + const posts = []; + + for (const row of result) { + posts.push(new Post(row.id, row)); + } + return posts; + } + + /** + * Fetches the data for the user. + */ + protected async loadData(): Promise { + let result: any; + if (this.row) { + result = this.row; + } else { + result = await queryHelper.first({ + text: "SELECT * FROM users WHERE user.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/DTO.ts b/src/lib/dataaccess/index.ts similarity index 98% rename from src/lib/DTO.ts rename to src/lib/dataaccess/index.ts index 2d93484..6325227 100644 --- a/src/lib/DTO.ts +++ b/src/lib/dataaccess/index.ts @@ -1,7 +1,7 @@ import {Runtime} from "inspector"; import {Pool} from "pg"; -import globals from "./globals"; -import {QueryHelper} from "./QueryHelper"; +import globals from "../globals"; +import {QueryHelper} from "../QueryHelper"; const config = globals.config; const tableCreationFile = __dirname + "/../sql/create-tables.sql"; diff --git a/src/public/graphql/schema.graphql b/src/public/graphql/schema.graphql index f5feb67..fb04f53 100644 --- a/src/public/graphql/schema.graphql +++ b/src/public/graphql/schema.graphql @@ -1,100 +1,129 @@ type Query { - "returns the user object for a given user id" - getUser(userId: ID): User - "returns the post object for a post id" - getPost(postId: ID): Post - "returns the chat object for a chat id" - getChat(chatId: ID): ChatRoom - "returns the request object for a request id" - getRequest(requestId: ID): Request - "find a post by the posted date or content" - findPost(first: Int, offset: Int, text: String!, postedDate: String): [Post] - "find a user by user name or handle" - findUser(first: Int, offset: Int, name: String!, handle: String!): [User] + "returns the user object for a given user id" + getUser(userId: ID): User + + "returns the post object for a post id" + getPost(postId: ID): Post + + "returns the chat object for a chat id" + getChat(chatId: ID): ChatRoom + + "returns the request object for a request id" + getRequest(requestId: ID): Request + + "find a post by the posted date or content" + findPost(first: Int, offset: Int, text: String!, postedDate: String): [Post] + + "find a user by user name or handle" + findUser(first: Int, offset: Int, name: String!, handle: String!): [User] } type Mutation { - "Upvote/downvote a Post" - vote(postId: ID!, type: [VoteType!]!): Boolean - "Report the post" - report(postId: ID!): Boolean + "Upvote/downvote a Post" + vote(postId: ID!, type: [VoteType!]!): Boolean + + "Report the post" + report(postId: ID!): Boolean + "lets you accept a request for a given request id" - acceptRequest(requestId: ID!): Boolean + acceptRequest(requestId: ID!): Boolean + "send a message in a Chatroom" - sendMessage(chatId: ID!, content: String!): Boolean + sendMessage(chatId: ID!, content: String!): Boolean + + # TODO: createPost, deletePost, sendRequest, denyRequest } "represents a single user account" type User { - "url for the Profile picture of the User" - profilePicture: String! - "name of the User" - name: String! - "unique identifier name from the User" - handle: String! - "Id of the User" - id: ID! - "the total number of posts the user posted" - numberOfPosts: Int - "returns a given number of posts of a user" - getAllPosts(first: Int=10, offset: Int): [Post] - "creation date of the user account" - joinedDate: String! - "returns chats the user pinned" - pinnedChats: [ChatRoom] - "returns all friends of the user" - friends: [User] - "all request for groupChats/friends/events" - requests: [Request] + "url for the Profile picture of the User" + profilePicture: String! + + "name of the User" + name: String! + + "unique identifier name from the User" + handle: String! + + "Id of the User" + id: ID! + + "the total number of posts the user posted" + numberOfPosts: Int + + "returns a given number of posts of a user" + getAllPosts(first: Int=10, offset: Int): [Post] + + "creation date of the user account" + joinedDate: String! + + "returns chats the user pinned" + pinnedChats: [ChatRoom] + + "returns all friends of the user" + friends: [User] + + "all request for groupChats/friends/events" + requests: [Request] } "represents a single user post" type Post { - "returns the path to the posts picture if it has one" - picture: String - "returns the text of the post" - text: String - "upvotes of the Post" - upvotes: Int! - "downvotes of the Post" - downvotes: Int! - "the user that is the author of the Post" - author: User! - "date the post was created" - creationDate: String! - "returns the type of vote the user performed on the post" - alreadyVoted: VoteType - "returns the tags of the post" - tags: [String] + "returns the path to the posts picture if it has one" + picture: String + + "returns the text of the post" + text: String + + "upvotes of the Post" + upvotes: Int! + + "downvotes of the Post" + downvotes: Int! + + "the user that is the author of the Post" + author: User! + + "date the post was created" + creationDate: String! + + "returns the type of vote the user performed on the post" + userVote: VoteType + + "returns the tags of the post" + tags: [String] } "represents a request of any type" type Request { - "id of the request" - id: ID! - "type of the request" - requestType: RequestType! + "id of the request" + id: ID! + + "type of the request" + requestType: RequestType! } "represents a chatroom" type ChatRoom { - "the members of the chatroom" - members: [User!] - "return a specfic range of messages posted in the chat" - getMessages(first: Int, offset: Int): [String] - "id of the chat" - id: ID! + "the members of the chatroom" + members: [User!] + + "return a specfic range of messages posted in the chat" + getMessages(first: Int, offset: Int): [String] + + "id of the chat" + id: ID! } "represents the type of vote performed on a post" enum VoteType { - UPVOTE - DOWNVOTE + UPVOTE + DOWNVOTE } "represents the type of request that the user has received" enum RequestType { - FRIENDREQUEST - GROUPINVITE - EVENTINVITE + FRIENDREQUEST + GROUPINVITE + EVENTINVITE } diff --git a/src/sql/create-tables.sql b/src/sql/create-tables.sql index 0d02b2f..7a6292e 100644 --- a/src/sql/create-tables.sql +++ b/src/sql/create-tables.sql @@ -20,7 +20,8 @@ CREATE TABLE IF NOT EXISTS posts ( CREATE TABLE IF NOT EXISTS votes ( user_id SERIAL REFERENCES users (id) ON DELETE CASCADE, - item_id BIGSERIAL REFERENCES posts (id) ON DELETE CASCADE + item_id BIGSERIAL REFERENCES posts (id) ON DELETE CASCADE, + vote_type varchar(8) DEFAULT 1 ); CREATE TABLE IF NOT EXISTS events ( From 67650267421d1e91400771c4cf9c3cac27c93890 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Sat, 21 Sep 2019 13:34:58 +0200 Subject: [PATCH 12/40] Changed dataAccess index --- src/lib/dataaccess/index.ts | 219 +++--------------------------------- 1 file changed, 17 insertions(+), 202 deletions(-) diff --git a/src/lib/dataaccess/index.ts b/src/lib/dataaccess/index.ts index 6325227..8019329 100644 --- a/src/lib/dataaccess/index.ts +++ b/src/lib/dataaccess/index.ts @@ -1,7 +1,7 @@ -import {Runtime} from "inspector"; import {Pool} from "pg"; import globals from "../globals"; import {QueryHelper} from "../QueryHelper"; +import {User} from "./User"; const config = globals.config; const tableCreationFile = __dirname + "/../sql/create-tables.sql"; @@ -12,27 +12,21 @@ const dbClient: Pool = new Pool({ port: config.database.port, user: config.database.user, }); -const queryHelper = new QueryHelper(dbClient, tableCreationFile); - -export class DTO { - private queryHelper: QueryHelper; - - constructor() { - this.queryHelper = queryHelper; - } +export const queryHelper = new QueryHelper(dbClient, tableCreationFile); +namespace dataaccess { /** * Initializes everything that needs to be initialized asynchronous. */ - public async init() { - await this.queryHelper.createTables(); + export async function init() { + await queryHelper.createTables(); } /** * Returns the user by id * @param userId */ - public getUser(userId: number) { + export function getUser(userId: number) { return new User(userId); } @@ -40,209 +34,30 @@ export class DTO { * Returns the user by handle. * @param userHandle */ - public async getUserByHandle(userHandle: string) { + export async function getUserByHandle(userHandle: string) { const result = await this.queryHelper.first({ text: "SELECT * FROM users WHERE users.handle = $1", values: [userHandle], }); return new User(result.id, result); } -} - -export class User { - public readonly id: number; - private $name: string; - private $handle: string; - private $email: string; - private $greenpoints: number; - private $joinedAt: string; - private dataLoaded: boolean; - - /** - * Constructor of the user - * @param id - * @param row - */ - constructor(id: number, private row?: any) { - this.id = id; - } - - /** - * The name of the user - */ - public async name(): Promise { - if (!this.dataLoaded) { - await this.loadData(); - } - return this.$name; - } - - /** - * Sets the username of the user - * @param name - */ - public async setName(name: string): Promise { - const result = await queryHelper.first({ - text: "UPDATE TABLE users SET name = $1 WHERE id = $2", - values: [name, this.id], - }); - return result.name; - } - - /** - * The unique handle of the user. - */ - public async handle(): Promise { - if (!this.dataLoaded) { - await this.loadData(); - } - return this.$handle; - } - - /** - * Updates the handle of the user - */ - public async setHandle(handle: string): Promise { - const result = await queryHelper.first({ - text: "UPDATE TABLE users SET handle = $1 WHERE id = $2", - values: [handle, this.id], - }); - return result.handle; - } - - /** - * The email of the user - */ - public async email(): Promise { - if (!this.dataLoaded) { - await this.loadData(); - } - return this.$email; - } - - /** - * Sets the email of the user - * @param email - */ - public async setEmail(email: string): Promise { - const result = await queryHelper.first({ - text: "UPDATE TABLE users SET email = $1 WHERE users.id = $2 RETURNING email", - values: [email, this.id], - }); - return result.email; - } - - /** - * The number of greenpoints of the user - */ - public async greenpoints(): Promise { - if (!this.dataLoaded) { - await this.loadData(); - } - return this.$greenpoints; - } - - /** - * Sets the greenpoints of a user. - * @param points - */ - public async setGreenpoints(points: number): Promise { - const result = await queryHelper.first({ - text: "UPDATE users SET greenpoints = $1 WHERE id = $2 RETURNING greenpoints", - values: [points, this.id], - }); - return result.greenpoints; - } /** - * The date the user joined the platform + * Enum representing the types of votes that can be performed on a post. */ - public async joinedAt(): Promise { - if (!this.dataLoaded) { - await this.loadData(); - } - return new Date(this.$joinedAt); + export enum VoteType { + UPVOTE = "UPVOTE", + DOWNVOTE = "DOWNVOTE", } /** - * Fetches the data for the user. + * Enum representing the types of request that can be created. */ - private async loadData(): Promise { - let result: any; - if (this.row) { - result = this.row; - } else { - result = await queryHelper.first({ - text: "SELECT * FROM users WHERE user.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; - } + export enum RequestType { + FRIENDREQUEST = "FRIENDREQUEST", + GROUPINVITE = "GROUPINVITE", + EVENTINVITE = "EVENTINVITE", } } -export class Post { - public readonly id: number; - private $upvotes: number; - private $downvotes: number; - private $createdAt: string; - private $content: string; - private $author: number; - private $type: string; - private dataLoaded: boolean = false; - - constructor(id: number, private row?: any) { - this.id = id; - } - - /** - * Returns the upvotes of a post. - */ - public async upvotes() { - if (!this.dataLoaded) { - await this.loadData(); - } - return this.$upvotes; - } - - /** - * Returns the downvotes of the post - */ - public async downvotes() { - if (!this.dataLoaded) { - await this.loadData(); - } - return this.$downvotes; - } - - /** - * Loads the data from the database if needed. - */ - private async loadData(): Promise { - let result: any; - if (this.row) { - result = this.row; - } else { - result = await queryHelper.first({ - text: "SELECT * FROM posts WHERE posts.id = $1", - values: [this.id], - }); - } - if (result) { - this.$author = result.author; - this.$content = result.content; - this.$downvotes = result.downvotes; - this.$upvotes = result.upvotes; - this.$createdAt = result.created_at; - this.$type = result.type; - this.dataLoaded = true; - } - } -} +export default dataaccess; From 65d5cee2bb2914c6b83a460e2bdd81e0299abd9a Mon Sep 17 00:00:00 2001 From: RandomUser27 Date: Sun, 22 Sep 2019 13:14:37 +0200 Subject: [PATCH 13/40] package.json --- package-lock.json | 33 +++++++++++++++++++-------------- package.json | 1 + 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index 212f9d3..c35e90d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -939,9 +939,9 @@ } }, "chokidar": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.6.tgz", - "integrity": "sha512-V2jUo67OKkc6ySiRpJrjlpJKl9kDuG+Xb8VgsGzb+aEouhgS1D0weyPU4lEzdAcsCAvrih2J2BqyXqHWvVLw5g==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", "dev": true, "requires": { "anymatch": "^2.0.0", @@ -1723,9 +1723,9 @@ } }, "es5-ext": { - "version": "0.10.50", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.50.tgz", - "integrity": "sha512-KMzZTPBkeQV/JcSQhI5/z6d9VWJ3EnQ194USTUwIYZ2ZbpN8+SGXQKt1h68EX44+qt+Fzr8DO17vnxrw7c3agw==", + "version": "0.10.51", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.51.tgz", + "integrity": "sha512-oRpWzM2WcLHVKpnrcyB7OW8j/s67Ba04JCm0WnNv3RiABSvs7mrQlutB8DBv793gKcp0XENR8Il8WxGTlZ73gQ==", "dev": true, "requires": { "es6-iterator": "~2.0.3", @@ -1745,13 +1745,13 @@ } }, "es6-symbol": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", - "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.2.tgz", + "integrity": "sha512-/ZypxQsArlv+KHpGvng52/Iz8by3EQPxhmbuz8yFG89N/caTFBSbcXONDw0aMjy827gQg26XAjP4uXFvnfINmQ==", "dev": true, "requires": { - "d": "1", - "es5-ext": "~0.10.14" + "d": "^1.0.1", + "es5-ext": "^0.10.51" } }, "es6-weak-map": { @@ -2893,6 +2893,11 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, + "g": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/g/-/g-2.0.1.tgz", + "integrity": "sha1-C1lj69DKcOO8jGdmk0oCGCHIuFc=" + }, "gauge": { "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", @@ -6625,9 +6630,9 @@ } }, "upath": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.2.tgz", - "integrity": "sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", "dev": true }, "uri-js": { diff --git a/package.json b/package.json index ae3c9f0..242fe85 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "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", "js-yaml": "^3.13.1", From b623018f76d61ab951924a4847bc8f6c2723b233 Mon Sep 17 00:00:00 2001 From: RandomUser27 Date: Sun, 22 Sep 2019 13:36:17 +0200 Subject: [PATCH 14/40] schema.graphql added: send reqest; deny request; post the post; sender and reciverid from request; --- src/public/graphql/schema.graphql | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/public/graphql/schema.graphql b/src/public/graphql/schema.graphql index fb04f53..e4705d6 100644 --- a/src/public/graphql/schema.graphql +++ b/src/public/graphql/schema.graphql @@ -25,13 +25,23 @@ type Mutation { "Report the post" report(postId: ID!): Boolean + "send a request" + sendRequest(request: Request!): Boolean + "lets you accept a request for a given request id" acceptRequest(requestId: ID!): Boolean + "lets you deny a request for a given request id" + denyRequest(requestId: ID!): Boolean + "send a message in a Chatroom" sendMessage(chatId: ID!, content: String!): Boolean - # TODO: createPost, deletePost, sendRequest, denyRequest + "create the post" + createPost(post: Post!): Boolean + + "delete the post for a given post id" + deletePost(postId: ID!): Boolean } "represents a single user account" @@ -99,6 +109,12 @@ type Request { "id of the request" id: ID! + "Id of the user who sended the request" + sender: User! + + "Id of the user who received the request" + receiver: User! + "type of the request" requestType: RequestType! } From ff77c14beca90be7a63fb862a91bb72120b09cb8 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Sun, 22 Sep 2019 20:14:04 +0200 Subject: [PATCH 15/40] Fixed Database Integration - fixed wrong table creation path - added table update path --- src/lib/QueryHelper.ts | 16 ++++++++++++++-- src/lib/dataaccess/DataObject.ts | 8 +++++++- src/lib/dataaccess/Post.ts | 6 ++++-- src/lib/dataaccess/User.ts | 1 + src/lib/dataaccess/index.ts | 7 +++++-- src/sql/create-tables.sql | 2 +- src/sql/update-tables.sql | 3 +++ 7 files changed, 35 insertions(+), 8 deletions(-) create mode 100644 src/sql/update-tables.sql diff --git a/src/lib/QueryHelper.ts b/src/lib/QueryHelper.ts index 7586276..5970f70 100644 --- a/src/lib/QueryHelper.ts +++ b/src/lib/QueryHelper.ts @@ -68,9 +68,10 @@ export class QueryHelper { /** * Constructor. * @param pgPool - * @param tableCreationFile + * @param [tableCreationFile] + * @param [tableUpdateFile] */ - constructor(pgPool: Pool, private tableCreationFile?: string) { + constructor(pgPool: Pool, private tableCreationFile?: string, private tableUpdateFile?: string) { this.pool = pgPool; } @@ -85,6 +86,17 @@ export class QueryHelper { } } + /** + * 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"); + await this.query({text: tableSql}); + } + } + /** * executes the sql query with values and returns all results. * @param query diff --git a/src/lib/dataaccess/DataObject.ts b/src/lib/dataaccess/DataObject.ts index afabc32..1890425 100644 --- a/src/lib/dataaccess/DataObject.ts +++ b/src/lib/dataaccess/DataObject.ts @@ -1,4 +1,7 @@ -abstract class DataObject { +/** + * abstact DataObject class + */ +export abstract class DataObject { protected dataLoaded: boolean = false; constructor(public id: number, protected row?: any) { @@ -6,6 +9,9 @@ abstract class DataObject { protected abstract loadData(): Promise; + /** + * Loads data from the database if data has not been loaded + */ protected loadDataIfNotExists() { if (this.dataLoaded) { this.loadData(); diff --git a/src/lib/dataaccess/Post.ts b/src/lib/dataaccess/Post.ts index 51c6b4c..b4193c4 100644 --- a/src/lib/dataaccess/Post.ts +++ b/src/lib/dataaccess/Post.ts @@ -1,4 +1,6 @@ -import {queryHelper, VoteType} from "./index"; +import {DataObject} from "./DataObject"; +import {queryHelper} from "./index"; +import dataaccess from "./index"; import {User} from "./User"; export class Post extends DataObject { @@ -63,7 +65,7 @@ export class Post extends DataObject { /** * The type of vote the user performed on the post. */ - public async userVote(userId: number): Promise { + public async userVote(userId: number): Promise { const result = await queryHelper.first({ text: "SELECT vote_type FROM votes WHERE user_id = $1 AND item_id = $2", values: [userId, this.id], diff --git a/src/lib/dataaccess/User.ts b/src/lib/dataaccess/User.ts index ea7c7c9..bb680c4 100644 --- a/src/lib/dataaccess/User.ts +++ b/src/lib/dataaccess/User.ts @@ -1,3 +1,4 @@ +import {DataObject} from "./DataObject"; import {queryHelper} from "./index"; import {Post} from "./Post"; diff --git a/src/lib/dataaccess/index.ts b/src/lib/dataaccess/index.ts index 8019329..1d610ef 100644 --- a/src/lib/dataaccess/index.ts +++ b/src/lib/dataaccess/index.ts @@ -4,7 +4,9 @@ import {QueryHelper} from "../QueryHelper"; import {User} from "./User"; const config = globals.config; -const tableCreationFile = __dirname + "/../sql/create-tables.sql"; +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, @@ -12,13 +14,14 @@ const dbClient: Pool = new Pool({ port: config.database.port, user: config.database.user, }); -export const queryHelper = new QueryHelper(dbClient, tableCreationFile); +export const queryHelper = new QueryHelper(dbClient, tableCreationFile, tableUpdateFile); namespace dataaccess { /** * Initializes everything that needs to be initialized asynchronous. */ export async function init() { + await queryHelper.updateTableDefinitions(); await queryHelper.createTables(); } diff --git a/src/sql/create-tables.sql b/src/sql/create-tables.sql index 7a6292e..7c1c97a 100644 --- a/src/sql/create-tables.sql +++ b/src/sql/create-tables.sql @@ -21,7 +21,7 @@ CREATE TABLE IF NOT EXISTS posts ( CREATE TABLE IF NOT EXISTS votes ( user_id SERIAL REFERENCES users (id) ON DELETE CASCADE, item_id BIGSERIAL REFERENCES posts (id) ON DELETE CASCADE, - vote_type varchar(8) DEFAULT 1 + vote_type varchar(8) DEFAULT 'upvote' ); CREATE TABLE IF NOT EXISTS events ( diff --git a/src/sql/update-tables.sql b/src/sql/update-tables.sql new file mode 100644 index 0000000..52ce2f6 --- /dev/null +++ b/src/sql/update-tables.sql @@ -0,0 +1,3 @@ +ALTER TABLE IF EXISTS votes + ADD COLUMN IF NOT EXISTS vote_type varchar(8) DEFAULT 'upvote', + ALTER COLUMN vote_type SET DEFAULT 'upvote'; From 9d26d248d7ff603c1fad9406fa71c1a97b48bf04 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Sun, 22 Sep 2019 20:17:38 +0200 Subject: [PATCH 16/40] Fixed Graphql Schema - removed Query types used as Input types --- src/public/graphql/schema.graphql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/public/graphql/schema.graphql b/src/public/graphql/schema.graphql index e4705d6..ff60fd3 100644 --- a/src/public/graphql/schema.graphql +++ b/src/public/graphql/schema.graphql @@ -26,7 +26,7 @@ type Mutation { report(postId: ID!): Boolean "send a request" - sendRequest(request: Request!): Boolean + sendRequest(reciever: ID!, type: RequestType): Boolean "lets you accept a request for a given request id" acceptRequest(requestId: ID!): Boolean @@ -38,7 +38,7 @@ type Mutation { sendMessage(chatId: ID!, content: String!): Boolean "create the post" - createPost(post: Post!): Boolean + createPost(text: String, picture: String, tags: [String]): Boolean "delete the post for a given post id" deletePost(postId: ID!): Boolean From 4179aac23124de0cde68b54163e6edd109ecf0e8 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Mon, 23 Sep 2019 11:03:47 +0200 Subject: [PATCH 17/40] Frontend first layout --- src/app.ts | 1 + src/public/stylesheets/sass/mixins.sass | 5 ++ src/public/stylesheets/sass/style.sass | 82 +++++++++++++++++++++++++ src/public/stylesheets/sass/vars.sass | 5 ++ src/views/home.pug | 5 -- src/views/home/feed.pug | 13 ++++ src/views/home/friends.pug | 1 + src/views/home/index.pug | 9 +++ src/views/home/stylebar.pug | 2 + src/views/includes/head.pug | 1 + 10 files changed, 119 insertions(+), 5 deletions(-) create mode 100644 src/public/stylesheets/sass/mixins.sass create mode 100644 src/public/stylesheets/sass/vars.sass delete mode 100644 src/views/home.pug create mode 100644 src/views/home/feed.pug create mode 100644 src/views/home/friends.pug create mode 100644 src/views/home/index.pug create mode 100644 src/views/home/stylebar.pug create mode 100644 src/views/includes/head.pug diff --git a/src/app.ts b/src/app.ts index 3634256..c7079ac 100644 --- a/src/app.ts +++ b/src/app.ts @@ -25,6 +25,7 @@ class App { await routes.ioListeners(this.io); this.app.set("views", path.join(__dirname, "views")); this.app.set("view engine", "pug"); + this.app.use(express.static(path.join(__dirname, "public"))); this.app.use(routes.router); } diff --git a/src/public/stylesheets/sass/mixins.sass b/src/public/stylesheets/sass/mixins.sass new file mode 100644 index 0000000..14bca84 --- /dev/null +++ b/src/public/stylesheets/sass/mixins.sass @@ -0,0 +1,5 @@ +@mixin gridPosition($rowStart, $rowEnd, $columnStart, $columnEnd) + grid-row-start: $rowStart + grid-row-end: $rowEnd + grid-column-start: $columnStart + grid-column-end: $columnEnd diff --git a/src/public/stylesheets/sass/style.sass b/src/public/stylesheets/sass/style.sass index e69de29..5a86235 100644 --- a/src/public/stylesheets/sass/style.sass +++ b/src/public/stylesheets/sass/style.sass @@ -0,0 +1,82 @@ +@import "vars" +@import "mixins" + +body + font-family: Arial, serif + +button + border: 2px solid $cPrimary + margin-top: 0.125em + padding: 0.125em + background-color: $cPrimary + color: $cPrimarySurface + font-weight: bold + transition-duration: 0.25s + +button:hover + background-color: lighten($cPrimary, 10%) + cursor: pointer + +button:active + background-color: darken($cPrimary, 5%) + box-shadow: inset 0.25em 0.25em 0.1em rgba(0, 0, 0, 0.25) + +.stylebar + @include gridPosition(1, 2, 1, 4) + display: grid + grid-template: 100% /25% 50% 25% + background-color: $cPrimary + color: $cPrimarySurface + + h1 + @include gridPosition(1, 2, 1, 2) + text-align: center + margin: auto + +#content + grid-template: 7.5% 92.5% / 25% 50% 25% + display: grid + width: 100% + height: 100% + +#friendscontainer + @include gridPosition(2, 3, 1, 2) + background-color: $cPrimaryBackground + +#feedcontainer + @include gridPosition(2, 3, 2, 3) + background-color: $cSecondaryBackground + .postinput + margin: 0.5em + input + width: 100% + border-radius: 0.25em + border: 1px solid $cPrimary + padding: 0.125em + height: 2em + button.submitbutton + border-radius: 0.25em + height: 2em + + .feeditem + background-color: $cPrimaryBackground + min-height: 2em + margin: 0.5em + padding: 0.25em + border-radius: 0.25em + .itemhead + align-items: flex-start + + .title, .handle, .date + margin: 0.125em + .title + font-weight: bold + + .handle, .date + color: $cInactiveText + .handle a + text-decoration: none + color: $cInactiveText + font-style: normal + .handle a:hover + text-decoration: underline diff --git a/src/public/stylesheets/sass/vars.sass b/src/public/stylesheets/sass/vars.sass new file mode 100644 index 0000000..871a831 --- /dev/null +++ b/src/public/stylesheets/sass/vars.sass @@ -0,0 +1,5 @@ +$cPrimaryBackground: #fff +$cSecondaryBackground: #ddd +$cInactiveText: #555 +$cPrimary: #0d6b14 +$cPrimarySurface: #fff diff --git a/src/views/home.pug b/src/views/home.pug deleted file mode 100644 index 8b7aebb..0000000 --- a/src/views/home.pug +++ /dev/null @@ -1,5 +0,0 @@ -html - head - title Greenvironment Network - body - h1 Greenvironment diff --git a/src/views/home/feed.pug b/src/views/home/feed.pug new file mode 100644 index 0000000..e3758e1 --- /dev/null +++ b/src/views/home/feed.pug @@ -0,0 +1,13 @@ +div#feedcontainer + div.postinput + input(type=text placeholder='Post something') + button.submitbutton Submit + div.feeditem + div.itemhead + span.title Testuser + span.handle + a(href='#') @testuser + span.date 23.09.19 10:07 + p.text + | Example Test text. + | This is a test diff --git a/src/views/home/friends.pug b/src/views/home/friends.pug new file mode 100644 index 0000000..ed36fb7 --- /dev/null +++ b/src/views/home/friends.pug @@ -0,0 +1 @@ +div#friendscontainer diff --git a/src/views/home/index.pug b/src/views/home/index.pug new file mode 100644 index 0000000..0fc45ad --- /dev/null +++ b/src/views/home/index.pug @@ -0,0 +1,9 @@ +html + head + title Greenvironment Network + include ../includes/head + body + div#content + include stylebar + include feed + include friends diff --git a/src/views/home/stylebar.pug b/src/views/home/stylebar.pug new file mode 100644 index 0000000..c6c36e3 --- /dev/null +++ b/src/views/home/stylebar.pug @@ -0,0 +1,2 @@ +div.stylebar + h1 Greenvironment diff --git a/src/views/includes/head.pug b/src/views/includes/head.pug new file mode 100644 index 0000000..9e917e6 --- /dev/null +++ b/src/views/includes/head.pug @@ -0,0 +1 @@ +link(rel='stylesheet' href='stylesheets/style.css') From 031ffd14fcdff694a374b0ae05360e7c6ba7003a Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 23 Sep 2019 15:31:09 +0200 Subject: [PATCH 18/40] added login and register page --- src/public/stylesheets/sass/style.sass | 26 ++++++++++++++++++++++++++ src/routes/home.ts | 6 ++++++ src/views/login/index.pug | 8 ++++++++ src/views/login/login.pug | 6 ++++++ src/views/login/stylebar.pug | 2 ++ src/views/register/index.pug | 8 ++++++++ src/views/register/register.pug | 8 ++++++++ src/views/register/stylebar.pug | 2 ++ 8 files changed, 66 insertions(+) create mode 100644 src/views/login/index.pug create mode 100644 src/views/login/login.pug create mode 100644 src/views/login/stylebar.pug create mode 100644 src/views/register/index.pug create mode 100644 src/views/register/register.pug create mode 100644 src/views/register/stylebar.pug diff --git a/src/public/stylesheets/sass/style.sass b/src/public/stylesheets/sass/style.sass index 5a86235..5474776 100644 --- a/src/public/stylesheets/sass/style.sass +++ b/src/public/stylesheets/sass/style.sass @@ -43,6 +43,32 @@ button:active @include gridPosition(2, 3, 1, 2) background-color: $cPrimaryBackground +#input-login + margin-top: 1em + @include gridPosition(2,3,2,3) + grid-template: 7.5% 7.5% 7.5% 7.5% 72%/ 100% + display: grid + background-color: $cPrimaryBackground + + input + margin: 0.25em + + .loginButton + margin: 0.25em + +#input-register + margin-top: 1em + @include gridPosition(2,3,2,3) + grid-template: 7.5% 7.5% 7.5% 7.5% 7.5% 7.5% 58%/ 100% + display: grid + background-color: $cPrimaryBackground + + input + margin: 0.25em + + .registerButton + margin: 0.25em + #feedcontainer @include gridPosition(2, 3, 2, 3) background-color: $cSecondaryBackground diff --git a/src/routes/home.ts b/src/routes/home.ts index 1af3be0..13bb254 100644 --- a/src/routes/home.ts +++ b/src/routes/home.ts @@ -49,6 +49,12 @@ class HomeRoute extends Route { this.router.get("/", (req, res) => { res.render("home"); }); + this.router.get("/login", (req, res) => { + res.render("login"); + }); + this.router.get("/register", (req, res) => { + res.render("register"); + }); } } diff --git a/src/views/login/index.pug b/src/views/login/index.pug new file mode 100644 index 0000000..41fe877 --- /dev/null +++ b/src/views/login/index.pug @@ -0,0 +1,8 @@ +html + head + title Greenvironment Network Login + include ../includes/head + body + div#content + include stylebar + include login diff --git a/src/views/login/login.pug b/src/views/login/login.pug new file mode 100644 index 0000000..f205c95 --- /dev/null +++ b/src/views/login/login.pug @@ -0,0 +1,6 @@ +div#input-login + input(type=text placeholder='username') + input(type=text placeholder='password') + button.loginButton Login + a(href="/register" ) + | You aren´t part of greenvironment yet? - create a new account diff --git a/src/views/login/stylebar.pug b/src/views/login/stylebar.pug new file mode 100644 index 0000000..c6c36e3 --- /dev/null +++ b/src/views/login/stylebar.pug @@ -0,0 +1,2 @@ +div.stylebar + h1 Greenvironment diff --git a/src/views/register/index.pug b/src/views/register/index.pug new file mode 100644 index 0000000..d8e9dd1 --- /dev/null +++ b/src/views/register/index.pug @@ -0,0 +1,8 @@ +html + head + title Greenvironment Network Register + include ../includes/head + body + div#content + include stylebar + include register diff --git a/src/views/register/register.pug b/src/views/register/register.pug new file mode 100644 index 0000000..baaf1e6 --- /dev/null +++ b/src/views/register/register.pug @@ -0,0 +1,8 @@ +div#input-register + input(type=text placeholder='username') + input(type=text placeholder='email') + input(type=text placeholder='password') + input(type=text placeholder='repeat password') + button.registerButton Register + a(href="/login" ) + | You are already part of greenvironment? - login diff --git a/src/views/register/stylebar.pug b/src/views/register/stylebar.pug new file mode 100644 index 0000000..c6c36e3 --- /dev/null +++ b/src/views/register/stylebar.pug @@ -0,0 +1,2 @@ +div.stylebar + h1 Greenvironment From 69e535276b57a08fa0a0fdbb745b3618af920292 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Mon, 23 Sep 2019 16:13:48 +0200 Subject: [PATCH 19/40] Added sessions --- package-lock.json | 64 +++++++++++++++++++++++++++++++---- package.json | 8 +++-- src/app.ts | 43 +++++++++++++++++++++++ src/default-config.yaml | 4 +++ src/lib/dataaccess/Profile.ts | 51 ++++++++++++++++++++++++++++ src/lib/dataaccess/User.ts | 47 ------------------------- src/lib/dataaccess/index.ts | 39 +++++++++++++++++++++ src/routes/index.ts | 4 +-- src/sql/create-tables.sql | 7 ++++ 9 files changed, 208 insertions(+), 59 deletions(-) create mode 100644 src/lib/dataaccess/Profile.ts diff --git a/package-lock.json b/package-lock.json index 7baeb51..99cd62f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -55,6 +55,15 @@ "@types/node": "*" } }, + "@types/compression": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/compression/-/compression-1.0.1.tgz", + "integrity": "sha512-GuoIYzD70h+4JUqUabsm31FGqvpCYHGKcLtor7nQ/YvUyNX0o9SJZ9boFI5HjFfbOda5Oe/XOvNK6FES8Y/79w==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, "@types/connect": { "version": "3.4.32", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.32.tgz", @@ -153,7 +162,8 @@ "@types/js-yaml": { "version": "3.12.1", "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-3.12.1.tgz", - "integrity": "sha512-SGGAhXLHDx+PK4YLNcNGa6goPf9XRWQNAUUbffkwVGGXIxmDKWyGGL4inzq2sPmExu431Ekb9aEMn9BkPqEYFA==" + "integrity": "sha512-SGGAhXLHDx+PK4YLNcNGa6goPf9XRWQNAUUbffkwVGGXIxmDKWyGGL4inzq2sPmExu431Ekb9aEMn9BkPqEYFA==", + "dev": true }, "@types/mime": { "version": "2.0.1", @@ -215,6 +225,7 @@ "version": "2.4.4", "resolved": "https://registry.npmjs.org/@types/winston/-/winston-2.4.4.tgz", "integrity": "sha512-BVGCztsypW8EYwJ+Hq+QNYiT/MUyCif0ouBH+flrY66O5W+KIXAMML6E/0fJpm7VjIzgangahl5S03bJJQGrZw==", + "dev": true, "requires": { "winston": "*" } @@ -1310,6 +1321,40 @@ "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=" }, + "compressible": { + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.17.tgz", + "integrity": "sha512-BGHeLCK1GV7j1bSmQQAi26X+GgWcTjLr/0tzSvMCl3LH1w1IJ4PFSPoV5316b30cneTziC+B1a+3OjoSUcQYmw==", + "requires": { + "mime-db": ">= 1.40.0 < 2" + } + }, + "compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "requires": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "dependencies": { + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" + }, + "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==" + } + } + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -1361,9 +1406,9 @@ } }, "connect-pg-simple": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/connect-pg-simple/-/connect-pg-simple-6.0.0.tgz", - "integrity": "sha512-6pQnRSGFyswyHMdKQp5C+g78fjU/1/6eY05VeixXwMixw5KYhAcoOCXyf8TdPE1IzRLNDBMQi64vojXK/HMXVw==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/connect-pg-simple/-/connect-pg-simple-6.0.1.tgz", + "integrity": "sha512-zW5AOtRNOLcXxphSmQ+oYj0snlLs1Je3u5K2NWyF7WhMVoPvnQXraK2wzS8f7qLwhMcmYukah2ymu0Gdxf7Qsg==", "requires": { "pg": "^7.4.3" } @@ -2691,12 +2736,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -2715,6 +2762,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -2894,7 +2942,8 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -3000,7 +3049,8 @@ "yallist": { "version": "3.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, diff --git a/package.json b/package.json index 0fb8448..664e5a3 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "author": "SoftEngI", "license": "ISC", "devDependencies": { + "@types/compression": "^1.0.1", "@types/connect-pg-simple": "^4.2.0", "@types/cookie-parser": "^1.4.2", "@types/express": "^4.17.1", @@ -26,9 +27,11 @@ "@types/express-socket.io-session": "^1.3.2", "@types/fs-extra": "^8.0.0", "@types/graphql": "^14.2.3", + "@types/js-yaml": "^3.12.1", "@types/node": "^12.7.2", "@types/pg": "^7.11.0", "@types/socket.io": "^2.1.2", + "@types/winston": "^2.4.4", "delete": "^1.1.0", "gulp": "^4.0.2", "gulp-minify": "^3.1.0", @@ -40,9 +43,8 @@ "typescript": "^3.5.3" }, "dependencies": { - "@types/js-yaml": "^3.12.1", - "@types/winston": "^2.4.4", - "connect-pg-simple": "^6.0.0", + "compression": "^1.7.4", + "connect-pg-simple": "^6.0.1", "cookie-parser": "^1.4.4", "express": "^4.17.1", "express-graphql": "^0.9.0", diff --git a/src/app.ts b/src/app.ts index c7079ac..6758970 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,4 +1,12 @@ +import * as compression from "compression"; +import connectPgSimple = require("connect-pg-simple"); +import * as cookieParser from "cookie-parser"; import * as express from "express"; +import * as graphqlHTTP from "express-graphql"; +import * as session from "express-session"; +import sharedsession = require("express-socket.io-session"); +import {buildSchema} from "graphql"; +import {importSchema} from "graphql-import"; import * as http from "http"; import * as path from "path"; import * as socketIo from "socket.io"; @@ -6,6 +14,8 @@ import dataaccess from "./lib/dataaccess"; import globals from "./lib/globals"; import routes from "./routes"; +const PgSession = connectPgSimple(session); + class App { public app: express.Application; public io: socketIo.Server; @@ -23,10 +33,43 @@ class App { public async init() { await dataaccess.init(); await routes.ioListeners(this.io); + + const appSession = session({ + cookie: { + maxAge: Number(globals.config.session.cookieMaxAge), + secure: "auto", + }, + resave: false, + saveUninitialized: true, // TODO: Set to false and only save when accepted by user. + secret: globals.config.session.secret, + store: new PgSession({ + pool: dataaccess.pool, + tableName: "user_sessions", + }), + }); + + this.io.use(sharedsession(appSession, {autoSave: true})); + this.app.set("views", path.join(__dirname, "views")); this.app.set("view engine", "pug"); + this.app.set("trust proxy", 1); + + this.app.use(compression()); + this.app.use(express.json()); + this.app.use(express.urlencoded({extended: false})); this.app.use(express.static(path.join(__dirname, "public"))); + this.app.use(cookieParser()); + this.app.use(appSession); this.app.use(routes.router); + this.app.use("/graphql", graphqlHTTP(async (request, response) => { + return { + // @ts-ignore all + context: {session: request.session}, + graphiql: true, + rootValue: await routes.resolvers(request, response), + schema: buildSchema(importSchema("./public/graphql/schema.graphql")), + }; + })); } /** diff --git a/src/default-config.yaml b/src/default-config.yaml index 417ccda..e232512 100644 --- a/src/default-config.yaml +++ b/src/default-config.yaml @@ -9,3 +9,7 @@ database: # http server configuration server: port: 8080 + +session: + secret: REPLACE WITH SAFE RANDOM GENERATED SECRET + cookieMaxAge: 604800000‬ # 7 days diff --git a/src/lib/dataaccess/Profile.ts b/src/lib/dataaccess/Profile.ts new file mode 100644 index 0000000..a3f3a46 --- /dev/null +++ b/src/lib/dataaccess/Profile.ts @@ -0,0 +1,51 @@ +import {queryHelper} from "./index"; +import {User} from "./User"; + +export class Profile extends User { + /** + * Sets the greenpoints of a user. + * @param points + */ + public async setGreenpoints(points: number): Promise { + const result = await queryHelper.first({ + text: "UPDATE users SET greenpoints = $1 WHERE id = $2 RETURNING greenpoints", + values: [points, this.id], + }); + return result.greenpoints; + } + + /** + * Sets the email of the user + * @param email + */ + public async setEmail(email: string): Promise { + const result = await queryHelper.first({ + text: "UPDATE TABLE users SET email = $1 WHERE users.id = $2 RETURNING email", + values: [email, this.id], + }); + return result.email; + } + + /** + * Updates the handle of the user + */ + public async setHandle(handle: string): Promise { + const result = await queryHelper.first({ + text: "UPDATE TABLE users SET handle = $1 WHERE id = $2", + values: [handle, this.id], + }); + return result.handle; + } + + /** + * Sets the username of the user + * @param name + */ + public async setName(name: string): Promise { + const result = await queryHelper.first({ + text: "UPDATE TABLE users SET name = $1 WHERE id = $2", + values: [name, this.id], + }); + return result.name; + } +} diff --git a/src/lib/dataaccess/User.ts b/src/lib/dataaccess/User.ts index bb680c4..394c062 100644 --- a/src/lib/dataaccess/User.ts +++ b/src/lib/dataaccess/User.ts @@ -17,18 +17,6 @@ export class User extends DataObject { return this.$name; } - /** - * Sets the username of the user - * @param name - */ - public async setName(name: string): Promise { - const result = await queryHelper.first({ - text: "UPDATE TABLE users SET name = $1 WHERE id = $2", - values: [name, this.id], - }); - return result.name; - } - /** * The unique handle of the user. */ @@ -37,17 +25,6 @@ export class User extends DataObject { return this.$handle; } - /** - * Updates the handle of the user - */ - public async setHandle(handle: string): Promise { - const result = await queryHelper.first({ - text: "UPDATE TABLE users SET handle = $1 WHERE id = $2", - values: [handle, this.id], - }); - return result.handle; - } - /** * The email of the user */ @@ -56,18 +33,6 @@ export class User extends DataObject { return this.$email; } - /** - * Sets the email of the user - * @param email - */ - public async setEmail(email: string): Promise { - const result = await queryHelper.first({ - text: "UPDATE TABLE users SET email = $1 WHERE users.id = $2 RETURNING email", - values: [email, this.id], - }); - return result.email; - } - /** * The number of greenpoints of the user */ @@ -76,18 +41,6 @@ export class User extends DataObject { return this.$greenpoints; } - /** - * Sets the greenpoints of a user. - * @param points - */ - public async setGreenpoints(points: number): Promise { - const result = await queryHelper.first({ - text: "UPDATE users SET greenpoints = $1 WHERE id = $2 RETURNING greenpoints", - values: [points, this.id], - }); - return result.greenpoints; - } - /** * The date the user joined the platform */ diff --git a/src/lib/dataaccess/index.ts b/src/lib/dataaccess/index.ts index 1d610ef..5696547 100644 --- a/src/lib/dataaccess/index.ts +++ b/src/lib/dataaccess/index.ts @@ -1,6 +1,7 @@ import {Pool} from "pg"; import globals from "../globals"; import {QueryHelper} from "../QueryHelper"; +import {Profile} from "./Profile"; import {User} from "./User"; const config = globals.config; @@ -16,7 +17,18 @@ const dbClient: Pool = new Pool({ }); export const queryHelper = new QueryHelper(dbClient, tableCreationFile, tableUpdateFile); +/** + * Generates a new handle from the username and a base64 string of the current time. + * @param username + */ +function generateHandle(username: string) { + return `${username}.${Buffer.from(Date.now().toString()).toString("base64")}`; +} + namespace dataaccess { + + export const pool: Pool = dbClient; + /** * Initializes everything that needs to be initialized asynchronous. */ @@ -45,6 +57,33 @@ namespace dataaccess { return new User(result.id, result); } + /** + * Returns the user by email and password + * @param email + * @param password + */ + export async function getUserByLogin(email: string, password: string) { + const result = await this.queryHelper.first({ + text: "SELECT * FROM users WHERE email = $1 AND password = $2", + values: [email, password], + }); + return new Profile(result.id, result); + } + + /** + * Registers a user with a username and password returning a user + * @param username + * @param email + * @param password + */ + export async function registerUser(username: string, email: string, password: string) { + const result = await this.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); + } + /** * Enum representing the types of votes that can be performed on a post. */ diff --git a/src/routes/index.ts b/src/routes/index.ts index 84cc7c5..e512ee6 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -28,9 +28,9 @@ namespace routes { * @param request * @param response */ - export const resolvers = async (request: any, response: any): Promise => { + export async function resolvers(request: any, response: any): Promise { return homeRoute.resolver(request, response); - }; + } /** * Assigns the io listeners or namespaces to the routes diff --git a/src/sql/create-tables.sql b/src/sql/create-tables.sql index 7c1c97a..faf2137 100644 --- a/src/sql/create-tables.sql +++ b/src/sql/create-tables.sql @@ -1,3 +1,10 @@ +CREATE TABLE IF NOT EXISTS "user_sessions" ( + "sid" varchar NOT NULL COLLATE "default", + "sess" json NOT NULL, + "expire" timestamp(6) NOT NULL, + PRIMARY KEY ("sid") NOT DEFERRABLE INITIALLY IMMEDIATE +) WITH (OIDS=FALSE); + CREATE TABLE IF NOT EXISTS users ( id SERIAL PRIMARY KEY, name varchar(128) NOT NULL, From 1b9e7e1a66d22714e2c918f45c7e8726e30d3f65 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Mon, 23 Sep 2019 16:16:40 +0200 Subject: [PATCH 20/40] Fixed maxAge value of config --- src/app.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app.ts b/src/app.ts index 6758970..3a60de6 100644 --- a/src/app.ts +++ b/src/app.ts @@ -36,7 +36,7 @@ class App { const appSession = session({ cookie: { - maxAge: Number(globals.config.session.cookieMaxAge), + maxAge: Number(globals.config.session.cookieMaxAge) || 604800000, secure: "auto", }, resave: false, From 6121aff29f8429fe35be315d292b2708c13575f9 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Mon, 23 Sep 2019 21:18:09 +0200 Subject: [PATCH 21/40] Added chat data objects --- package-lock.json | 41 +++++------------- src/lib/dataaccess/ChatMessage.ts | 7 ++++ src/lib/dataaccess/Chatroom.ts | 51 +++++++++++++++++++++++ src/public/graphql/schema.graphql | 3 ++ src/views/home/index.pug | 2 +- src/views/{home => includes}/stylebar.pug | 0 src/views/login/index.pug | 2 +- src/views/login/stylebar.pug | 2 - src/views/register/index.pug | 2 +- src/views/register/stylebar.pug | 2 - 10 files changed, 75 insertions(+), 37 deletions(-) create mode 100644 src/lib/dataaccess/ChatMessage.ts create mode 100644 src/lib/dataaccess/Chatroom.ts rename src/views/{home => includes}/stylebar.pug (100%) delete mode 100644 src/views/login/stylebar.pug delete mode 100644 src/views/register/stylebar.pug diff --git a/package-lock.json b/package-lock.json index 99cd62f..c82d4b5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2526,8 +2526,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -2548,14 +2547,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" @@ -2570,20 +2567,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", @@ -2700,8 +2694,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -2713,7 +2706,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -2728,7 +2720,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -2736,14 +2727,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" @@ -2762,7 +2751,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -2843,8 +2831,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -2856,7 +2843,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -2942,8 +2928,7 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -2979,7 +2964,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", @@ -2999,7 +2983,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -3043,14 +3026,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 } } }, diff --git a/src/lib/dataaccess/ChatMessage.ts b/src/lib/dataaccess/ChatMessage.ts new file mode 100644 index 0000000..5930679 --- /dev/null +++ b/src/lib/dataaccess/ChatMessage.ts @@ -0,0 +1,7 @@ +import {Chatroom} from "./Chatroom"; +import {User} from "./User"; + +export class ChatMessage { + constructor(public author: User, public chat: Chatroom, public timestamp: number, public content: string) { + } +} diff --git a/src/lib/dataaccess/Chatroom.ts b/src/lib/dataaccess/Chatroom.ts new file mode 100644 index 0000000..c6fe144 --- /dev/null +++ b/src/lib/dataaccess/Chatroom.ts @@ -0,0 +1,51 @@ +import {ChatMessage} from "./ChatMessage"; +import {queryHelper} from "./index"; +import {User} from "./User"; + +export class Chatroom { + + constructor(private id: number) {} + + /** + * Returns all members of a chatroom. + */ + public async members(): Promise { + const result = await queryHelper.all({ + 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) { + chatMembers.push(new User(row)); + } + return chatMembers; + } + + /** + * Returns messages of the chat + * @param limit - the limit of messages to return + * @param offset - the offset of messages to return + * @param containing - filter by containing + */ + public async messages(limit?: number, offset?: number, containing?: string) { + const lim = limit || 16; + const offs = offset || 0; + + const result = await queryHelper.all({ + text: "SELECT * FROM chat_messages WHERE chat = $1 ORDER BY created_at DESC LIMIT $2 OFFSET $3", + values: [this.id, lim, offs], + }); + + const messages = []; + for (const row of result) { + messages.push(new ChatMessage(new User(row.author), this, row.timestamp, row.content)); + } + if (containing) { + return messages.filter((x) => x.content.includes(containing)); + } else { + return messages; + } + } +} diff --git a/src/public/graphql/schema.graphql b/src/public/graphql/schema.graphql index ff60fd3..c5b03c3 100644 --- a/src/public/graphql/schema.graphql +++ b/src/public/graphql/schema.graphql @@ -19,6 +19,9 @@ type Query { } type Mutation { + "Accepts the usage of cookies." + acceptCookies: Boolean + "Upvote/downvote a Post" vote(postId: ID!, type: [VoteType!]!): Boolean diff --git a/src/views/home/index.pug b/src/views/home/index.pug index 0fc45ad..7da0198 100644 --- a/src/views/home/index.pug +++ b/src/views/home/index.pug @@ -4,6 +4,6 @@ html include ../includes/head body div#content - include stylebar + include ../includes/stylebar include feed include friends diff --git a/src/views/home/stylebar.pug b/src/views/includes/stylebar.pug similarity index 100% rename from src/views/home/stylebar.pug rename to src/views/includes/stylebar.pug diff --git a/src/views/login/index.pug b/src/views/login/index.pug index 41fe877..81c7585 100644 --- a/src/views/login/index.pug +++ b/src/views/login/index.pug @@ -4,5 +4,5 @@ html include ../includes/head body div#content - include stylebar + include ../includes/stylebar include login diff --git a/src/views/login/stylebar.pug b/src/views/login/stylebar.pug deleted file mode 100644 index c6c36e3..0000000 --- a/src/views/login/stylebar.pug +++ /dev/null @@ -1,2 +0,0 @@ -div.stylebar - h1 Greenvironment diff --git a/src/views/register/index.pug b/src/views/register/index.pug index d8e9dd1..7ade6a7 100644 --- a/src/views/register/index.pug +++ b/src/views/register/index.pug @@ -4,5 +4,5 @@ html include ../includes/head body div#content - include stylebar + include ../includes/stylebar include register diff --git a/src/views/register/stylebar.pug b/src/views/register/stylebar.pug deleted file mode 100644 index c6c36e3..0000000 --- a/src/views/register/stylebar.pug +++ /dev/null @@ -1,2 +0,0 @@ -div.stylebar - h1 Greenvironment From e6d219126683dad9df74e92971e36d8746f45c89 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Tue, 24 Sep 2019 14:20:40 +0200 Subject: [PATCH 22/40] Fixed graphql - fixed graphql resolution - added login, logout, acceptCookies implementation --- package-lock.json | 11 ++++++++++ package.json | 2 ++ src/app.ts | 20 +++++++++++------- src/lib/dataaccess/index.ts | 14 ++++++++----- src/lib/globals.ts | 7 ++++++- src/public/graphql/schema.graphql | 6 ++++++ src/routes/home.ts | 35 +++++++++++++++++++++++++++++-- src/routes/index.ts | 2 +- 8 files changed, 81 insertions(+), 16 deletions(-) diff --git a/package-lock.json b/package-lock.json index c82d4b5..3689e96 100644 --- a/package-lock.json +++ b/package-lock.json @@ -159,6 +159,12 @@ "integrity": "sha512-UoCovaxbJIxagCvVfalfK7YaNhmxj3BQFRQ2RHQKLiu+9wNXhJnlbspsLHt/YQM99IaLUUFJNzCwzc6W0ypMeQ==", "dev": true }, + "@types/http-status": { + "version": "0.2.30", + "resolved": "https://registry.npmjs.org/@types/http-status/-/http-status-0.2.30.tgz", + "integrity": "sha512-wcBc5XEOMmhuoWfNhwnpw8+tVAsueUeARxCTcRQ0BCN5V/dyKQBJNWdxmvcZW5IJWoeU47UWQ+ACCg48KKnqyA==", + "dev": true + }, "@types/js-yaml": { "version": "3.12.1", "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-3.12.1.tgz", @@ -3664,6 +3670,11 @@ "sshpk": "^1.7.0" } }, + "http-status": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/http-status/-/http-status-1.3.2.tgz", + "integrity": "sha512-vR1YTaDyi2BukI0UiH01xy92oiZi4in7r0dmSPnrZg72Vu1SzyOLalwWP5NUk1rNiB2L+XVK2lcSVOqaertX8A==" + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", diff --git a/package.json b/package.json index 664e5a3..7dcee4a 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "@types/express-socket.io-session": "^1.3.2", "@types/fs-extra": "^8.0.0", "@types/graphql": "^14.2.3", + "@types/http-status": "^0.2.30", "@types/js-yaml": "^3.12.1", "@types/node": "^12.7.2", "@types/pg": "^7.11.0", @@ -54,6 +55,7 @@ "g": "^2.0.1", "graphql": "^14.4.2", "graphql-import": "^0.7.1", + "http-status": "^1.3.2", "js-yaml": "^3.13.1", "pg": "^7.12.1", "pug": "^2.0.4", diff --git a/src/app.ts b/src/app.ts index 3a60de6..7ea9b9b 100644 --- a/src/app.ts +++ b/src/app.ts @@ -14,6 +14,8 @@ import dataaccess from "./lib/dataaccess"; import globals from "./lib/globals"; import routes from "./routes"; +const logger = globals.logger; + const PgSession = connectPgSimple(session); class App { @@ -40,7 +42,7 @@ class App { secure: "auto", }, resave: false, - saveUninitialized: true, // TODO: Set to false and only save when accepted by user. + saveUninitialized: false, secret: globals.config.session.secret, store: new PgSession({ pool: dataaccess.pool, @@ -60,14 +62,18 @@ class App { this.app.use(express.static(path.join(__dirname, "public"))); this.app.use(cookieParser()); this.app.use(appSession); + this.app.use((req, res, next) => { + logger.verbose(`${req.method} ${req.url}`); + next(); + }); this.app.use(routes.router); - this.app.use("/graphql", graphqlHTTP(async (request, response) => { + this.app.use("/graphql", graphqlHTTP((request, response) => { return { // @ts-ignore all context: {session: request.session}, graphiql: true, - rootValue: await routes.resolvers(request, response), - schema: buildSchema(importSchema("./public/graphql/schema.graphql")), + rootValue: routes.resolvers(request, response), + schema: buildSchema(importSchema(path.join(__dirname, "./public/graphql/schema.graphql"))), }; })); } @@ -77,11 +83,11 @@ class App { */ public start() { if (globals.config.server.port) { - globals.logger.info(`Starting server...`); + logger.info(`Starting server...`); this.app.listen(globals.config.server.port); - globals.logger.info(`Server running on port ${globals.config.server.port}`); + logger.info(`Server running on port ${globals.config.server.port}`); } else { - globals.logger.error("No port specified in the config." + + logger.error("No port specified in the config." + "Please configure a port in the config.yaml."); } } diff --git a/src/lib/dataaccess/index.ts b/src/lib/dataaccess/index.ts index 5696547..6a9c854 100644 --- a/src/lib/dataaccess/index.ts +++ b/src/lib/dataaccess/index.ts @@ -50,7 +50,7 @@ namespace dataaccess { * @param userHandle */ export async function getUserByHandle(userHandle: string) { - const result = await this.queryHelper.first({ + const result = await queryHelper.first({ text: "SELECT * FROM users WHERE users.handle = $1", values: [userHandle], }); @@ -62,12 +62,16 @@ namespace dataaccess { * @param email * @param password */ - export async function getUserByLogin(email: string, password: string) { - const result = await this.queryHelper.first({ + 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], }); - return new Profile(result.id, result); + if (result) { + return new Profile(result.id, result); + } else { + return null; + } } /** @@ -77,7 +81,7 @@ namespace dataaccess { * @param password */ export async function registerUser(username: string, email: string, password: string) { - const result = await this.queryHelper.first({ + 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], }); diff --git a/src/lib/globals.ts b/src/lib/globals.ts index a85ffea..3457fa5 100644 --- a/src/lib/globals.ts +++ b/src/lib/globals.ts @@ -15,6 +15,10 @@ const defaultConfig = __dirname + "/../default-config.yaml"; // ensure that the config exists by copying the default config. if (!(fsx.pathExistsSync(configPath))) { fsx.copySync(defaultConfig, configPath); +} else { + const conf = yaml.safeLoad(fsx.readFileSync(configPath, "utf-8")); + const defConf = yaml.safeLoad(fsx.readFileSync(defaultConfig, "utf-8")); + fsx.writeFileSync(configPath, yaml.safeDump(Object.assign(defConf, conf))); } /** @@ -28,10 +32,11 @@ namespace globals { format: winston.format.combine( winston.format.timestamp(), winston.format.colorize(), - winston.format.printf(({ level, message, label, timestamp }) => { + winston.format.printf(({ level, message, timestamp }) => { return `${timestamp} ${level}: ${message}`; }), ), + level: "debug", }), ], }); diff --git a/src/public/graphql/schema.graphql b/src/public/graphql/schema.graphql index c5b03c3..ea9c4eb 100644 --- a/src/public/graphql/schema.graphql +++ b/src/public/graphql/schema.graphql @@ -22,6 +22,12 @@ type Mutation { "Accepts the usage of cookies." acceptCookies: Boolean + "Login of the user. The passwordHash should be a sha512 hash of the password." + login(email: String, passwordHash: String): User + + "Logout of the user." + logout: Boolean + "Upvote/downvote a Post" vote(postId: ID!, type: [VoteType!]!): Boolean diff --git a/src/routes/home.ts b/src/routes/home.ts index 13bb254..2af5947 100644 --- a/src/routes/home.ts +++ b/src/routes/home.ts @@ -1,5 +1,9 @@ import {Router} from "express"; +import {GraphQLError} from "graphql"; +import * as status from "http-status"; +import {constants} from "http2"; import {Server} from "socket.io"; +import dataaccess from "../lib/dataaccess"; import Route from "../lib/Route"; /** @@ -36,9 +40,36 @@ class HomeRoute extends Route { * @param req - the request object * @param res - the response object */ - public async resolver(req: any, res: any): Promise { + public resolver(req: any, res: any): any { return { - // TODO: Define grapql resolvers + acceptCookies() { + req.session.cookiesAccepted = true; + return true; + }, + async login(args: any) { + if (args.email && args.passwordHash) { + const user = await dataaccess.getUserByLogin(args.email, args.passwordHash); + if (user && user.id) { + req.session.userId = user.id; + return user; + } else { + res.status(status.BAD_REQUEST); + return new GraphQLError("Invalid login data."); + } + } else { + res.status(status.BAD_REQUEST); + return new GraphQLError("No email or password given."); + } + }, + logout() { + if (req.session.user) { + delete req.session.user; + return true; + } else { + res.status(status.UNAUTHORIZED); + return new GraphQLError("User is not logged in."); + } + }, }; } diff --git a/src/routes/index.ts b/src/routes/index.ts index e512ee6..eb65750 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -28,7 +28,7 @@ namespace routes { * @param request * @param response */ - export async function resolvers(request: any, response: any): Promise { + export function resolvers(request: any, response: any): Promise { return homeRoute.resolver(request, response); } From 1d97e3305efc8a87dd815b8d267235d16e852aca Mon Sep 17 00:00:00 2001 From: Trivernis Date: Tue, 24 Sep 2019 18:06:04 +0200 Subject: [PATCH 23/40] API implementation --- package-lock.json | 41 +++++++++++++++++------- src/lib/QueryHelper.ts | 2 +- src/lib/dataaccess/DataObject.ts | 6 ++-- src/lib/dataaccess/Post.ts | 52 ++++++++++++++++++++++++------- src/lib/dataaccess/User.ts | 31 ++++++++++++------ src/lib/dataaccess/index.ts | 15 +++++++++ src/public/graphql/schema.graphql | 19 ++++++----- src/routes/home.ts | 38 ++++++++++++++++++++++ src/sql/create-tables.sql | 2 +- src/sql/update-tables.sql | 9 ++++-- 10 files changed, 169 insertions(+), 46 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3689e96..64e04b1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2532,7 +2532,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -2553,12 +2554,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2573,17 +2576,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -2700,7 +2706,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -2712,6 +2719,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -2726,6 +2734,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -2733,12 +2742,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -2757,6 +2768,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -2837,7 +2849,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -2849,6 +2862,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -2934,7 +2948,8 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -2970,6 +2985,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -2989,6 +3005,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -3032,12 +3049,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, diff --git a/src/lib/QueryHelper.ts b/src/lib/QueryHelper.ts index 5970f70..95b7a55 100644 --- a/src/lib/QueryHelper.ts +++ b/src/lib/QueryHelper.ts @@ -133,7 +133,7 @@ export class QueryHelper { try { return await this.pool.query(query); } catch (err) { - logger.debug(`Error on query "${query}".`); + logger.debug(`Error on query "${JSON.stringify(query)}".`); logger.error(`Sql query failed: ${err}`); logger.verbose(err.stack); return { diff --git a/src/lib/dataaccess/DataObject.ts b/src/lib/dataaccess/DataObject.ts index 1890425..019bcc3 100644 --- a/src/lib/dataaccess/DataObject.ts +++ b/src/lib/dataaccess/DataObject.ts @@ -12,9 +12,9 @@ export abstract class DataObject { /** * Loads data from the database if data has not been loaded */ - protected loadDataIfNotExists() { - if (this.dataLoaded) { - this.loadData(); + protected async loadDataIfNotExists() { + if (!this.dataLoaded) { + await this.loadData(); } } } diff --git a/src/lib/dataaccess/Post.ts b/src/lib/dataaccess/Post.ts index b4193c4..08f9c4b 100644 --- a/src/lib/dataaccess/Post.ts +++ b/src/lib/dataaccess/Post.ts @@ -5,8 +5,6 @@ import {User} from "./User"; export class Post extends DataObject { public readonly id: number; - private $upvotes: number; - private $downvotes: number; private $createdAt: string; private $content: string; private $author: number; @@ -16,23 +14,29 @@ export class Post extends DataObject { * Returns the upvotes of a post. */ public async upvotes(): Promise { - this.loadDataIfNotExists(); - return this.$upvotes; + const result = await queryHelper.first({ + text: "SELECT COUNT(*) count FROM votes WHERE item_id = $1 AND vote_type = 'UPVOTE'", + values: [this.id], + }); + return result.count; } /** * Returns the downvotes of the post */ public async downvotes(): Promise { - this.loadDataIfNotExists(); - return this.$downvotes; + const result = await queryHelper.first({ + 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 { - this.loadDataIfNotExists(); + await this.loadDataIfNotExists(); return this.$content; } @@ -40,7 +44,7 @@ export class Post extends DataObject { * The date the post was created at. */ public async createdAt(): Promise { - this.loadDataIfNotExists(); + await this.loadDataIfNotExists(); return this.$createdAt; } @@ -48,7 +52,7 @@ export class Post extends DataObject { * The autor of the post. */ public async author(): Promise { - this.loadDataIfNotExists(); + await this.loadDataIfNotExists(); return new User(this.$author); } @@ -77,6 +81,34 @@ export class Post extends DataObject { } } + /** + * Performs a vote on a post. + * @param userId + * @param type + */ + public async vote(userId: number, type: dataaccess.VoteType): Promise { + const uVote = await this.userVote(userId); + if (uVote === type) { + await queryHelper.first({ + text: "DELETE FROM votes WHERE item_id = $1 AND user_id = $2", + values: [this.id, userId], + }); + } else { + if (uVote) { + await queryHelper.first({ + text: "UPDATE votes SET vote_type = $1 WHERE user_id = $1 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. */ @@ -93,8 +125,6 @@ export class Post extends DataObject { if (result) { this.$author = result.author; this.$content = result.content; - this.$downvotes = result.downvotes; - this.$upvotes = result.upvotes; this.$createdAt = result.created_at; this.$type = result.type; this.dataLoaded = true; diff --git a/src/lib/dataaccess/User.ts b/src/lib/dataaccess/User.ts index 394c062..de5b25e 100644 --- a/src/lib/dataaccess/User.ts +++ b/src/lib/dataaccess/User.ts @@ -13,7 +13,7 @@ export class User extends DataObject { * The name of the user */ public async name(): Promise { - this.loadDataIfNotExists(); + await this.loadDataIfNotExists(); return this.$name; } @@ -21,7 +21,7 @@ export class User extends DataObject { * The unique handle of the user. */ public async handle(): Promise { - this.loadDataIfNotExists(); + await this.loadDataIfNotExists(); return this.$handle; } @@ -29,7 +29,7 @@ export class User extends DataObject { * The email of the user */ public async email(): Promise { - this.loadDataIfNotExists(); + await this.loadDataIfNotExists(); return this.$email; } @@ -37,25 +37,38 @@ export class User extends DataObject { * The number of greenpoints of the user */ public async greenpoints(): Promise { - this.loadDataIfNotExists(); + await this.loadDataIfNotExists(); return this.$greenpoints; } + /** + * Returns the number of posts the user created + */ + public async numberOfPosts(): Promise { + const result = await queryHelper.first({ + 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 { - this.loadDataIfNotExists(); + await this.loadDataIfNotExists(); return new Date(this.$joinedAt); } /** * Returns all posts for a user. */ - public async posts(): Promise { + public async posts({first, offset}: {first: number, offset: number}): Promise { + first = first || 10; + offset = offset || 0; const result = await queryHelper.all({ - text: "SELECT * FROM posts WHERE author = $1", - values: [this.id], + text: "SELECT * FROM posts WHERE author = $1 ORDER BY created_at DESC LIMIT $2 OFFSET $3", + values: [this.id, first, offset], }); const posts = []; @@ -74,7 +87,7 @@ export class User extends DataObject { result = this.row; } else { result = await queryHelper.first({ - text: "SELECT * FROM users WHERE user.id = $1", + text: "SELECT * FROM users WHERE users.id = $1", values: [this.id], }); } diff --git a/src/lib/dataaccess/index.ts b/src/lib/dataaccess/index.ts index 6a9c854..a0e38f5 100644 --- a/src/lib/dataaccess/index.ts +++ b/src/lib/dataaccess/index.ts @@ -1,6 +1,7 @@ import {Pool} from "pg"; import globals from "../globals"; import {QueryHelper} from "../QueryHelper"; +import {Post} from "./Post"; import {Profile} from "./Profile"; import {User} from "./User"; @@ -88,6 +89,20 @@ namespace dataaccess { return new Profile(result.id, result); } + /** + * Creates a post + * @param content + * @param authorId + * @param type + */ + export async function createPost(content: string, authorId: number, type: string) { + const result = await queryHelper.first({ + text: "INSERT INTO posts (content, author, type) VALUES ($1, $2, $3) RETURNING *", + values: [content, authorId, type], + }); + return new Post(result.id, result); + } + /** * Enum representing the types of votes that can be performed on a post. */ diff --git a/src/public/graphql/schema.graphql b/src/public/graphql/schema.graphql index ea9c4eb..eba344a 100644 --- a/src/public/graphql/schema.graphql +++ b/src/public/graphql/schema.graphql @@ -2,6 +2,9 @@ type Query { "returns the user object for a given user id" getUser(userId: ID): User + "returns the logged in user" + getSelf: User + "returns the post object for a post id" getPost(postId: ID): Post @@ -25,11 +28,14 @@ type Mutation { "Login of the user. The passwordHash should be a sha512 hash of the password." login(email: String, passwordHash: String): User + "Registers the user." + register(username: String, email: String, passwordHash: String): User + "Logout of the user." logout: Boolean "Upvote/downvote a Post" - vote(postId: ID!, type: [VoteType!]!): Boolean + vote(postId: ID!, type: VoteType!): VoteType "Report the post" report(postId: ID!): Boolean @@ -47,7 +53,7 @@ type Mutation { sendMessage(chatId: ID!, content: String!): Boolean "create the post" - createPost(text: String, picture: String, tags: [String]): Boolean + createPost(content: String!): Boolean "delete the post for a given post id" deletePost(postId: ID!): Boolean @@ -56,7 +62,7 @@ type Mutation { "represents a single user account" type User { "url for the Profile picture of the User" - profilePicture: String! + profilePicture: String "name of the User" name: String! @@ -71,13 +77,10 @@ type User { numberOfPosts: Int "returns a given number of posts of a user" - getAllPosts(first: Int=10, offset: Int): [Post] + posts(first: Int=10, offset: Int): [Post] "creation date of the user account" - joinedDate: String! - - "returns chats the user pinned" - pinnedChats: [ChatRoom] + joinedAt: String! "returns all friends of the user" friends: [User] diff --git a/src/routes/home.ts b/src/routes/home.ts index 2af5947..1ed5115 100644 --- a/src/routes/home.ts +++ b/src/routes/home.ts @@ -4,6 +4,8 @@ import * as status from "http-status"; import {constants} from "http2"; import {Server} from "socket.io"; import dataaccess from "../lib/dataaccess"; +import {Post} from "../lib/dataaccess/Post"; +import {Profile} from "../lib/dataaccess/Profile"; import Route from "../lib/Route"; /** @@ -42,6 +44,14 @@ class HomeRoute extends Route { */ public resolver(req: any, res: any): any { return { + getSelf() { + if (req.session.userId) { + return new Profile(req.session.userId); + } else { + res.status(status.UNAUTHORIZED); + return new GraphQLError("Not logged in"); + } + }, acceptCookies() { req.session.cookiesAccepted = true; return true; @@ -70,6 +80,34 @@ class HomeRoute extends Route { return new GraphQLError("User is not logged in."); } }, + async register(args: any) { + if (args.username && args.email && args.passwordHash) { + const user = await dataaccess.registerUser(args.username, args.email, args.passwordHash); + if (user) { + req.session.userId = user.id; + return user; + } else { + res.status(status.INTERNAL_SERVER_ERROR); + return new GraphQLError("Failed to create account."); + } + } else { + res.status(status.BAD_REQUEST); + return new GraphQLError("No username, email or password given."); + } + }, + async vote(args: any) { + if (args.postId && args.type) { + if (req.session.userId) { + return await (new Post(args.postId)).vote(req.session.userId, args.type); + } else { + res.status(status.UNAUTHORIZED); + return new GraphQLError("Not logged in."); + } + } else { + res.status(status.BAD_REQUEST); + return new GraphQLError("No postId or type given."); + } + }, }; } diff --git a/src/sql/create-tables.sql b/src/sql/create-tables.sql index faf2137..17f6e74 100644 --- a/src/sql/create-tables.sql +++ b/src/sql/create-tables.sql @@ -22,7 +22,7 @@ CREATE TABLE IF NOT EXISTS posts ( created_at TIMESTAMP DEFAULT now(), content text, author SERIAL REFERENCES users (id) ON DELETE CASCADE, - type varchar(16) NOT NULL + type varchar(16) NOT NULL DEFAULT 'MISC' ); CREATE TABLE IF NOT EXISTS votes ( diff --git a/src/sql/update-tables.sql b/src/sql/update-tables.sql index 52ce2f6..13bc681 100644 --- a/src/sql/update-tables.sql +++ b/src/sql/update-tables.sql @@ -1,3 +1,8 @@ ALTER TABLE IF EXISTS votes - ADD COLUMN IF NOT EXISTS vote_type varchar(8) DEFAULT 'upvote', - ALTER COLUMN vote_type SET DEFAULT 'upvote'; + ADD COLUMN IF NOT EXISTS vote_type varchar(8) DEFAULT 'UPVOTE', + ALTER COLUMN vote_type SET DEFAULT 'UPVOTE'; + +ALTER TABLE IF EXISTS posts + ALTER COLUMN type SET DEFAULT 'MISC', + DROP COLUMN IF EXISTS upvotes, + DROP COLUMN IF EXISTS downvotes; From 84b683db11e8b98dd97c509279605c6d03beb241 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Tue, 24 Sep 2019 18:54:50 +0200 Subject: [PATCH 24/40] Removed frontend part and added graphql implementation --- gulpfile.js | 2 +- package-lock.json | 6 ++++ package.json | 1 + src/lib/dataaccess/index.ts | 14 ++++++++- src/public/graphql/schema.graphql | 7 +---- src/public/javascripts/main.js | 0 src/routes/home.ts | 49 ++++++++++++++++++------------- src/views/home/feed.pug | 13 -------- src/views/home/friends.pug | 1 - src/views/home/index.pug | 9 ------ src/views/includes/head.pug | 1 - src/views/includes/stylebar.pug | 2 -- src/views/login/index.pug | 8 ----- src/views/login/login.pug | 6 ---- src/views/register/index.pug | 8 ----- src/views/register/register.pug | 8 ----- 16 files changed, 51 insertions(+), 84 deletions(-) delete mode 100644 src/public/javascripts/main.js delete mode 100644 src/views/home/feed.pug delete mode 100644 src/views/home/friends.pug delete mode 100644 src/views/home/index.pug delete mode 100644 src/views/includes/head.pug delete mode 100644 src/views/includes/stylebar.pug delete mode 100644 src/views/login/index.pug delete mode 100644 src/views/login/login.pug delete mode 100644 src/views/register/index.pug delete mode 100644 src/views/register/register.pug diff --git a/gulpfile.js b/gulpfile.js index c1abd43..a5853d1 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -3,7 +3,7 @@ const sass = require('gulp-sass'); const ts = require('gulp-typescript'); const minify = require('gulp-minify'); const del = require('delete'); - +const gulp = require('gulp'); function clearDist(cb) { del('dist/*', cb); diff --git a/package-lock.json b/package-lock.json index 64e04b1..34608d6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3348,6 +3348,12 @@ } } }, + "gulp-angular": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/gulp-angular/-/gulp-angular-0.1.2.tgz", + "integrity": "sha1-ljV2ul7qoDZqMf6l7S7AHvC0O3g=", + "dev": true + }, "gulp-minify": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/gulp-minify/-/gulp-minify-3.1.0.tgz", diff --git a/package.json b/package.json index 7dcee4a..1d42af9 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "@types/winston": "^2.4.4", "delete": "^1.1.0", "gulp": "^4.0.2", + "gulp-angular": "^0.1.2", "gulp-minify": "^3.1.0", "gulp-sass": "^4.0.2", "gulp-typescript": "^5.0.1", diff --git a/src/lib/dataaccess/index.ts b/src/lib/dataaccess/index.ts index a0e38f5..c0816e6 100644 --- a/src/lib/dataaccess/index.ts +++ b/src/lib/dataaccess/index.ts @@ -95,7 +95,7 @@ namespace dataaccess { * @param authorId * @param type */ - export async function createPost(content: string, authorId: number, type: string) { + export async function createPost(content: string, authorId: number, type?: string) { const result = await queryHelper.first({ text: "INSERT INTO posts (content, author, type) VALUES ($1, $2, $3) RETURNING *", values: [content, authorId, type], @@ -103,6 +103,18 @@ namespace dataaccess { return new Post(result.id, result); } + /** + * Deletes a post + * @param postId + */ + export async function deletePost(postId: number) { + const result = await queryHelper.first({ + text: "DELETE FROM posts WHERE posts.id = $1", + values: [postId], + }); + return true; + } + /** * Enum representing the types of votes that can be performed on a post. */ diff --git a/src/public/graphql/schema.graphql b/src/public/graphql/schema.graphql index eba344a..bb369f5 100644 --- a/src/public/graphql/schema.graphql +++ b/src/public/graphql/schema.graphql @@ -91,11 +91,9 @@ type User { "represents a single user post" type Post { - "returns the path to the posts picture if it has one" - picture: String "returns the text of the post" - text: String + content: String "upvotes of the Post" upvotes: Int! @@ -111,9 +109,6 @@ type Post { "returns the type of vote the user performed on the post" userVote: VoteType - - "returns the tags of the post" - tags: [String] } "represents a request of any type" diff --git a/src/public/javascripts/main.js b/src/public/javascripts/main.js deleted file mode 100644 index e69de29..0000000 diff --git a/src/routes/home.ts b/src/routes/home.ts index 1ed5115..2a88931 100644 --- a/src/routes/home.ts +++ b/src/routes/home.ts @@ -1,7 +1,6 @@ import {Router} from "express"; import {GraphQLError} from "graphql"; import * as status from "http-status"; -import {constants} from "http2"; import {Server} from "socket.io"; import dataaccess from "../lib/dataaccess"; import {Post} from "../lib/dataaccess/Post"; @@ -18,7 +17,6 @@ class HomeRoute extends Route { constructor() { super(); this.router = Router(); - this.configure(); } /** @@ -56,7 +54,7 @@ class HomeRoute extends Route { req.session.cookiesAccepted = true; return true; }, - async login(args: any) { + async login(args: {email: string, passwordHash: string}) { if (args.email && args.passwordHash) { const user = await dataaccess.getUserByLogin(args.email, args.passwordHash); if (user && user.id) { @@ -80,7 +78,7 @@ class HomeRoute extends Route { return new GraphQLError("User is not logged in."); } }, - async register(args: any) { + async register(args: {username: string, email: string, passwordHash: string}) { if (args.username && args.email && args.passwordHash) { const user = await dataaccess.registerUser(args.username, args.email, args.passwordHash); if (user) { @@ -95,7 +93,7 @@ class HomeRoute extends Route { return new GraphQLError("No username, email or password given."); } }, - async vote(args: any) { + async vote(args: {postId: number, type: dataaccess.VoteType}) { if (args.postId && args.type) { if (req.session.userId) { return await (new Post(args.postId)).vote(req.session.userId, args.type); @@ -108,23 +106,34 @@ class HomeRoute extends Route { return new GraphQLError("No postId or type given."); } }, + async createPost(args: {content: string}) { + if (args.content) { + if (req.session.userId) { + return await dataaccess.createPost(args.content, req.session.userId); + } else { + res.status(status.UNAUTHORIZED); + return new GraphQLError("Not logged in."); + } + } else { + res.status(status.BAD_REQUEST); + return new GraphQLError("Can't create empty post."); + } + }, + async deletePost(args: {postId: number}) { + if (args.postId) { + const post = new Post(args.postId); + if ((await post.author()).id === req.session.userId) { + return await dataaccess.deletePost(post.id); + } else { + res.status(status.FORBIDDEN); + return new GraphQLError("User is not author of the post."); + } + } else { + return new GraphQLError("No postId given."); + } + }, }; } - - /** - * Configures the route. - */ - private configure() { - this.router.get("/", (req, res) => { - res.render("home"); - }); - this.router.get("/login", (req, res) => { - res.render("login"); - }); - this.router.get("/register", (req, res) => { - res.render("register"); - }); - } } export default HomeRoute; diff --git a/src/views/home/feed.pug b/src/views/home/feed.pug deleted file mode 100644 index e3758e1..0000000 --- a/src/views/home/feed.pug +++ /dev/null @@ -1,13 +0,0 @@ -div#feedcontainer - div.postinput - input(type=text placeholder='Post something') - button.submitbutton Submit - div.feeditem - div.itemhead - span.title Testuser - span.handle - a(href='#') @testuser - span.date 23.09.19 10:07 - p.text - | Example Test text. - | This is a test diff --git a/src/views/home/friends.pug b/src/views/home/friends.pug deleted file mode 100644 index ed36fb7..0000000 --- a/src/views/home/friends.pug +++ /dev/null @@ -1 +0,0 @@ -div#friendscontainer diff --git a/src/views/home/index.pug b/src/views/home/index.pug deleted file mode 100644 index 7da0198..0000000 --- a/src/views/home/index.pug +++ /dev/null @@ -1,9 +0,0 @@ -html - head - title Greenvironment Network - include ../includes/head - body - div#content - include ../includes/stylebar - include feed - include friends diff --git a/src/views/includes/head.pug b/src/views/includes/head.pug deleted file mode 100644 index 9e917e6..0000000 --- a/src/views/includes/head.pug +++ /dev/null @@ -1 +0,0 @@ -link(rel='stylesheet' href='stylesheets/style.css') diff --git a/src/views/includes/stylebar.pug b/src/views/includes/stylebar.pug deleted file mode 100644 index c6c36e3..0000000 --- a/src/views/includes/stylebar.pug +++ /dev/null @@ -1,2 +0,0 @@ -div.stylebar - h1 Greenvironment diff --git a/src/views/login/index.pug b/src/views/login/index.pug deleted file mode 100644 index 81c7585..0000000 --- a/src/views/login/index.pug +++ /dev/null @@ -1,8 +0,0 @@ -html - head - title Greenvironment Network Login - include ../includes/head - body - div#content - include ../includes/stylebar - include login diff --git a/src/views/login/login.pug b/src/views/login/login.pug deleted file mode 100644 index f205c95..0000000 --- a/src/views/login/login.pug +++ /dev/null @@ -1,6 +0,0 @@ -div#input-login - input(type=text placeholder='username') - input(type=text placeholder='password') - button.loginButton Login - a(href="/register" ) - | You aren´t part of greenvironment yet? - create a new account diff --git a/src/views/register/index.pug b/src/views/register/index.pug deleted file mode 100644 index 7ade6a7..0000000 --- a/src/views/register/index.pug +++ /dev/null @@ -1,8 +0,0 @@ -html - head - title Greenvironment Network Register - include ../includes/head - body - div#content - include ../includes/stylebar - include register diff --git a/src/views/register/register.pug b/src/views/register/register.pug deleted file mode 100644 index baaf1e6..0000000 --- a/src/views/register/register.pug +++ /dev/null @@ -1,8 +0,0 @@ -div#input-register - input(type=text placeholder='username') - input(type=text placeholder='email') - input(type=text placeholder='password') - input(type=text placeholder='repeat password') - button.registerButton Register - a(href="/login" ) - | You are already part of greenvironment? - login From d7f819e02e18ee73395efe9b1f44d0f46a180583 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Fri, 27 Sep 2019 20:48:16 +0200 Subject: [PATCH 25/40] More graphql implementation stuff - implemented methods to get user and post information - implemented methods to create chatrooms --- package-lock.json | 41 +++++-------------- src/app.ts | 2 +- src/lib/QueryHelper.ts | 2 +- src/lib/dataaccess/index.ts | 46 +++++++++++++++++++++ src/lib/regex.ts | 11 +++++ src/public/graphql/schema.graphql | 13 +++--- src/routes/home.ts | 68 +++++++++++++++++++++++-------- 7 files changed, 130 insertions(+), 53 deletions(-) create mode 100644 src/lib/regex.ts diff --git a/package-lock.json b/package-lock.json index 34608d6..4d87280 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2532,8 +2532,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -2554,14 +2553,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" @@ -2576,20 +2573,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", @@ -2706,8 +2700,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -2719,7 +2712,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -2734,7 +2726,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -2742,14 +2733,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" @@ -2768,7 +2757,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -2849,8 +2837,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -2862,7 +2849,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -2948,8 +2934,7 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -2985,7 +2970,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", @@ -3005,7 +2989,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -3049,14 +3032,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 } } }, diff --git a/src/app.ts b/src/app.ts index 7ea9b9b..5801771 100644 --- a/src/app.ts +++ b/src/app.ts @@ -10,7 +10,7 @@ import {importSchema} from "graphql-import"; import * as http from "http"; import * as path from "path"; import * as socketIo from "socket.io"; -import dataaccess from "./lib/dataaccess"; +import dataaccess, {queryHelper} from "./lib/dataaccess"; import globals from "./lib/globals"; import routes from "./routes"; diff --git a/src/lib/QueryHelper.ts b/src/lib/QueryHelper.ts index 95b7a55..fb1b638 100644 --- a/src/lib/QueryHelper.ts +++ b/src/lib/QueryHelper.ts @@ -54,7 +54,7 @@ export class SqlTransaction { /** * Releases the client back to the pool. */ - public async release() { + public release() { this.client.release(); } } diff --git a/src/lib/dataaccess/index.ts b/src/lib/dataaccess/index.ts index c0816e6..db72696 100644 --- a/src/lib/dataaccess/index.ts +++ b/src/lib/dataaccess/index.ts @@ -1,6 +1,7 @@ import {Pool} from "pg"; import globals from "../globals"; import {QueryHelper} from "../QueryHelper"; +import {Chatroom} from "./Chatroom"; import {Post} from "./Post"; import {Profile} from "./Profile"; import {User} from "./User"; @@ -89,6 +90,22 @@ namespace dataaccess { return new Profile(result.id, result); } + /** + * Returns a post for a given postId.s + * @param postId + */ + export async function getPost(postId: number) { + const result = await queryHelper.first({ + text: "SELECT * FROM posts WHERE id = $1", + values: [postId], + }); + if (result) { + return new Post(result.id, result); + } else { + return null; + } + } + /** * Creates a post * @param content @@ -115,6 +132,35 @@ namespace dataaccess { return true; } + /** + * Creates a chatroom containing two users + * @param members + */ + export async function createChat(...members: number[]) { + const idResult = await queryHelper.first({ + text: "INSERT INTO chats (id) values (nextval('chats_id_seq'::regclass)) RETURNING *;", + }); + const id = idResult.id; + const transaction = await queryHelper.createTransaction(); + try { + await transaction.begin(); + for (const member of members) { + await transaction.query({ + name: "chat-member-insert", + text: "INSERT INTO chat_members (ABSOLUTE chat, member) VALUES ($1, $2);", + values: [member], + }); + } + await transaction.commit(); + } catch (err) { + globals.logger.warn(`Failed to insert chatmember into database: ${err.message}`); + globals.logger.debug(err.stack); + } finally { + transaction.release(); + } + return new Chatroom(id); + } + /** * Enum representing the types of votes that can be performed on a post. */ diff --git a/src/lib/regex.ts b/src/lib/regex.ts new file mode 100644 index 0000000..1ed6c11 --- /dev/null +++ b/src/lib/regex.ts @@ -0,0 +1,11 @@ +export namespace is { + const emailRegex = /\S+?@\S+?(\.\S+?)?\.\w{2,3}(.\w{2-3})?/g + + /** + * Tests if a string is a valid email. + * @param testString + */ + export function email(testString: string) { + return emailRegex.test(testString) + } +} diff --git a/src/public/graphql/schema.graphql b/src/public/graphql/schema.graphql index bb369f5..60c32e3 100644 --- a/src/public/graphql/schema.graphql +++ b/src/public/graphql/schema.graphql @@ -1,18 +1,21 @@ type Query { - "returns the user object for a given user id" - getUser(userId: ID): User + "returns the user object for a given user id or a handle (only one required)" + getUser(userId: ID, handle: String): User "returns the logged in user" getSelf: User "returns the post object for a post id" - getPost(postId: ID): Post + getPost(postId: ID!): Post "returns the chat object for a chat id" - getChat(chatId: ID): ChatRoom + getChat(chatId: ID!): ChatRoom + + "Creates a chat between the user (and optional an other user)" + createChat(members: [ID!]): ChatRoom "returns the request object for a request id" - getRequest(requestId: ID): Request + getRequest(requestId: ID!): Request "find a post by the posted date or content" findPost(first: Int, offset: Int, text: String!, postedDate: String): [Post] diff --git a/src/routes/home.ts b/src/routes/home.ts index 2a88931..1bb397b 100644 --- a/src/routes/home.ts +++ b/src/routes/home.ts @@ -5,6 +5,7 @@ import {Server} from "socket.io"; import dataaccess from "../lib/dataaccess"; import {Post} from "../lib/dataaccess/Post"; import {Profile} from "../lib/dataaccess/Profile"; +import {is} from "../lib/regex"; import Route from "../lib/Route"; /** @@ -50,13 +51,31 @@ class HomeRoute extends Route { return new GraphQLError("Not logged in"); } }, + async getUser({userId, handle}: {userId: number, handle: string}) { + if (handle) { + return await dataaccess.getUserByHandle(handle); + } else if (userId) { + return dataaccess.getUser(userId); + } else { + res.status(status.BAD_REQUEST); + return new GraphQLError("No userId or handle provided."); + } + }, + async getPost({postId}: {postId: number}) { + if (postId) { + return await dataaccess.getPost(postId); + } else { + res.status(status.BAD_REQUEST); + return new GraphQLError("No postId given."); + } + }, acceptCookies() { req.session.cookiesAccepted = true; return true; }, - async login(args: {email: string, passwordHash: string}) { - if (args.email && args.passwordHash) { - const user = await dataaccess.getUserByLogin(args.email, args.passwordHash); + async login({email, passwordHash}: {email: string, passwordHash: string}) { + if (email && passwordHash) { + const user = await dataaccess.getUserByLogin(email, passwordHash); if (user && user.id) { req.session.userId = user.id; return user; @@ -75,12 +94,16 @@ class HomeRoute extends Route { return true; } else { res.status(status.UNAUTHORIZED); - return new GraphQLError("User is not logged in."); + return new GraphQLError("Not logged in."); } }, - async register(args: {username: string, email: string, passwordHash: string}) { - if (args.username && args.email && args.passwordHash) { - const user = await dataaccess.registerUser(args.username, args.email, args.passwordHash); + async register({username, email, passwordHash}: {username: string, email: string, passwordHash: string}) { + if (username && email && passwordHash) { + if (!is.email(email)) { + res.status(status.BAD_REQUEST); + return new GraphQLError(`'${email}' is not a valid email address!`); + } + const user = await dataaccess.registerUser(username, email, passwordHash); if (user) { req.session.userId = user.id; return user; @@ -93,10 +116,10 @@ class HomeRoute extends Route { return new GraphQLError("No username, email or password given."); } }, - async vote(args: {postId: number, type: dataaccess.VoteType}) { - if (args.postId && args.type) { + async vote({postId, type}: {postId: number, type: dataaccess.VoteType}) { + if (postId && type) { if (req.session.userId) { - return await (new Post(args.postId)).vote(req.session.userId, args.type); + return await (new Post(postId)).vote(req.session.userId, type); } else { res.status(status.UNAUTHORIZED); return new GraphQLError("Not logged in."); @@ -106,10 +129,10 @@ class HomeRoute extends Route { return new GraphQLError("No postId or type given."); } }, - async createPost(args: {content: string}) { - if (args.content) { + async createPost({content}: {content: string}) { + if (content) { if (req.session.userId) { - return await dataaccess.createPost(args.content, req.session.userId); + return await dataaccess.createPost(content, req.session.userId); } else { res.status(status.UNAUTHORIZED); return new GraphQLError("Not logged in."); @@ -119,9 +142,9 @@ class HomeRoute extends Route { return new GraphQLError("Can't create empty post."); } }, - async deletePost(args: {postId: number}) { - if (args.postId) { - const post = new Post(args.postId); + async deletePost({postId}: {postId: number}) { + if (postId) { + const post = new Post(postId); if ((await post.author()).id === req.session.userId) { return await dataaccess.deletePost(post.id); } else { @@ -132,6 +155,19 @@ class HomeRoute extends Route { return new GraphQLError("No postId given."); } }, + async createChat({members}: {members: number[]}) { + if (req.session.userId) { + const chatMembers = [req.session.userId]; + if (members) { + chatMembers.push(...members); + } + return await dataaccess.createChat(...chatMembers); + + } else { + res.status(status.UNAUTHORIZED); + return new GraphQLError("Not logged in."); + } + }, }; } } From c97d0ffe55e399ec11ee6057e36e707857b2bdd4 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Sat, 28 Sep 2019 22:08:48 +0200 Subject: [PATCH 26/40] Api functions - added chat message functions - added markdown rendering - added custom error classes --- package-lock.json | 67 +++++++++++++++++++++++++---- package.json | 6 ++- src/default-config.yaml | 4 ++ src/lib/dataaccess/ChatMessage.ts | 8 ++++ src/lib/dataaccess/Chatroom.ts | 11 +++++ src/lib/dataaccess/DataObject.ts | 8 ++++ src/lib/dataaccess/Post.ts | 9 ++++ src/lib/dataaccess/Profile.ts | 22 ++++++++++ src/lib/dataaccess/User.ts | 1 + src/lib/dataaccess/index.ts | 51 +++++++++++++++------- src/lib/errors/BaseError.ts | 13 ++++++ src/lib/errors/ChatNotFoundError.ts | 7 +++ src/lib/errors/UserNotFoundError.ts | 7 +++ src/lib/errors/graphqlErrors.ts | 9 ++++ src/lib/markdown.ts | 36 ++++++++++++++++ src/public/graphql/schema.graphql | 33 +++++++++++--- src/routes/home.ts | 45 +++++++++++++++---- 17 files changed, 298 insertions(+), 39 deletions(-) create mode 100644 src/lib/errors/BaseError.ts create mode 100644 src/lib/errors/ChatNotFoundError.ts create mode 100644 src/lib/errors/UserNotFoundError.ts create mode 100644 src/lib/errors/graphqlErrors.ts create mode 100644 src/lib/markdown.ts diff --git a/package-lock.json b/package-lock.json index 4d87280..8e55c5b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -171,6 +171,21 @@ "integrity": "sha512-SGGAhXLHDx+PK4YLNcNGa6goPf9XRWQNAUUbffkwVGGXIxmDKWyGGL4inzq2sPmExu431Ekb9aEMn9BkPqEYFA==", "dev": true }, + "@types/linkify-it": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-2.1.0.tgz", + "integrity": "sha512-Q7DYAOi9O/+cLLhdaSvKdaumWyHbm7HAk/bFwwyTuU0arR5yyCeW5GOoqt4tJTpDRxhpx9Q8kQL6vMpuw9hDSw==", + "dev": true + }, + "@types/markdown-it": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-0.0.9.tgz", + "integrity": "sha512-IFSepyZXbF4dgSvsk8EsgaQ/8Msv1I5eTL0BZ0X3iGO9jw6tCVtPG8HchIPm3wrkmGdqZOD42kE0zplVi1gYDA==", + "dev": true, + "requires": { + "@types/linkify-it": "*" + } + }, "@types/mime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz", @@ -178,9 +193,9 @@ "dev": true }, "@types/node": { - "version": "12.7.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.2.tgz", - "integrity": "sha512-dyYO+f6ihZEtNPDcWNR1fkoTDf3zAK3lAABDze3mz6POyIercH0lEUawUFXlG8xaQZmm1yEBON/4TsYv/laDYg==", + "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 }, "@types/pg": { @@ -1894,6 +1909,11 @@ "has-binary2": "~1.0.2" } }, + "entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz", + "integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==" + }, "env-variable": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/env-variable/-/env-variable-0.0.5.tgz", @@ -3329,12 +3349,6 @@ } } }, - "gulp-angular": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/gulp-angular/-/gulp-angular-0.1.2.tgz", - "integrity": "sha1-ljV2ul7qoDZqMf6l7S7AHvC0O3g=", - "dev": true - }, "gulp-minify": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/gulp-minify/-/gulp-minify-3.1.0.tgz", @@ -4214,6 +4228,14 @@ "resolve": "^1.1.7" } }, + "linkify-it": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz", + "integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==", + "requires": { + "uc.micro": "^1.0.1" + } + }, "load-json-file": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", @@ -4320,6 +4342,23 @@ "object-visit": "^1.0.0" } }, + "markdown-it": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-10.0.0.tgz", + "integrity": "sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg==", + "requires": { + "argparse": "^1.0.7", + "entities": "~2.0.0", + "linkify-it": "^2.0.0", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + } + }, + "markdown-it-emoji": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/markdown-it-emoji/-/markdown-it-emoji-1.4.0.tgz", + "integrity": "sha1-m+4OmpkKljupbfaYDE/dsF37Tcw=" + }, "matchdep": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz", @@ -4360,6 +4399,11 @@ "resolve-dir": "^1.0.0" } }, + "mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=" + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -6897,6 +6941,11 @@ "integrity": "sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g==", "dev": true }, + "uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" + }, "uglify-js": { "version": "2.8.29", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", diff --git a/package.json b/package.json index 1d42af9..52e9130 100644 --- a/package.json +++ b/package.json @@ -29,13 +29,13 @@ "@types/graphql": "^14.2.3", "@types/http-status": "^0.2.30", "@types/js-yaml": "^3.12.1", - "@types/node": "^12.7.2", + "@types/markdown-it": "0.0.9", + "@types/node": "^12.7.8", "@types/pg": "^7.11.0", "@types/socket.io": "^2.1.2", "@types/winston": "^2.4.4", "delete": "^1.1.0", "gulp": "^4.0.2", - "gulp-angular": "^0.1.2", "gulp-minify": "^3.1.0", "gulp-sass": "^4.0.2", "gulp-typescript": "^5.0.1", @@ -58,6 +58,8 @@ "graphql-import": "^0.7.1", "http-status": "^1.3.2", "js-yaml": "^3.13.1", + "markdown-it": "^10.0.0", + "markdown-it-emoji": "^1.4.0", "pg": "^7.12.1", "pug": "^2.0.4", "socket.io": "^2.2.0", diff --git a/src/default-config.yaml b/src/default-config.yaml index e232512..e607f15 100644 --- a/src/default-config.yaml +++ b/src/default-config.yaml @@ -13,3 +13,7 @@ server: session: secret: REPLACE WITH SAFE RANDOM GENERATED SECRET cookieMaxAge: 604800000‬ # 7 days + +markdown: + plugins: + - 'markdown-it-emoji' diff --git a/src/lib/dataaccess/ChatMessage.ts b/src/lib/dataaccess/ChatMessage.ts index 5930679..afbc600 100644 --- a/src/lib/dataaccess/ChatMessage.ts +++ b/src/lib/dataaccess/ChatMessage.ts @@ -1,7 +1,15 @@ +import markdown from "../markdown"; import {Chatroom} from "./Chatroom"; import {User} from "./User"; export class ChatMessage { constructor(public author: User, public chat: Chatroom, public timestamp: number, public content: string) { } + + /** + * The content rendered by markdown-it. + */ + public htmlContent(): string { + return markdown.renderInline(this.content); + } } diff --git a/src/lib/dataaccess/Chatroom.ts b/src/lib/dataaccess/Chatroom.ts index c6fe144..080a285 100644 --- a/src/lib/dataaccess/Chatroom.ts +++ b/src/lib/dataaccess/Chatroom.ts @@ -6,6 +6,17 @@ export class Chatroom { constructor(private id: number) {} + /** + * Returns if the chat exists. + */ + public async exists(): Promise { + const result = await queryHelper.first({ + text: "SELECT id FROM chats WHERE id = $1", + values: [this.id], + }); + return !!result.id; + } + /** * Returns all members of a chatroom. */ diff --git a/src/lib/dataaccess/DataObject.ts b/src/lib/dataaccess/DataObject.ts index 019bcc3..9c598ac 100644 --- a/src/lib/dataaccess/DataObject.ts +++ b/src/lib/dataaccess/DataObject.ts @@ -7,6 +7,14 @@ export abstract class DataObject { constructor(public id: number, protected row?: any) { } + /** + * Returns if the object extists by trying to load data. + */ + public async exists() { + await this.loadDataIfNotExists(); + return this.dataLoaded; + } + protected abstract loadData(): Promise; /** diff --git a/src/lib/dataaccess/Post.ts b/src/lib/dataaccess/Post.ts index 08f9c4b..dffec19 100644 --- a/src/lib/dataaccess/Post.ts +++ b/src/lib/dataaccess/Post.ts @@ -1,3 +1,4 @@ +import markdown from "../markdown"; import {DataObject} from "./DataObject"; import {queryHelper} from "./index"; import dataaccess from "./index"; @@ -40,6 +41,14 @@ export class Post extends DataObject { return this.$content; } + /** + * the content rendered by markdown-it. + */ + public async htmlContent(): Promise { + await this.loadDataIfNotExists(); + return markdown.render(this.$content); + } + /** * The date the post was created at. */ diff --git a/src/lib/dataaccess/Profile.ts b/src/lib/dataaccess/Profile.ts index a3f3a46..33eb7eb 100644 --- a/src/lib/dataaccess/Profile.ts +++ b/src/lib/dataaccess/Profile.ts @@ -1,7 +1,29 @@ +import {Chatroom} from "./Chatroom"; import {queryHelper} from "./index"; import {User} from "./User"; export class Profile extends User { + + /** + * Returns all chatrooms (with pagination). + * @param first + * @param offset + */ + public async chats({first, offset}: {first: number, offset?: number}) { + first = first || 10; + offset = offset || 0; + + const result = await queryHelper.all({ + text: "SELECT chat FROM chat_members WHERE member = $1 LIMIT $2 OFFSET $3", + values: [this.id, first, offset], + }); + if (result) { + return result.map((row) => new Chatroom(row.chat)); + } else { + return []; + } + } + /** * Sets the greenpoints of a user. * @param points diff --git a/src/lib/dataaccess/User.ts b/src/lib/dataaccess/User.ts index de5b25e..b29dfcc 100644 --- a/src/lib/dataaccess/User.ts +++ b/src/lib/dataaccess/User.ts @@ -8,6 +8,7 @@ export class User extends DataObject { private $email: string; private $greenpoints: number; private $joinedAt: string; + private $exists: boolean; /** * The name of the user diff --git a/src/lib/dataaccess/index.ts b/src/lib/dataaccess/index.ts index db72696..3db84ab 100644 --- a/src/lib/dataaccess/index.ts +++ b/src/lib/dataaccess/index.ts @@ -1,6 +1,9 @@ import {Pool} from "pg"; +import {ChatNotFoundError} from "../errors/ChatNotFoundError"; +import {UserNotFoundError} from "../errors/UserNotFoundError"; import globals from "../globals"; import {QueryHelper} from "../QueryHelper"; +import {ChatMessage} from "./ChatMessage"; import {Chatroom} from "./Chatroom"; import {Post} from "./Post"; import {Profile} from "./Profile"; @@ -27,6 +30,9 @@ function generateHandle(username: string) { return `${username}.${Buffer.from(Date.now().toString()).toString("base64")}`; } +/** + * Namespace with functions to fetch initial data for wrapping. + */ namespace dataaccess { export const pool: Pool = dbClient; @@ -39,24 +45,20 @@ namespace dataaccess { await queryHelper.createTables(); } - /** - * Returns the user by id - * @param userId - */ - export function getUser(userId: number) { - return new User(userId); - } - /** * Returns the user by handle. * @param userHandle */ - export async function getUserByHandle(userHandle: string) { + export async function getUserByHandle(userHandle: string): Promise { const result = await queryHelper.first({ text: "SELECT * FROM users WHERE users.handle = $1", values: [userHandle], }); - return new User(result.id, result); + if (result) { + return new User(result.id, result); + } else { + throw new UserNotFoundError(userHandle); + } } /** @@ -72,7 +74,7 @@ namespace dataaccess { if (result) { return new Profile(result.id, result); } else { - return null; + throw new UserNotFoundError(email); } } @@ -94,7 +96,7 @@ namespace dataaccess { * Returns a post for a given postId.s * @param postId */ - export async function getPost(postId: number) { + export async function getPost(postId: number): Promise { const result = await queryHelper.first({ text: "SELECT * FROM posts WHERE id = $1", values: [postId], @@ -112,7 +114,7 @@ namespace dataaccess { * @param authorId * @param type */ - export async function createPost(content: string, authorId: number, type?: string) { + export async function createPost(content: string, authorId: number, type?: string): Promise { const result = await queryHelper.first({ text: "INSERT INTO posts (content, author, type) VALUES ($1, $2, $3) RETURNING *", values: [content, authorId, type], @@ -124,7 +126,7 @@ namespace dataaccess { * Deletes a post * @param postId */ - export async function deletePost(postId: number) { + export async function deletePost(postId: number): Promise { const result = await queryHelper.first({ text: "DELETE FROM posts WHERE posts.id = $1", values: [postId], @@ -136,7 +138,7 @@ namespace dataaccess { * Creates a chatroom containing two users * @param members */ - export async function createChat(...members: number[]) { + export async function createChat(...members: number[]): Promise { const idResult = await queryHelper.first({ text: "INSERT INTO chats (id) values (nextval('chats_id_seq'::regclass)) RETURNING *;", }); @@ -161,6 +163,25 @@ namespace dataaccess { return new Chatroom(id); } + /** + * Sends a message into a chat. + * @param authorId + * @param chatId + * @param content + */ + export async function sendChatMessage(authorId: number, chatId: number, content: string) { + const chat = new Chatroom(chatId); + if ((await chat.exists())) { + const result = await queryHelper.first({ + text: "INSERT INTO chat_messages (chat, author, content, created_at) values ($1, $2, $3) RETURNING *", + values: [chatId, authorId, content], + }); + return new ChatMessage(new User(result.author), chat, result.timestamp, result.content); + } else { + throw new ChatNotFoundError(chatId); + } + } + /** * Enum representing the types of votes that can be performed on a post. */ diff --git a/src/lib/errors/BaseError.ts b/src/lib/errors/BaseError.ts new file mode 100644 index 0000000..f99171d --- /dev/null +++ b/src/lib/errors/BaseError.ts @@ -0,0 +1,13 @@ +import {GraphQLError} from "graphql"; + +/** + * Base error class. + */ +export class BaseError extends Error { + public readonly graphqlError: GraphQLError; + + constructor(message?: string, friendlyMessage?: string) { + super(message); + this.graphqlError = new GraphQLError(friendlyMessage || message); + } +} diff --git a/src/lib/errors/ChatNotFoundError.ts b/src/lib/errors/ChatNotFoundError.ts new file mode 100644 index 0000000..1d03525 --- /dev/null +++ b/src/lib/errors/ChatNotFoundError.ts @@ -0,0 +1,7 @@ +import {BaseError} from "./BaseError"; + +export class ChatNotFoundError extends BaseError { + constructor(chatId: number) { + super(`Chat with id ${chatId} not found.`); + } +} diff --git a/src/lib/errors/UserNotFoundError.ts b/src/lib/errors/UserNotFoundError.ts new file mode 100644 index 0000000..7869242 --- /dev/null +++ b/src/lib/errors/UserNotFoundError.ts @@ -0,0 +1,7 @@ +import {BaseError} from "./BaseError"; + +export class UserNotFoundError extends BaseError { + constructor(username: string) { + super(`User ${username} not found!`); + } +} diff --git a/src/lib/errors/graphqlErrors.ts b/src/lib/errors/graphqlErrors.ts new file mode 100644 index 0000000..9784712 --- /dev/null +++ b/src/lib/errors/graphqlErrors.ts @@ -0,0 +1,9 @@ +import {GraphQLError} from "graphql"; +import {BaseError} from "./BaseError"; + +export class NotLoggedInGqlError extends GraphQLError { + + constructor() { + super("Not logged in"); + } +} diff --git a/src/lib/markdown.ts b/src/lib/markdown.ts new file mode 100644 index 0000000..fb164ec --- /dev/null +++ b/src/lib/markdown.ts @@ -0,0 +1,36 @@ +import * as MarkdownIt from "markdown-it/lib"; +import globals from "./globals"; + +namespace markdown { + + const md = new MarkdownIt(); + + for (const pluginName of globals.config.markdown.plugins) { + try { + const plugin = require(pluginName); + if (plugin) { + md.use(plugin); + } + } catch (err) { + globals.logger.warn(`Markdown-it plugin '${pluginName}' not found!`); + } + } + + /** + * Renders the markdown string inline (without blocks). + * @param markdownString + */ + export function renderInline(markdownString: string) { + return md.renderInline(markdownString); + } + + /** + * Renders the markdown string. + * @param markdownString + */ + export function render(markdownString: string) { + return md.render(markdownString); + } +} + +export default markdown; diff --git a/src/public/graphql/schema.graphql b/src/public/graphql/schema.graphql index 60c32e3..2ff19cf 100644 --- a/src/public/graphql/schema.graphql +++ b/src/public/graphql/schema.graphql @@ -53,7 +53,7 @@ type Mutation { denyRequest(requestId: ID!): Boolean "send a message in a Chatroom" - sendMessage(chatId: ID!, content: String!): Boolean + sendMessage(chatId: ID!, content: String!): ChatMessage "create the post" createPost(content: String!): Boolean @@ -70,6 +70,9 @@ type User { "name of the User" name: String! + "returns the chatrooms the user joined." + chats(first: Int=10, offset: Int): [ChatRoom] + "unique identifier name from the User" handle: String! @@ -85,7 +88,7 @@ type User { "creation date of the user account" joinedAt: String! - "returns all friends of the user" + "all friends of the user" friends: [User] "all request for groupChats/friends/events" @@ -95,9 +98,12 @@ type User { "represents a single user post" type Post { - "returns the text of the post" + "the text of the post" content: String + "the content of the post rendered by markdown-it" + htmlContent: String + "upvotes of the Post" upvotes: Int! @@ -110,7 +116,7 @@ type Post { "date the post was created" creationDate: String! - "returns the type of vote the user performed on the post" + "the type of vote the user performed on the post" userVote: VoteType } @@ -135,12 +141,29 @@ type ChatRoom { members: [User!] "return a specfic range of messages posted in the chat" - getMessages(first: Int, offset: Int): [String] + getMessages(first: Int, offset: Int): [ChatMessage] "id of the chat" id: ID! } +type ChatMessage { + "The author of the chat message." + author: User + + "The chatroom the message was posted in" + chat: ChatRoom + + "The timestamp when the message was posted (epoch)." + timestamp: Int + + "The content of the message." + content: String + + "The content of the message rendered by markdown-it." + htmlContent: String +} + "represents the type of vote performed on a post" enum VoteType { UPVOTE diff --git a/src/routes/home.ts b/src/routes/home.ts index 1bb397b..cd9fec9 100644 --- a/src/routes/home.ts +++ b/src/routes/home.ts @@ -3,8 +3,12 @@ import {GraphQLError} from "graphql"; import * as status from "http-status"; import {Server} from "socket.io"; import dataaccess from "../lib/dataaccess"; +import {Chatroom} from "../lib/dataaccess/Chatroom"; import {Post} from "../lib/dataaccess/Post"; import {Profile} from "../lib/dataaccess/Profile"; +import {User} from "../lib/dataaccess/User"; +import {NotLoggedInGqlError} from "../lib/errors/graphqlErrors"; +import globals from "../lib/globals"; import {is} from "../lib/regex"; import Route from "../lib/Route"; @@ -48,14 +52,14 @@ class HomeRoute extends Route { return new Profile(req.session.userId); } else { res.status(status.UNAUTHORIZED); - return new GraphQLError("Not logged in"); + return new NotLoggedInGqlError(); } }, async getUser({userId, handle}: {userId: number, handle: string}) { if (handle) { return await dataaccess.getUserByHandle(handle); } else if (userId) { - return dataaccess.getUser(userId); + return new User(userId); } else { res.status(status.BAD_REQUEST); return new GraphQLError("No userId or handle provided."); @@ -69,19 +73,28 @@ class HomeRoute extends Route { return new GraphQLError("No postId given."); } }, + async getChat({chatId}: {chatId: number}) { + if (chatId) { + return new Chatroom(chatId); + } else { + res.status(status.BAD_REQUEST); + return new GraphQLError("No chatId given."); + } + }, acceptCookies() { req.session.cookiesAccepted = true; return true; }, async login({email, passwordHash}: {email: string, passwordHash: string}) { if (email && passwordHash) { - const user = await dataaccess.getUserByLogin(email, passwordHash); - if (user && user.id) { + try { + const user = await dataaccess.getUserByLogin(email, passwordHash); req.session.userId = user.id; return user; - } else { + } catch (err) { + globals.logger.verbose(`Failed to login user '${email}'`); res.status(status.BAD_REQUEST); - return new GraphQLError("Invalid login data."); + return err.graphqlError; } } else { res.status(status.BAD_REQUEST); @@ -94,7 +107,7 @@ class HomeRoute extends Route { return true; } else { res.status(status.UNAUTHORIZED); - return new GraphQLError("Not logged in."); + return new NotLoggedInGqlError(); } }, async register({username, email, passwordHash}: {username: string, email: string, passwordHash: string}) { @@ -165,7 +178,23 @@ class HomeRoute extends Route { } else { res.status(status.UNAUTHORIZED); - return new GraphQLError("Not logged in."); + return new NotLoggedInGqlError(); + } + }, + async sendChatMessage({chatId, content}: {chatId: number, content: string}) { + if (!req.session.userId) { + return new NotLoggedInGqlError(); + } + if (chatId && content) { + try { + return await dataaccess.sendChatMessage(req.session.userId, chatId, content); + } catch (err) { + res.status(status.BAD_REQUEST); + return err.graphqlError; + } + } else { + res.status(status.BAD_REQUEST); + return new GraphQLError("No chatId or content given."); } }, }; From e1a9287641295e83c9f0df241c0b8477b95b17a0 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Sat, 28 Sep 2019 23:30:49 +0200 Subject: [PATCH 27/40] Updated database for requests --- src/lib/dataaccess/User.ts | 19 ++++ src/lib/dataaccess/index.ts | 2 +- src/routes/home.ts | 4 +- src/sql/create-tables.sql | 190 ++++++++++++++++++++++++------------ src/sql/update-tables.sql | 22 +++-- 5 files changed, 162 insertions(+), 75 deletions(-) diff --git a/src/lib/dataaccess/User.ts b/src/lib/dataaccess/User.ts index b29dfcc..6358e59 100644 --- a/src/lib/dataaccess/User.ts +++ b/src/lib/dataaccess/User.ts @@ -61,6 +61,25 @@ export class User extends DataObject { return new Date(this.$joinedAt); } + /** + * Returns all friends of the user. + */ + public async friends(): Promise { + const result = await queryHelper.all({ + text: "SELECT * FROM user_friends WHERE user_id = $1 OR friend_id = $1", + values: [this.id], + }); + const userFriends = []; + for (const row of result) { + if (row.user_id === this.id) { + userFriends.push(new User(row.friend_id)); + } else { + userFriends.push(new User(row.user_id)); + } + } + return userFriends; + } + /** * Returns all posts for a user. */ diff --git a/src/lib/dataaccess/index.ts b/src/lib/dataaccess/index.ts index 3db84ab..922fdac 100644 --- a/src/lib/dataaccess/index.ts +++ b/src/lib/dataaccess/index.ts @@ -41,8 +41,8 @@ namespace dataaccess { * Initializes everything that needs to be initialized asynchronous. */ export async function init() { - await queryHelper.updateTableDefinitions(); await queryHelper.createTables(); + await queryHelper.updateTableDefinitions(); } /** diff --git a/src/routes/home.ts b/src/routes/home.ts index cd9fec9..0ce10ad 100644 --- a/src/routes/home.ts +++ b/src/routes/home.ts @@ -135,7 +135,7 @@ class HomeRoute extends Route { return await (new Post(postId)).vote(req.session.userId, type); } else { res.status(status.UNAUTHORIZED); - return new GraphQLError("Not logged in."); + return new NotLoggedInGqlError(); } } else { res.status(status.BAD_REQUEST); @@ -148,7 +148,7 @@ class HomeRoute extends Route { return await dataaccess.createPost(content, req.session.userId); } else { res.status(status.UNAUTHORIZED); - return new GraphQLError("Not logged in."); + return new NotLoggedInGqlError(); } } else { res.status(status.BAD_REQUEST); diff --git a/src/sql/create-tables.sql b/src/sql/create-tables.sql index 17f6e74..a1563ac 100644 --- a/src/sql/create-tables.sql +++ b/src/sql/create-tables.sql @@ -1,65 +1,125 @@ -CREATE TABLE IF NOT EXISTS "user_sessions" ( - "sid" varchar NOT NULL COLLATE "default", - "sess" json NOT NULL, - "expire" timestamp(6) NOT NULL, - PRIMARY KEY ("sid") NOT DEFERRABLE INITIALLY IMMEDIATE -) WITH (OIDS=FALSE); - -CREATE TABLE IF NOT EXISTS users ( - id SERIAL PRIMARY KEY, - name varchar(128) NOT NULL, - handle varchar(128) UNIQUE NOT NULL, - password varchar(1024) NOT NULL, - email varchar(128) UNIQUE NOT NULL, - greenpoints INTEGER DEFAULT 0, - joined_at TIMESTAMP DEFAULT now() -); - -CREATE TABLE IF NOT EXISTS posts ( - id BIGSERIAL PRIMARY KEY, - upvotes INTEGER DEFAULT 0, - downvotes INTEGER DEFAULT 0, - created_at TIMESTAMP DEFAULT now(), - content text, - author SERIAL REFERENCES users (id) ON DELETE CASCADE, - type varchar(16) NOT NULL DEFAULT 'MISC' -); - -CREATE TABLE IF NOT EXISTS votes ( - user_id SERIAL REFERENCES users (id) ON DELETE CASCADE, - item_id BIGSERIAL REFERENCES posts (id) ON DELETE CASCADE, - vote_type varchar(8) DEFAULT 'upvote' -); - -CREATE TABLE IF NOT EXISTS events ( - id BIGSERIAL PRIMARY KEY, - time TIMESTAMP, - owner SERIAL REFERENCES users (id) -); - -CREATE TABLE IF NOT EXISTS event_members ( - event BIGSERIAL REFERENCES events (id), - member SERIAL REFERENCES users (id) -); - -CREATE TABLE IF NOT EXISTS chats ( - id BIGSERIAL PRIMARY KEY -); - -CREATE TABLE IF NOT EXISTS chat_messages ( - chat BIGSERIAL REFERENCES chats (id) ON DELETE CASCADE, - author SERIAL REFERENCES users (id) ON DELETE SET NULL, - content VARCHAR(1024) NOT NULL, - created_at TIMESTAMP DEFAULT now(), - PRIMARY KEY (chat, author, created_at) -); - -CREATE TABLE IF NOT EXISTS chat_members ( - chat BIGSERIAL REFERENCES chats (id) ON DELETE CASCADE, - member SERIAL REFERENCES users (id) ON DELETE CASCADE -); - -CREATE TABLE IF NOT EXISTS user_friends ( - user_id SERIAL REFERENCES users (id) ON DELETE CASCADE, - friend_id SERIAL REFERENCES users (id) ON DELETE CASCADE -); +--create functions +DO $$BEGIN + + IF NOT EXISTS(SELECT 1 from pg_proc WHERE proname = 'function_exists') THEN + CREATE FUNCTION function_exists(text) RETURNS boolean LANGUAGE plpgsql AS $BODY$ + BEGIN + RETURN EXISTS(SELECT 1 from pg_proc WHERE proname = $1); + END $BODY$; + END IF; + + IF NOT function_exists('type_exists') THEN + CREATE FUNCTION type_exists(text) RETURNS boolean LANGUAGE plpgsql AS $BODY$ + BEGIN + RETURN EXISTS (SELECT 1 FROM pg_type WHERE typname = $1); + END $BODY$; + END IF; + + IF NOT function_exists('cast_to_votetype') THEN + CREATE FUNCTION cast_to_votetype(text) RETURNS votetype LANGUAGE plpgsql AS $BODY$ + BEGIN + RETURN CASE WHEN $1::votetype IS NULL THEN 'UPVOTE' ELSE $1::votetype END; + END $BODY$; + END IF; + + IF NOT function_exists('cast_to_posttype') THEN + CREATE FUNCTION cast_to_posttype(text) RETURNS posttype LANGUAGE plpgsql AS $BODY$ + BEGIN + RETURN CASE WHEN $1::posttype IS NULL THEN 'MISC' ELSE $1::posttype END; + END $BODY$; + END IF; + +END$$; + +--create types +DO $$ BEGIN + + IF NOT type_exists('votetype') THEN + CREATE TYPE votetype AS enum ('DOWNVOTE', 'UPVOTE'); + END IF; + + IF NOT type_exists('posttype') THEN + CREATE TYPE posttype AS enum ('MISC', 'ACTION', 'IMAGE', 'TEXT'); + END IF; + + IF NOT type_exists('requesttype') THEN + CREATE TYPE requesttype AS enum ('FRIENDREQUEST'); + END IF; + +END$$; + +-- create tables +DO $$ BEGIN + + CREATE TABLE IF NOT EXISTS "user_sessions" ( + "sid" varchar NOT NULL COLLATE "default", + "sess" json NOT NULL, + "expire" timestamp(6) NOT NULL, + PRIMARY KEY ("sid") NOT DEFERRABLE INITIALLY IMMEDIATE + ) WITH (OIDS=FALSE); + + CREATE TABLE IF NOT EXISTS users ( + id SERIAL PRIMARY KEY, + name varchar(128) NOT NULL, + handle varchar(128) UNIQUE NOT NULL, + password varchar(1024) NOT NULL, + email varchar(128) UNIQUE NOT NULL, + greenpoints INTEGER DEFAULT 0, + joined_at TIMESTAMP DEFAULT now() + ); + + CREATE TABLE IF NOT EXISTS posts ( + id BIGSERIAL PRIMARY KEY, + upvotes INTEGER DEFAULT 0, + downvotes INTEGER DEFAULT 0, + created_at TIMESTAMP DEFAULT now(), + content text, + author SERIAL REFERENCES users (id) ON DELETE CASCADE, + type posttype NOT NULL DEFAULT 'MISC' + ); + + CREATE TABLE IF NOT EXISTS votes ( + user_id SERIAL REFERENCES users (id) ON DELETE CASCADE, + item_id BIGSERIAL REFERENCES posts (id) ON DELETE CASCADE, + vote_type votetype DEFAULT 'DOWNVOTE' + ); + + CREATE TABLE IF NOT EXISTS events ( + id BIGSERIAL PRIMARY KEY, + time TIMESTAMP, + owner SERIAL REFERENCES users (id) + ); + + CREATE TABLE IF NOT EXISTS event_members ( + event BIGSERIAL REFERENCES events (id), + member SERIAL REFERENCES users (id) + ); + + CREATE TABLE IF NOT EXISTS chats ( + id BIGSERIAL PRIMARY KEY + ); + + CREATE TABLE IF NOT EXISTS chat_messages ( + chat BIGSERIAL REFERENCES chats (id) ON DELETE CASCADE, + author SERIAL REFERENCES users (id) ON DELETE SET NULL, + content VARCHAR(1024) NOT NULL, + created_at TIMESTAMP DEFAULT now(), + PRIMARY KEY (chat, author, created_at) + ); + + CREATE TABLE IF NOT EXISTS chat_members ( + chat BIGSERIAL REFERENCES chats (id) ON DELETE CASCADE, + member SERIAL REFERENCES users (id) ON DELETE CASCADE + ); + + CREATE TABLE IF NOT EXISTS user_friends ( + user_id SERIAL REFERENCES users (id) ON DELETE CASCADE, + friend_id SERIAL REFERENCES users (id) ON DELETE CASCADE + ); + + CREATE TABLE IF NOT EXISTS requests ( + sender SERIAL REFERENCES users (id) ON DELETE CASCADE, + receiver SERIAL REFERENCES users (id) ON DELETE CASCADE + ); + +END $$; diff --git a/src/sql/update-tables.sql b/src/sql/update-tables.sql index 13bc681..da794c7 100644 --- a/src/sql/update-tables.sql +++ b/src/sql/update-tables.sql @@ -1,8 +1,16 @@ -ALTER TABLE IF EXISTS votes - ADD COLUMN IF NOT EXISTS vote_type varchar(8) DEFAULT 'UPVOTE', - ALTER COLUMN vote_type SET DEFAULT 'UPVOTE'; +DO $$ BEGIN -ALTER TABLE IF EXISTS posts - ALTER COLUMN type SET DEFAULT 'MISC', - DROP COLUMN IF EXISTS upvotes, - DROP COLUMN IF EXISTS downvotes; + ALTER TABLE IF EXISTS votes + ADD COLUMN IF NOT EXISTS vote_type votetype DEFAULT 'UPVOTE', + ALTER COLUMN vote_type TYPE votetype USING cast_to_votetype(vote_type::text), + ALTER COLUMN vote_type DROP DEFAULT, + ALTER COLUMN vote_type SET DEFAULT 'UPVOTE'; + + ALTER TABLE IF EXISTS posts + ALTER COLUMN type TYPE posttype USING cast_to_posttype(type::text), + ALTER COLUMN type DROP DEFAULT, + ALTER COLUMN type SET DEFAULT 'MISC', + DROP COLUMN IF EXISTS upvotes, + DROP COLUMN IF EXISTS downvotes; + +END $$; From 47d775fd2c8f39e519a1de4808542eda76ee2073 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Sun, 29 Sep 2019 00:19:46 +0200 Subject: [PATCH 28/40] Bug Fixes - fixed Chatrooms - fixed ChatMessages --- src/lib/dataaccess/ChatMessage.ts | 2 +- src/lib/dataaccess/Chatroom.ts | 12 +++++++----- src/lib/dataaccess/DataObject.ts | 1 + src/lib/dataaccess/index.ts | 11 ++++++----- src/public/graphql/schema.graphql | 16 ++++++++-------- src/routes/home.ts | 2 +- src/sql/create-tables.sql | 2 +- 7 files changed, 25 insertions(+), 21 deletions(-) diff --git a/src/lib/dataaccess/ChatMessage.ts b/src/lib/dataaccess/ChatMessage.ts index afbc600..9ae0a12 100644 --- a/src/lib/dataaccess/ChatMessage.ts +++ b/src/lib/dataaccess/ChatMessage.ts @@ -3,7 +3,7 @@ import {Chatroom} from "./Chatroom"; import {User} from "./User"; export class ChatMessage { - constructor(public author: User, public chat: Chatroom, public timestamp: number, public content: string) { + constructor(public author: User, public chat: Chatroom, public createdAt: number, public content: string) { } /** diff --git a/src/lib/dataaccess/Chatroom.ts b/src/lib/dataaccess/Chatroom.ts index 080a285..6b839c6 100644 --- a/src/lib/dataaccess/Chatroom.ts +++ b/src/lib/dataaccess/Chatroom.ts @@ -4,7 +4,9 @@ import {User} from "./User"; export class Chatroom { - constructor(private id: number) {} + constructor(private readonly id: number) { + this.id = Number(id); + } /** * Returns if the chat exists. @@ -29,7 +31,7 @@ export class Chatroom { }); const chatMembers = []; for (const row of result) { - chatMembers.push(new User(row)); + chatMembers.push(new User(row.id, row)); } return chatMembers; } @@ -40,8 +42,8 @@ export class Chatroom { * @param offset - the offset of messages to return * @param containing - filter by containing */ - public async messages(limit?: number, offset?: number, containing?: string) { - const lim = limit || 16; + 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({ @@ -51,7 +53,7 @@ export class Chatroom { const messages = []; for (const row of result) { - messages.push(new ChatMessage(new User(row.author), this, row.timestamp, row.content)); + messages.push(new ChatMessage(new User(row.author), this, row.created_at, row.content)); } if (containing) { return messages.filter((x) => x.content.includes(containing)); diff --git a/src/lib/dataaccess/DataObject.ts b/src/lib/dataaccess/DataObject.ts index 9c598ac..2f89988 100644 --- a/src/lib/dataaccess/DataObject.ts +++ b/src/lib/dataaccess/DataObject.ts @@ -5,6 +5,7 @@ export abstract class DataObject { protected dataLoaded: boolean = false; constructor(public id: number, protected row?: any) { + this.id = Number(id); } /** diff --git a/src/lib/dataaccess/index.ts b/src/lib/dataaccess/index.ts index 922fdac..11fba10 100644 --- a/src/lib/dataaccess/index.ts +++ b/src/lib/dataaccess/index.ts @@ -140,7 +140,7 @@ namespace dataaccess { */ export async function createChat(...members: number[]): Promise { const idResult = await queryHelper.first({ - text: "INSERT INTO chats (id) values (nextval('chats_id_seq'::regclass)) RETURNING *;", + text: "INSERT INTO chats (id) values (default) RETURNING *;", }); const id = idResult.id; const transaction = await queryHelper.createTransaction(); @@ -149,14 +149,15 @@ namespace dataaccess { for (const member of members) { await transaction.query({ name: "chat-member-insert", - text: "INSERT INTO chat_members (ABSOLUTE chat, member) VALUES ($1, $2);", - values: [member], + text: "INSERT INTO chat_members (chat, member) VALUES ($1, $2);", + values: [id, member], }); } await transaction.commit(); } catch (err) { globals.logger.warn(`Failed to insert chatmember into database: ${err.message}`); globals.logger.debug(err.stack); + await transaction.rollback(); } finally { transaction.release(); } @@ -173,10 +174,10 @@ namespace dataaccess { const chat = new Chatroom(chatId); if ((await chat.exists())) { const result = await queryHelper.first({ - text: "INSERT INTO chat_messages (chat, author, content, created_at) values ($1, $2, $3) RETURNING *", + text: "INSERT INTO chat_messages (chat, author, content) values ($1, $2, $3) RETURNING *", values: [chatId, authorId, content], }); - return new ChatMessage(new User(result.author), chat, result.timestamp, result.content); + return new ChatMessage(new User(result.author), chat, result.created_at, result.content); } else { throw new ChatNotFoundError(chatId); } diff --git a/src/public/graphql/schema.graphql b/src/public/graphql/schema.graphql index 2ff19cf..fb23b6f 100644 --- a/src/public/graphql/schema.graphql +++ b/src/public/graphql/schema.graphql @@ -11,9 +11,6 @@ type Query { "returns the chat object for a chat id" getChat(chatId: ID!): ChatRoom - "Creates a chat between the user (and optional an other user)" - createChat(members: [ID!]): ChatRoom - "returns the request object for a request id" getRequest(requestId: ID!): Request @@ -60,6 +57,9 @@ type Mutation { "delete the post for a given post id" deletePost(postId: ID!): Boolean + + "Creates a chat between the user (and optional an other user)" + createChat(members: [ID!]): ChatRoom } "represents a single user account" @@ -141,7 +141,7 @@ type ChatRoom { members: [User!] "return a specfic range of messages posted in the chat" - getMessages(first: Int, offset: Int): [ChatMessage] + messages(first: Int = 10, offset: Int, containing: String): [ChatMessage]! "id of the chat" id: ID! @@ -149,16 +149,16 @@ type ChatRoom { type ChatMessage { "The author of the chat message." - author: User + author: User! "The chatroom the message was posted in" - chat: ChatRoom + chat: ChatRoom! "The timestamp when the message was posted (epoch)." - timestamp: Int + createdAt: String! "The content of the message." - content: String + content: String! "The content of the message rendered by markdown-it." htmlContent: String diff --git a/src/routes/home.ts b/src/routes/home.ts index 0ce10ad..97e5943 100644 --- a/src/routes/home.ts +++ b/src/routes/home.ts @@ -181,7 +181,7 @@ class HomeRoute extends Route { return new NotLoggedInGqlError(); } }, - async sendChatMessage({chatId, content}: {chatId: number, content: string}) { + async sendMessage({chatId, content}: {chatId: number, content: string}) { if (!req.session.userId) { return new NotLoggedInGqlError(); } diff --git a/src/sql/create-tables.sql b/src/sql/create-tables.sql index a1563ac..486f50e 100644 --- a/src/sql/create-tables.sql +++ b/src/sql/create-tables.sql @@ -52,7 +52,7 @@ END$$; DO $$ BEGIN CREATE TABLE IF NOT EXISTS "user_sessions" ( - "sid" varchar NOT NULL COLLATE "default", + "sid" varchar NOT NULL, "sess" json NOT NULL, "expire" timestamp(6) NOT NULL, PRIMARY KEY ("sid") NOT DEFERRABLE INITIALLY IMMEDIATE From 7b94e4e3da213c09da80e0207ff48e3074aa9258 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Sun, 29 Sep 2019 13:03:38 +0200 Subject: [PATCH 29/40] Added in-memory caching - added class for caching management - added cache parameter to sql query object - implemented caching for sql querys with cache = true - added loglevel to config - improved some methods of dataaccess classes to enable better caching --- src/default-config.yaml | 3 ++ src/lib/MemoryCache.ts | 73 +++++++++++++++++++++++++++++++ src/lib/QueryHelper.ts | 27 ++++++++++-- src/lib/dataaccess/Chatroom.ts | 14 +++++- src/lib/dataaccess/Profile.ts | 6 ++- src/lib/dataaccess/User.ts | 5 +++ src/lib/globals.ts | 7 ++- src/public/graphql/schema.graphql | 60 +++++++++++++++++++++++-- 8 files changed, 183 insertions(+), 12 deletions(-) create mode 100644 src/lib/MemoryCache.ts diff --git a/src/default-config.yaml b/src/default-config.yaml index e607f15..fda957a 100644 --- a/src/default-config.yaml +++ b/src/default-config.yaml @@ -17,3 +17,6 @@ session: markdown: plugins: - 'markdown-it-emoji' + +logging: + level: info diff --git a/src/lib/MemoryCache.ts b/src/lib/MemoryCache.ts new file mode 100644 index 0000000..e5b8a0b --- /dev/null +++ b/src/lib/MemoryCache.ts @@ -0,0 +1,73 @@ +import {EventEmitter} from "events"; +import * as crypto from "crypto"; + +export class MemoryCache extends EventEmitter { + private cacheItems: any = {}; + private cacheExpires: any = {}; + private expireCheck: NodeJS.Timeout; + + /** + * Creates interval function. + * @param ttl + */ + constructor(private ttl: number = 500) { + super(); + this.expireCheck = setInterval(() => this.checkExpires(), ttl / 2); + } + + /** + * Creates a md5 hash of the given key. + * @param key + */ + public hashKey(key: string): string { + const hash = crypto.createHash("md5"); + const data = hash.update(key, "utf8"); + return data.digest("hex"); + } + + /** + * Sets an entry. + * @param key + * @param value + */ + public set(key: string, value: any) { + this.cacheItems[key] = value; + this.cacheExpires[key] = Date.now() + this.ttl; + this.emit("set", key, value); + } + + /** + * Returns the entry stored with the given key. + * @param key + */ + public get(key: string) { + if (this.cacheItems.hasOwnProperty(key)) { + this.emit("hit", key, this.cacheItems[key]); + return this.cacheItems[key]; + } else { + this.emit("miss", key); + } + } + + /** + * Deletes a cache item. + * @param key + */ + public delete(key: string) { + this.emit("delete", key); + delete this.cacheItems[key]; + } + + /** + * Checks expires and clears items that are over the expire value. + */ + private checkExpires() { + for (const [key, value] of Object.entries(this.cacheExpires)) { + if (value < Date.now()) { + this.emit("delete", key); + delete this.cacheItems[key]; + delete this.cacheExpires[key]; + } + } + } +} diff --git a/src/lib/QueryHelper.ts b/src/lib/QueryHelper.ts index fb1b638..01c98b8 100644 --- a/src/lib/QueryHelper.ts +++ b/src/lib/QueryHelper.ts @@ -11,6 +11,10 @@ import globals from "./globals"; const logger = globals.logger; +export interface IAdvancedQueryConfig extends QueryConfig { + cache?: boolean; +} + /** * Transaction class to wrap SQL transactions. */ @@ -101,7 +105,7 @@ export class QueryHelper { * executes the sql query with values and returns all results. * @param query */ - public async all(query: QueryConfig): Promise { + public async all(query: IAdvancedQueryConfig): Promise { const result = await this.query(query); return result.rows; } @@ -110,7 +114,7 @@ export class QueryHelper { * executes the sql query with values and returns the first result. * @param query */ - public async first(query: QueryConfig): Promise { + public async first(query: IAdvancedQueryConfig): Promise { const result = await this.query(query); if (result.rows && result.rows.length > 0) { return result.rows[0]; @@ -129,9 +133,24 @@ export class QueryHelper { * Queries the database with error handling. * @param query - the sql and values to execute */ - private async query(query: QueryConfig): Promise { + private async query(query: IAdvancedQueryConfig): Promise { try { - return await this.pool.query(query); + 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}`); diff --git a/src/lib/dataaccess/Chatroom.ts b/src/lib/dataaccess/Chatroom.ts index 6b839c6..0787637 100644 --- a/src/lib/dataaccess/Chatroom.ts +++ b/src/lib/dataaccess/Chatroom.ts @@ -1,3 +1,4 @@ +import globals from "../globals"; import {ChatMessage} from "./ChatMessage"; import {queryHelper} from "./index"; import {User} from "./User"; @@ -24,6 +25,7 @@ export class 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;`, @@ -31,7 +33,8 @@ export class Chatroom { }); const chatMembers = []; for (const row of result) { - chatMembers.push(new User(row.id, row)); + const user = new User(row.id, row); + chatMembers.push(user); } return chatMembers; } @@ -47,13 +50,20 @@ export class Chatroom { 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) { - messages.push(new ChatMessage(new User(row.author), this, row.created_at, row.content)); + if (!users[row.author]) { + const user = new User(row.author); + await user.exists(); + users[row.author] = user; + } + messages.push(new ChatMessage(users[row.author], this, row.created_at, row.content)); } if (containing) { return messages.filter((x) => x.content.includes(containing)); diff --git a/src/lib/dataaccess/Profile.ts b/src/lib/dataaccess/Profile.ts index 33eb7eb..957d631 100644 --- a/src/lib/dataaccess/Profile.ts +++ b/src/lib/dataaccess/Profile.ts @@ -6,10 +6,14 @@ export class Profile extends User { /** * Returns all chatrooms (with pagination). + * Skips the query if the user doesn't exist. * @param first * @param offset */ - public async chats({first, offset}: {first: number, offset?: number}) { + public async chats({first, offset}: {first: number, offset?: number}): Promise { + if (!(await this.exists())) { + return []; + } first = first || 10; offset = offset || 0; diff --git a/src/lib/dataaccess/User.ts b/src/lib/dataaccess/User.ts index 6358e59..b3501cc 100644 --- a/src/lib/dataaccess/User.ts +++ b/src/lib/dataaccess/User.ts @@ -1,3 +1,4 @@ +import globals from "../globals"; import {DataObject} from "./DataObject"; import {queryHelper} from "./index"; import {Post} from "./Post"; @@ -47,6 +48,7 @@ export class User extends DataObject { */ public async numberOfPosts(): Promise { const result = await queryHelper.first({ + cache: true, text: "SELECT COUNT(*) count FROM posts WHERE author = $1", values: [this.id], }); @@ -66,6 +68,7 @@ export class User extends DataObject { */ 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], }); @@ -87,6 +90,7 @@ export class User extends DataObject { 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], }); @@ -107,6 +111,7 @@ export class User extends DataObject { result = this.row; } else { result = await queryHelper.first({ + cache: true, text: "SELECT * FROM users WHERE users.id = $1", values: [this.id], }); diff --git a/src/lib/globals.ts b/src/lib/globals.ts index 3457fa5..ea0b8d8 100644 --- a/src/lib/globals.ts +++ b/src/lib/globals.ts @@ -8,6 +8,7 @@ import * as fsx from "fs-extra"; import * as yaml from "js-yaml"; import * as winston from "winston"; +import {MemoryCache} from "./Cache"; const configPath = "config.yaml"; const defaultConfig = __dirname + "/../default-config.yaml"; @@ -26,6 +27,7 @@ if (!(fsx.pathExistsSync(configPath))) { */ namespace globals { export const config = yaml.safeLoad(fsx.readFileSync("config.yaml", "utf-8")); + export const cache = new MemoryCache(1200); export const logger = winston.createLogger({ transports: [ new winston.transports.Console({ @@ -36,10 +38,13 @@ namespace globals { return `${timestamp} ${level}: ${message}`; }), ), - level: "debug", + level: config.logging.level, }), ], }); + cache.on("set", (key) => logger.debug(`Caching '${key}'.`)); + cache.on("miss", (key) => logger.debug(`Cache miss for '${key}'`)); + cache.on("hit", (key) => logger.debug(`Cache hit for '${key}'`)); } export default globals; diff --git a/src/public/graphql/schema.graphql b/src/public/graphql/schema.graphql index fb23b6f..e82d4ec 100644 --- a/src/public/graphql/schema.graphql +++ b/src/public/graphql/schema.graphql @@ -3,7 +3,7 @@ type Query { getUser(userId: ID, handle: String): User "returns the logged in user" - getSelf: User + getSelf: Profile "returns the post object for a post id" getPost(postId: ID!): Post @@ -26,10 +26,10 @@ type Mutation { acceptCookies: Boolean "Login of the user. The passwordHash should be a sha512 hash of the password." - login(email: String, passwordHash: String): User + login(email: String, passwordHash: String): Profile "Registers the user." - register(username: String, email: String, passwordHash: String): User + register(username: String, email: String, passwordHash: String): Profile "Logout of the user." logout: Boolean @@ -62,8 +62,60 @@ type Mutation { createChat(members: [ID!]): ChatRoom } +interface UserData { + "url for the Profile picture of the User" + profilePicture: String + + "name of the User" + name: String! + + "unique identifier name from the User" + handle: String! + + "Id of the User" + id: ID! + + "the total number of posts the user posted" + numberOfPosts: Int + + "returns a given number of posts of a user" + posts(first: Int=10, offset: Int): [Post] + + "creation date of the user account" + joinedAt: String! + + "all friends of the user" + friends: [User] +} + "represents a single user account" -type User { +type User implements UserData{ + "url for the Profile picture of the User" + profilePicture: String + + "name of the User" + name: String! + + "unique identifier name from the User" + handle: String! + + "Id of the User" + id: ID! + + "the total number of posts the user posted" + numberOfPosts: Int + + "returns a given number of posts of a user" + posts(first: Int=10, offset: Int): [Post] + + "creation date of the user account" + joinedAt: String! + + "all friends of the user" + friends: [User] +} + +type Profile implements UserData { "url for the Profile picture of the User" profilePicture: String From 46733d104611ec7a4d0518558acb2dfa5e023fa9 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Sun, 29 Sep 2019 13:03:52 +0200 Subject: [PATCH 30/40] Fixed import filename for caching on globals --- src/lib/globals.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/globals.ts b/src/lib/globals.ts index ea0b8d8..d3df954 100644 --- a/src/lib/globals.ts +++ b/src/lib/globals.ts @@ -8,7 +8,7 @@ import * as fsx from "fs-extra"; import * as yaml from "js-yaml"; import * as winston from "winston"; -import {MemoryCache} from "./Cache"; +import {MemoryCache} from "./MemoryCache"; const configPath = "config.yaml"; const defaultConfig = __dirname + "/../default-config.yaml"; From 770c8a35faf3f72d7714c006a26ba343f2466400 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Sun, 29 Sep 2019 13:28:13 +0200 Subject: [PATCH 31/40] Fixed data loading - fixed multiple loading requests on pending data load --- src/lib/MemoryCache.ts | 2 +- src/lib/dataaccess/DataObject.ts | 15 +++++++++++++-- src/lib/dataaccess/Post.ts | 4 ++++ src/lib/dataaccess/index.ts | 1 + src/public/graphql/schema.graphql | 7 +++++-- 5 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/lib/MemoryCache.ts b/src/lib/MemoryCache.ts index e5b8a0b..17890d8 100644 --- a/src/lib/MemoryCache.ts +++ b/src/lib/MemoryCache.ts @@ -1,5 +1,5 @@ -import {EventEmitter} from "events"; import * as crypto from "crypto"; +import {EventEmitter} from "events"; export class MemoryCache extends EventEmitter { private cacheItems: any = {}; diff --git a/src/lib/dataaccess/DataObject.ts b/src/lib/dataaccess/DataObject.ts index 2f89988..26fc809 100644 --- a/src/lib/dataaccess/DataObject.ts +++ b/src/lib/dataaccess/DataObject.ts @@ -1,10 +1,14 @@ /** * abstact DataObject class */ -export abstract class DataObject { +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); } @@ -22,8 +26,15 @@ export abstract class DataObject { * Loads data from the database if data has not been loaded */ protected async loadDataIfNotExists() { - if (!this.dataLoaded) { + 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 dffec19..2d3d8d6 100644 --- a/src/lib/dataaccess/Post.ts +++ b/src/lib/dataaccess/Post.ts @@ -16,6 +16,7 @@ export class Post extends DataObject { */ 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], }); @@ -27,6 +28,7 @@ export class Post extends DataObject { */ 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], }); @@ -80,6 +82,7 @@ export class Post extends DataObject { */ 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], }); @@ -127,6 +130,7 @@ export class Post extends DataObject { result = this.row; } else { result = await queryHelper.first({ + cache: true, text: "SELECT * FROM posts WHERE posts.id = $1", values: [this.id], }); diff --git a/src/lib/dataaccess/index.ts b/src/lib/dataaccess/index.ts index 11fba10..039d3d4 100644 --- a/src/lib/dataaccess/index.ts +++ b/src/lib/dataaccess/index.ts @@ -115,6 +115,7 @@ namespace dataaccess { * @param type */ export async function createPost(content: string, authorId: number, type?: string): Promise { + type = type || "MISC"; const result = await queryHelper.first({ text: "INSERT INTO posts (content, author, type) VALUES ($1, $2, $3) RETURNING *", values: [content, authorId, type], diff --git a/src/public/graphql/schema.graphql b/src/public/graphql/schema.graphql index e82d4ec..1629f16 100644 --- a/src/public/graphql/schema.graphql +++ b/src/public/graphql/schema.graphql @@ -53,7 +53,7 @@ type Mutation { sendMessage(chatId: ID!, content: String!): ChatMessage "create the post" - createPost(content: String!): Boolean + createPost(content: String!): Post "delete the post for a given post id" deletePost(postId: ID!): Boolean @@ -150,6 +150,9 @@ type Profile implements UserData { "represents a single user post" type Post { + "The id of the post." + id: ID! + "the text of the post" content: String @@ -166,7 +169,7 @@ type Post { author: User! "date the post was created" - creationDate: String! + createdAt: String! "the type of vote the user performed on the post" userVote: VoteType From b9655acc124485d65333b3891e1674964911beea Mon Sep 17 00:00:00 2001 From: Trivernis Date: Sun, 29 Sep 2019 17:09:07 +0200 Subject: [PATCH 32/40] Implemented Request API - implemented sendRequest, acceptRequest, denyRequest --- src/lib/dataaccess/ChatMessage.ts | 7 +- src/lib/dataaccess/Post.ts | 2 +- src/lib/dataaccess/Profile.ts | 94 ++++++++++++++++++++++++-- src/lib/dataaccess/Request.ts | 12 ++++ src/lib/dataaccess/index.ts | 17 +++++ src/lib/errors/RequestNotFoundError.ts | 9 +++ src/public/graphql/schema.graphql | 24 +++---- src/routes/home.ts | 46 +++++++++++++ src/sql/create-tables.sql | 16 +++-- src/sql/update-tables.sql | 3 + 10 files changed, 206 insertions(+), 24 deletions(-) create mode 100644 src/lib/dataaccess/Request.ts create mode 100644 src/lib/errors/RequestNotFoundError.ts diff --git a/src/lib/dataaccess/ChatMessage.ts b/src/lib/dataaccess/ChatMessage.ts index 9ae0a12..bbad8ad 100644 --- a/src/lib/dataaccess/ChatMessage.ts +++ b/src/lib/dataaccess/ChatMessage.ts @@ -3,8 +3,11 @@ import {Chatroom} from "./Chatroom"; import {User} from "./User"; export class ChatMessage { - constructor(public author: User, public chat: Chatroom, public createdAt: number, public content: string) { - } + constructor( + public readonly author: User, + public readonly chat: Chatroom, + public readonly createdAt: number, + public readonly content: string) {} /** * The content rendered by markdown-it. diff --git a/src/lib/dataaccess/Post.ts b/src/lib/dataaccess/Post.ts index 2d3d8d6..7908f62 100644 --- a/src/lib/dataaccess/Post.ts +++ b/src/lib/dataaccess/Post.ts @@ -108,7 +108,7 @@ export class Post extends DataObject { } else { if (uVote) { await queryHelper.first({ - text: "UPDATE votes SET vote_type = $1 WHERE user_id = $1 AND item_id = $3", + text: "UPDATE votes SET vote_type = $1 WHERE user_id = $2 AND item_id = $3", values: [type, userId, this.id], }); } else { diff --git a/src/lib/dataaccess/Profile.ts b/src/lib/dataaccess/Profile.ts index 957d631..090e6da 100644 --- a/src/lib/dataaccess/Profile.ts +++ b/src/lib/dataaccess/Profile.ts @@ -1,6 +1,8 @@ +import {RequestNotFoundError} from "../errors/RequestNotFoundError"; import {Chatroom} from "./Chatroom"; -import {queryHelper} from "./index"; +import dataaccess, {queryHelper} from "./index"; import {User} from "./User"; +import {Request} from "./Request"; export class Profile extends User { @@ -28,6 +30,30 @@ 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); + } + + /** + * Returns all received requests of the user. + */ + public async receivedRequests() { + const result = await queryHelper.all({ + cache: true, + text: "SELECT * FROM requests WHERE receiver = $1", + values: [this.id], + }); + return this.getRequests(result); + } + /** * Sets the greenpoints of a user. * @param points @@ -46,7 +72,7 @@ export class Profile extends User { */ public async setEmail(email: string): Promise { const result = await queryHelper.first({ - text: "UPDATE TABLE users SET email = $1 WHERE users.id = $2 RETURNING email", + text: "UPDATE users SET email = $1 WHERE users.id = $2 RETURNING email", values: [email, this.id], }); return result.email; @@ -57,7 +83,7 @@ export class Profile extends User { */ public async setHandle(handle: string): Promise { const result = await queryHelper.first({ - text: "UPDATE TABLE users SET handle = $1 WHERE id = $2", + text: "UPDATE users SET handle = $1 WHERE id = $2", values: [handle, this.id], }); return result.handle; @@ -69,9 +95,69 @@ export class Profile extends User { */ public async setName(name: string): Promise { const result = await queryHelper.first({ - text: "UPDATE TABLE users SET name = $1 WHERE id = $2", + text: "UPDATE users SET name = $1 WHERE id = $2", values: [name, this.id], }); return result.name; } + + /** + * Denys a request. + * @param sender + * @param type + */ + public async denyRequest(sender: number, type: dataaccess.RequestType) { + await queryHelper.first({ + text: "DELETE FROM requests WHERE receiver = $1 AND sender = $2 AND type = $3", + values: [this.id, sender, type], + }); + } + + /** + * Accepts a request. + * @param sender + * @param type + */ + public async acceptRequest(sender: number, type: dataaccess.RequestType) { + const exists = await queryHelper.first({ + cache: true, + text: "SELECT 1 FROM requests WHERE receiver = $1 AND sender = $2 AND type = $3", + values: [this.id, sender, type], + }); + if (exists) { + if (type === dataaccess.RequestType.FRIENDREQUEST) { + await queryHelper.first({ + text: "INSERT INTO user_friends (user_id, friend_id) VALUES ($1, $2)", + values: [this.id, sender], + }); + } + } else { + throw new RequestNotFoundError(sender, this.id, type); + } + } + + /** + * Returns request wrapper for a row database request result. + * @param rows + */ + private getRequests(rows: any) { + const requests = []; + const requestUsers: any = {}; + + for (const row of rows) { + let sender = requestUsers[row.sender]; + + if (!sender) { + sender = new User(row.sender); + requestUsers[row.sender] = sender; + } + let receiver = requestUsers[row.receiver]; + if (!receiver) { + receiver = new User(row.receiver); + requestUsers[row.receiver] = receiver; + } + requests.push(new Request(sender, receiver, row.type)); + } + return requests; + } } diff --git a/src/lib/dataaccess/Request.ts b/src/lib/dataaccess/Request.ts new file mode 100644 index 0000000..f161757 --- /dev/null +++ b/src/lib/dataaccess/Request.ts @@ -0,0 +1,12 @@ +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) {} +} diff --git a/src/lib/dataaccess/index.ts b/src/lib/dataaccess/index.ts index 039d3d4..28ae27c 100644 --- a/src/lib/dataaccess/index.ts +++ b/src/lib/dataaccess/index.ts @@ -7,6 +7,7 @@ import {ChatMessage} from "./ChatMessage"; import {Chatroom} from "./Chatroom"; import {Post} from "./Post"; import {Profile} from "./Profile"; +import {Request} from "./Request"; import {User} from "./User"; const config = globals.config; @@ -184,6 +185,22 @@ namespace dataaccess { } } + /** + * Sends a request to a user. + * @param sender + * @param receiver + * @param type + */ + export async function createRequest(sender: number, receiver: number, type?: RequestType) { + type = type || RequestType.FRIENDREQUEST; + + const result = await queryHelper.first({ + text: "INSERT INTO requests (sender, receiver, type) VALUES ($1, $2, $3) RETURNING *", + values: [sender, receiver, type], + }); + return new Request(new User(result.sender), new User(result.receiver), result.type); + } + /** * Enum representing the types of votes that can be performed on a post. */ diff --git a/src/lib/errors/RequestNotFoundError.ts b/src/lib/errors/RequestNotFoundError.ts new file mode 100644 index 0000000..8a020d1 --- /dev/null +++ b/src/lib/errors/RequestNotFoundError.ts @@ -0,0 +1,9 @@ +import dataaccess from "../dataaccess"; +import {BaseError} from "./BaseError"; + +export class RequestNotFoundError extends BaseError { + constructor(sender: number, receiver: number, type: dataaccess.RequestType) { + super(`Request with sender '${sender}' and receiver '${receiver}' of type '${type}' not found.`); + } + +} diff --git a/src/public/graphql/schema.graphql b/src/public/graphql/schema.graphql index 1629f16..19a19eb 100644 --- a/src/public/graphql/schema.graphql +++ b/src/public/graphql/schema.graphql @@ -11,9 +11,6 @@ type Query { "returns the chat object for a chat id" getChat(chatId: ID!): ChatRoom - "returns the request object for a request id" - getRequest(requestId: ID!): Request - "find a post by the posted date or content" findPost(first: Int, offset: Int, text: String!, postedDate: String): [Post] @@ -41,10 +38,10 @@ type Mutation { report(postId: ID!): Boolean "send a request" - sendRequest(reciever: ID!, type: RequestType): Boolean + sendRequest(receiver: ID!, type: RequestType): Request "lets you accept a request for a given request id" - acceptRequest(requestId: ID!): Boolean + acceptRequest(sender: ID!, type: RequestType): Boolean "lets you deny a request for a given request id" denyRequest(requestId: ID!): Boolean @@ -143,8 +140,11 @@ type Profile implements UserData { "all friends of the user" friends: [User] - "all request for groupChats/friends/events" - requests: [Request] + "all sent request for groupChats/friends/events" + sentRequests: [Request] + + "all received request for groupChats/friends/events" + receivedRequests: [Request] } "represents a single user post" @@ -177,9 +177,6 @@ type Post { "represents a request of any type" type Request { - "id of the request" - id: ID! - "Id of the user who sended the request" sender: User! @@ -187,7 +184,7 @@ type Request { receiver: User! "type of the request" - requestType: RequestType! + type: RequestType! } "represents a chatroom" @@ -225,7 +222,10 @@ enum VoteType { DOWNVOTE } -"represents the type of request that the user has received" +""" +represents the type of request that the user has received +Currently on Friend Requests are implemented. +""" enum RequestType { FRIENDREQUEST GROUPINVITE diff --git a/src/routes/home.ts b/src/routes/home.ts index 97e5943..88ce8b0 100644 --- a/src/routes/home.ts +++ b/src/routes/home.ts @@ -183,6 +183,7 @@ class HomeRoute extends Route { }, async sendMessage({chatId, content}: {chatId: number, content: string}) { if (!req.session.userId) { + res.status(status.UNAUTHORIZED); return new NotLoggedInGqlError(); } if (chatId && content) { @@ -197,6 +198,51 @@ class HomeRoute extends Route { return new GraphQLError("No chatId or content given."); } }, + async sendRequest({receiver, type}: {receiver: number, type: dataaccess.RequestType}) { + if (!req.session.userId) { + res.status(status.UNAUTHORIZED); + return new NotLoggedInGqlError(); + } + if (receiver && type) { + return await dataaccess.createRequest(req.session.userId, receiver, type); + } else { + res.status(status.BAD_REQUEST); + return new GraphQLError("No receiver or type given."); + } + }, + async denyRequest({sender, type}: {sender: number, type: dataaccess.RequestType}) { + if (!req.session.userId) { + res.status(status.UNAUTHORIZED); + return new NotLoggedInGqlError(); + } + if (sender && type) { + const profile = new Profile(req.session.userId); + await profile.denyRequest(sender, type); + return true; + } else { + res.status(status.BAD_REQUEST); + return new GraphQLError("No sender or type given."); + } + }, + async acceptRequest({sender, type}: {sender: number, type: dataaccess.RequestType}) { + if (!req.session.userId) { + res.status(status.UNAUTHORIZED); + return new NotLoggedInGqlError(); + } + if (sender && type) { + try { + const profile = new Profile(req.session.userId); + await profile.acceptRequest(sender, type); + return true; + } catch (err) { + res.status(status.BAD_REQUEST); + return err.graphqlError; + } + } else { + res.status(status.BAD_REQUEST); + return new GraphQLError("No sender or type given."); + } + }, }; } } diff --git a/src/sql/create-tables.sql b/src/sql/create-tables.sql index 486f50e..128492b 100644 --- a/src/sql/create-tables.sql +++ b/src/sql/create-tables.sql @@ -81,7 +81,8 @@ DO $$ BEGIN CREATE TABLE IF NOT EXISTS votes ( user_id SERIAL REFERENCES users (id) ON DELETE CASCADE, item_id BIGSERIAL REFERENCES posts (id) ON DELETE CASCADE, - vote_type votetype DEFAULT 'DOWNVOTE' + vote_type votetype DEFAULT 'DOWNVOTE', + PRIMARY KEY (user_id, item_id) ); CREATE TABLE IF NOT EXISTS events ( @@ -92,7 +93,8 @@ DO $$ BEGIN CREATE TABLE IF NOT EXISTS event_members ( event BIGSERIAL REFERENCES events (id), - member SERIAL REFERENCES users (id) + member SERIAL REFERENCES users (id), + PRIMARY KEY (event, member) ); CREATE TABLE IF NOT EXISTS chats ( @@ -109,17 +111,21 @@ DO $$ BEGIN CREATE TABLE IF NOT EXISTS chat_members ( chat BIGSERIAL REFERENCES chats (id) ON DELETE CASCADE, - member SERIAL REFERENCES users (id) ON DELETE CASCADE + member SERIAL REFERENCES users (id) ON DELETE CASCADE, + PRIMARY KEY (chat, member) ); CREATE TABLE IF NOT EXISTS user_friends ( user_id SERIAL REFERENCES users (id) ON DELETE CASCADE, - friend_id SERIAL REFERENCES users (id) ON DELETE CASCADE + friend_id SERIAL REFERENCES users (id) ON DELETE CASCADE, + PRIMARY KEY (user_id, friend_id) ); CREATE TABLE IF NOT EXISTS requests ( sender SERIAL REFERENCES users (id) ON DELETE CASCADE, - receiver SERIAL REFERENCES users (id) ON DELETE CASCADE + receiver SERIAL REFERENCES users (id) ON DELETE CASCADE, + type requesttype DEFAULT 'FRIENDREQUEST', + PRIMARY KEY (sender, receiver, type) ); END $$; diff --git a/src/sql/update-tables.sql b/src/sql/update-tables.sql index da794c7..858a214 100644 --- a/src/sql/update-tables.sql +++ b/src/sql/update-tables.sql @@ -13,4 +13,7 @@ DO $$ BEGIN DROP COLUMN IF EXISTS upvotes, DROP COLUMN IF EXISTS downvotes; + ALTER TABLE requests + ADD COLUMN IF NOT EXISTS type requesttype DEFAULT 'FRIENDREQUEST'; + END $$; From aa761f98ca40b96d50ab19240f2076cce29feb8a Mon Sep 17 00:00:00 2001 From: Trivernis Date: Sun, 29 Sep 2019 17:24:59 +0200 Subject: [PATCH 33/40] MemoryCache performance improvement - switched to faster sha-1 function for hash strings --- src/lib/MemoryCache.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/MemoryCache.ts b/src/lib/MemoryCache.ts index 17890d8..1e76834 100644 --- a/src/lib/MemoryCache.ts +++ b/src/lib/MemoryCache.ts @@ -20,7 +20,7 @@ export class MemoryCache extends EventEmitter { * @param key */ public hashKey(key: string): string { - const hash = crypto.createHash("md5"); + const hash = crypto.createHash("sha1"); const data = hash.update(key, "utf8"); return data.digest("hex"); } From bf79df08ad29546962477926589fdbcf3ee80605 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Tue, 1 Oct 2019 14:47:46 +0200 Subject: [PATCH 34/40] changed table creation files --- package-lock.json | 41 +++++++--- src/sql/create-tables.sql | 155 +++++++++++++++++++------------------- 2 files changed, 106 insertions(+), 90 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8e55c5b..7a07454 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2552,7 +2552,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -2573,12 +2574,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2593,17 +2596,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -2720,7 +2726,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -2732,6 +2739,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -2746,6 +2754,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -2753,12 +2762,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -2777,6 +2788,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -2857,7 +2869,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -2869,6 +2882,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -2954,7 +2968,8 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -2990,6 +3005,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -3009,6 +3025,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -3052,12 +3069,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, diff --git a/src/sql/create-tables.sql b/src/sql/create-tables.sql index 128492b..e27559c 100644 --- a/src/sql/create-tables.sql +++ b/src/sql/create-tables.sql @@ -49,83 +49,80 @@ DO $$ BEGIN END$$; -- create tables -DO $$ BEGIN - CREATE TABLE IF NOT EXISTS "user_sessions" ( - "sid" varchar NOT NULL, - "sess" json NOT NULL, - "expire" timestamp(6) NOT NULL, - PRIMARY KEY ("sid") NOT DEFERRABLE INITIALLY IMMEDIATE - ) WITH (OIDS=FALSE); - - CREATE TABLE IF NOT EXISTS users ( - id SERIAL PRIMARY KEY, - name varchar(128) NOT NULL, - handle varchar(128) UNIQUE NOT NULL, - password varchar(1024) NOT NULL, - email varchar(128) UNIQUE NOT NULL, - greenpoints INTEGER DEFAULT 0, - joined_at TIMESTAMP DEFAULT now() - ); - - CREATE TABLE IF NOT EXISTS posts ( - id BIGSERIAL PRIMARY KEY, - upvotes INTEGER DEFAULT 0, - downvotes INTEGER DEFAULT 0, - created_at TIMESTAMP DEFAULT now(), - content text, - author SERIAL REFERENCES users (id) ON DELETE CASCADE, - type posttype NOT NULL DEFAULT 'MISC' - ); - - CREATE TABLE IF NOT EXISTS votes ( - user_id SERIAL REFERENCES users (id) ON DELETE CASCADE, - item_id BIGSERIAL REFERENCES posts (id) ON DELETE CASCADE, - vote_type votetype DEFAULT 'DOWNVOTE', - PRIMARY KEY (user_id, item_id) - ); - - CREATE TABLE IF NOT EXISTS events ( - id BIGSERIAL PRIMARY KEY, - time TIMESTAMP, - owner SERIAL REFERENCES users (id) - ); - - CREATE TABLE IF NOT EXISTS event_members ( - event BIGSERIAL REFERENCES events (id), - member SERIAL REFERENCES users (id), - PRIMARY KEY (event, member) - ); - - CREATE TABLE IF NOT EXISTS chats ( - id BIGSERIAL PRIMARY KEY - ); - - CREATE TABLE IF NOT EXISTS chat_messages ( - chat BIGSERIAL REFERENCES chats (id) ON DELETE CASCADE, - author SERIAL REFERENCES users (id) ON DELETE SET NULL, - content VARCHAR(1024) NOT NULL, - created_at TIMESTAMP DEFAULT now(), - PRIMARY KEY (chat, author, created_at) - ); - - CREATE TABLE IF NOT EXISTS chat_members ( - chat BIGSERIAL REFERENCES chats (id) ON DELETE CASCADE, - member SERIAL REFERENCES users (id) ON DELETE CASCADE, - PRIMARY KEY (chat, member) - ); - - CREATE TABLE IF NOT EXISTS user_friends ( - user_id SERIAL REFERENCES users (id) ON DELETE CASCADE, - friend_id SERIAL REFERENCES users (id) ON DELETE CASCADE, - PRIMARY KEY (user_id, friend_id) - ); - - CREATE TABLE IF NOT EXISTS requests ( - sender SERIAL REFERENCES users (id) ON DELETE CASCADE, - receiver SERIAL REFERENCES users (id) ON DELETE CASCADE, - type requesttype DEFAULT 'FRIENDREQUEST', - PRIMARY KEY (sender, receiver, type) - ); - -END $$; +CREATE TABLE IF NOT EXISTS "user_sessions" ( + "sid" varchar NOT NULL, + "sess" json NOT NULL, + "expire" timestamp(6) NOT NULL, + PRIMARY KEY ("sid") NOT DEFERRABLE INITIALLY IMMEDIATE +) WITH (OIDS=FALSE); + +CREATE TABLE IF NOT EXISTS users ( + id SERIAL PRIMARY KEY, + name varchar(128) NOT NULL, + handle varchar(128) UNIQUE NOT NULL, + password varchar(1024) NOT NULL, + email varchar(128) UNIQUE NOT NULL, + greenpoints INTEGER DEFAULT 0, + joined_at TIMESTAMP DEFAULT now() +); + +CREATE TABLE IF NOT EXISTS posts ( + id BIGSERIAL PRIMARY KEY, + upvotes INTEGER DEFAULT 0, + downvotes INTEGER DEFAULT 0, + created_at TIMESTAMP DEFAULT now(), + content text, + author SERIAL REFERENCES users (id) ON DELETE CASCADE, + type posttype NOT NULL DEFAULT 'MISC' +); + +CREATE TABLE IF NOT EXISTS votes ( + user_id SERIAL REFERENCES users (id) ON DELETE CASCADE, + item_id BIGSERIAL REFERENCES posts (id) ON DELETE CASCADE, + vote_type votetype DEFAULT 'DOWNVOTE', + PRIMARY KEY (user_id, item_id) +); + +CREATE TABLE IF NOT EXISTS events ( + id BIGSERIAL PRIMARY KEY, + time TIMESTAMP, + owner SERIAL REFERENCES users (id) +); + +CREATE TABLE IF NOT EXISTS event_members ( + event BIGSERIAL REFERENCES events (id), + member SERIAL REFERENCES users (id), + PRIMARY KEY (event, member) +); + +CREATE TABLE IF NOT EXISTS chats ( + id BIGSERIAL PRIMARY KEY +); + +CREATE TABLE IF NOT EXISTS chat_messages ( + chat BIGSERIAL REFERENCES chats (id) ON DELETE CASCADE, + author SERIAL REFERENCES users (id) ON DELETE SET NULL, + content VARCHAR(1024) NOT NULL, + created_at TIMESTAMP DEFAULT now(), + PRIMARY KEY (chat, author, created_at) +); + +CREATE TABLE IF NOT EXISTS chat_members ( + chat BIGSERIAL REFERENCES chats (id) ON DELETE CASCADE, + member SERIAL REFERENCES users (id) ON DELETE CASCADE, + PRIMARY KEY (chat, member) +); + +CREATE TABLE IF NOT EXISTS user_friends ( + user_id SERIAL REFERENCES users (id) ON DELETE CASCADE, + friend_id SERIAL REFERENCES users (id) ON DELETE CASCADE, + PRIMARY KEY (user_id, friend_id) +); + +CREATE TABLE IF NOT EXISTS requests ( + sender SERIAL REFERENCES users (id) ON DELETE CASCADE, + receiver SERIAL REFERENCES users (id) ON DELETE CASCADE, + type requesttype DEFAULT 'FRIENDREQUEST', + PRIMARY KEY (sender, receiver, type) +); From bf3be7d1292878e0b86c07d00991c8ffa4f70a27 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Tue, 1 Oct 2019 15:02:07 +0200 Subject: [PATCH 35/40] changed sql - added create tables to anonymous block --- src/lib/QueryHelper.ts | 9 +++ src/lib/dataaccess/index.ts | 8 +- src/sql/create-tables.sql | 155 ++++++++++++++++++------------------ 3 files changed, 94 insertions(+), 78 deletions(-) diff --git a/src/lib/QueryHelper.ts b/src/lib/QueryHelper.ts index 01c98b8..1b8aa37 100644 --- a/src/lib/QueryHelper.ts +++ b/src/lib/QueryHelper.ts @@ -79,6 +79,15 @@ export class QueryHelper { 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 */ diff --git a/src/lib/dataaccess/index.ts b/src/lib/dataaccess/index.ts index 28ae27c..3dccfcd 100644 --- a/src/lib/dataaccess/index.ts +++ b/src/lib/dataaccess/index.ts @@ -42,8 +42,12 @@ namespace dataaccess { * Initializes everything that needs to be initialized asynchronous. */ export async function init() { - await queryHelper.createTables(); - await queryHelper.updateTableDefinitions(); + try { + await queryHelper.init(); + } catch (err) { + globals.logger.error(err.message); + globals.logger.debug(err.stack); + } } /** diff --git a/src/sql/create-tables.sql b/src/sql/create-tables.sql index e27559c..128492b 100644 --- a/src/sql/create-tables.sql +++ b/src/sql/create-tables.sql @@ -49,80 +49,83 @@ DO $$ BEGIN END$$; -- create tables +DO $$ BEGIN -CREATE TABLE IF NOT EXISTS "user_sessions" ( - "sid" varchar NOT NULL, - "sess" json NOT NULL, - "expire" timestamp(6) NOT NULL, - PRIMARY KEY ("sid") NOT DEFERRABLE INITIALLY IMMEDIATE -) WITH (OIDS=FALSE); - -CREATE TABLE IF NOT EXISTS users ( - id SERIAL PRIMARY KEY, - name varchar(128) NOT NULL, - handle varchar(128) UNIQUE NOT NULL, - password varchar(1024) NOT NULL, - email varchar(128) UNIQUE NOT NULL, - greenpoints INTEGER DEFAULT 0, - joined_at TIMESTAMP DEFAULT now() -); - -CREATE TABLE IF NOT EXISTS posts ( - id BIGSERIAL PRIMARY KEY, - upvotes INTEGER DEFAULT 0, - downvotes INTEGER DEFAULT 0, - created_at TIMESTAMP DEFAULT now(), - content text, - author SERIAL REFERENCES users (id) ON DELETE CASCADE, - type posttype NOT NULL DEFAULT 'MISC' -); - -CREATE TABLE IF NOT EXISTS votes ( - user_id SERIAL REFERENCES users (id) ON DELETE CASCADE, - item_id BIGSERIAL REFERENCES posts (id) ON DELETE CASCADE, - vote_type votetype DEFAULT 'DOWNVOTE', - PRIMARY KEY (user_id, item_id) -); - -CREATE TABLE IF NOT EXISTS events ( - id BIGSERIAL PRIMARY KEY, - time TIMESTAMP, - owner SERIAL REFERENCES users (id) -); - -CREATE TABLE IF NOT EXISTS event_members ( - event BIGSERIAL REFERENCES events (id), - member SERIAL REFERENCES users (id), - PRIMARY KEY (event, member) -); - -CREATE TABLE IF NOT EXISTS chats ( - id BIGSERIAL PRIMARY KEY -); - -CREATE TABLE IF NOT EXISTS chat_messages ( - chat BIGSERIAL REFERENCES chats (id) ON DELETE CASCADE, - author SERIAL REFERENCES users (id) ON DELETE SET NULL, - content VARCHAR(1024) NOT NULL, - created_at TIMESTAMP DEFAULT now(), - PRIMARY KEY (chat, author, created_at) -); - -CREATE TABLE IF NOT EXISTS chat_members ( - chat BIGSERIAL REFERENCES chats (id) ON DELETE CASCADE, - member SERIAL REFERENCES users (id) ON DELETE CASCADE, - PRIMARY KEY (chat, member) -); - -CREATE TABLE IF NOT EXISTS user_friends ( - user_id SERIAL REFERENCES users (id) ON DELETE CASCADE, - friend_id SERIAL REFERENCES users (id) ON DELETE CASCADE, - PRIMARY KEY (user_id, friend_id) -); - -CREATE TABLE IF NOT EXISTS requests ( - sender SERIAL REFERENCES users (id) ON DELETE CASCADE, - receiver SERIAL REFERENCES users (id) ON DELETE CASCADE, - type requesttype DEFAULT 'FRIENDREQUEST', - PRIMARY KEY (sender, receiver, type) -); + CREATE TABLE IF NOT EXISTS "user_sessions" ( + "sid" varchar NOT NULL, + "sess" json NOT NULL, + "expire" timestamp(6) NOT NULL, + PRIMARY KEY ("sid") NOT DEFERRABLE INITIALLY IMMEDIATE + ) WITH (OIDS=FALSE); + + CREATE TABLE IF NOT EXISTS users ( + id SERIAL PRIMARY KEY, + name varchar(128) NOT NULL, + handle varchar(128) UNIQUE NOT NULL, + password varchar(1024) NOT NULL, + email varchar(128) UNIQUE NOT NULL, + greenpoints INTEGER DEFAULT 0, + joined_at TIMESTAMP DEFAULT now() + ); + + CREATE TABLE IF NOT EXISTS posts ( + id BIGSERIAL PRIMARY KEY, + upvotes INTEGER DEFAULT 0, + downvotes INTEGER DEFAULT 0, + created_at TIMESTAMP DEFAULT now(), + content text, + author SERIAL REFERENCES users (id) ON DELETE CASCADE, + type posttype NOT NULL DEFAULT 'MISC' + ); + + CREATE TABLE IF NOT EXISTS votes ( + user_id SERIAL REFERENCES users (id) ON DELETE CASCADE, + item_id BIGSERIAL REFERENCES posts (id) ON DELETE CASCADE, + vote_type votetype DEFAULT 'DOWNVOTE', + PRIMARY KEY (user_id, item_id) + ); + + CREATE TABLE IF NOT EXISTS events ( + id BIGSERIAL PRIMARY KEY, + time TIMESTAMP, + owner SERIAL REFERENCES users (id) + ); + + CREATE TABLE IF NOT EXISTS event_members ( + event BIGSERIAL REFERENCES events (id), + member SERIAL REFERENCES users (id), + PRIMARY KEY (event, member) + ); + + CREATE TABLE IF NOT EXISTS chats ( + id BIGSERIAL PRIMARY KEY + ); + + CREATE TABLE IF NOT EXISTS chat_messages ( + chat BIGSERIAL REFERENCES chats (id) ON DELETE CASCADE, + author SERIAL REFERENCES users (id) ON DELETE SET NULL, + content VARCHAR(1024) NOT NULL, + created_at TIMESTAMP DEFAULT now(), + PRIMARY KEY (chat, author, created_at) + ); + + CREATE TABLE IF NOT EXISTS chat_members ( + chat BIGSERIAL REFERENCES chats (id) ON DELETE CASCADE, + member SERIAL REFERENCES users (id) ON DELETE CASCADE, + PRIMARY KEY (chat, member) + ); + + CREATE TABLE IF NOT EXISTS user_friends ( + user_id SERIAL REFERENCES users (id) ON DELETE CASCADE, + friend_id SERIAL REFERENCES users (id) ON DELETE CASCADE, + PRIMARY KEY (user_id, friend_id) + ); + + CREATE TABLE IF NOT EXISTS requests ( + sender SERIAL REFERENCES users (id) ON DELETE CASCADE, + receiver SERIAL REFERENCES users (id) ON DELETE CASCADE, + type requesttype DEFAULT 'FRIENDREQUEST', + PRIMARY KEY (sender, receiver, type) + ); + +END $$; From 2ed4f9793a67ab49156d4ac524e4f5a74b3098a0 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Tue, 1 Oct 2019 15:13:36 +0200 Subject: [PATCH 36/40] Changed to table creation - changed creation and update to transaction type - fixed creation file --- src/lib/QueryHelper.ts | 26 ++++++++++++++++++++++++-- src/sql/create-tables.sql | 34 ++++++++++++++++++++-------------- 2 files changed, 44 insertions(+), 16 deletions(-) diff --git a/src/lib/QueryHelper.ts b/src/lib/QueryHelper.ts index 1b8aa37..83b36fc 100644 --- a/src/lib/QueryHelper.ts +++ b/src/lib/QueryHelper.ts @@ -95,7 +95,18 @@ export class QueryHelper { if (this.tableCreationFile) { logger.info("Creating nonexistent tables..."); const tableSql = await fsx.readFile(this.tableCreationFile, "utf-8"); - await this.query({text: tableSql}); + 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(); + } } } @@ -106,7 +117,18 @@ export class QueryHelper { if (this.tableUpdateFile) { logger.info("Updating table definitions..."); const tableSql = await fsx.readFile(this.tableUpdateFile, "utf-8"); - await this.query({text: tableSql}); + 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(); + } } } diff --git a/src/sql/create-tables.sql b/src/sql/create-tables.sql index 128492b..70e49e2 100644 --- a/src/sql/create-tables.sql +++ b/src/sql/create-tables.sql @@ -15,20 +15,6 @@ DO $$BEGIN END $BODY$; END IF; - IF NOT function_exists('cast_to_votetype') THEN - CREATE FUNCTION cast_to_votetype(text) RETURNS votetype LANGUAGE plpgsql AS $BODY$ - BEGIN - RETURN CASE WHEN $1::votetype IS NULL THEN 'UPVOTE' ELSE $1::votetype END; - END $BODY$; - END IF; - - IF NOT function_exists('cast_to_posttype') THEN - CREATE FUNCTION cast_to_posttype(text) RETURNS posttype LANGUAGE plpgsql AS $BODY$ - BEGIN - RETURN CASE WHEN $1::posttype IS NULL THEN 'MISC' ELSE $1::posttype END; - END $BODY$; - END IF; - END$$; --create types @@ -48,6 +34,26 @@ DO $$ BEGIN END$$; +-- create functions relying on types + +DO $$ BEGIN + + IF NOT function_exists('cast_to_votetype') THEN + CREATE FUNCTION cast_to_votetype(text) RETURNS votetype LANGUAGE plpgsql AS $BODY$ + BEGIN + RETURN CASE WHEN $1::votetype IS NULL THEN 'UPVOTE' ELSE $1::votetype END; + END $BODY$; + END IF; + + IF NOT function_exists('cast_to_posttype') THEN + CREATE FUNCTION cast_to_posttype(text) RETURNS posttype LANGUAGE plpgsql AS $BODY$ + BEGIN + RETURN CASE WHEN $1::posttype IS NULL THEN 'MISC' ELSE $1::posttype END; + END $BODY$; + END IF; + +END$$; + -- create tables DO $$ BEGIN From 2d0a6e3433a2e922f2f900b2681818f91a1b1c95 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Tue, 1 Oct 2019 17:45:55 +0200 Subject: [PATCH 37/40] Implemented Socket.io - added socket for chat messages - added socket for new requests - added socket for post creation - added socket for created post --- src/lib/InternalEvents.ts | 8 +++ src/lib/dataaccess/ChatMessage.ts | 13 ++++ src/lib/dataaccess/Chatroom.ts | 4 +- src/lib/dataaccess/Post.ts | 13 ++++ src/lib/dataaccess/Request.ts | 14 ++++- src/lib/dataaccess/index.ts | 31 ++++++++-- src/lib/globals.ts | 2 + src/public/graphql/schema.graphql | 3 + src/routes/home.ts | 98 +++++++++++++++++++++++++------ 9 files changed, 163 insertions(+), 23 deletions(-) create mode 100644 src/lib/InternalEvents.ts diff --git a/src/lib/InternalEvents.ts b/src/lib/InternalEvents.ts new file mode 100644 index 0000000..13cf6ab --- /dev/null +++ b/src/lib/InternalEvents.ts @@ -0,0 +1,8 @@ +export enum InternalEvents { + CHATCREATE = "chatCreate", + CHATMESSAGE = "chatMessage", + GQLCHATMESSAGE = "graphqlChatMessage", + REQUESTCREATE = "requestCreate", + POSTCREATE = "postCreate", + GQLPOSTCREATE = "graphqlPostCreate", +} diff --git a/src/lib/dataaccess/ChatMessage.ts b/src/lib/dataaccess/ChatMessage.ts index bbad8ad..4c91a16 100644 --- a/src/lib/dataaccess/ChatMessage.ts +++ b/src/lib/dataaccess/ChatMessage.ts @@ -15,4 +15,17 @@ export class ChatMessage { public htmlContent(): string { return markdown.renderInline(this.content); } + + /** + * Returns resolved and rendered content of the chat message. + */ + public resolvedContent() { + return { + author: this.author.id, + chat: this.chat.id, + content: this.content, + createdAt: this.createdAt, + htmlContent: this.htmlContent(), + }; + } } diff --git a/src/lib/dataaccess/Chatroom.ts b/src/lib/dataaccess/Chatroom.ts index 0787637..e541f06 100644 --- a/src/lib/dataaccess/Chatroom.ts +++ b/src/lib/dataaccess/Chatroom.ts @@ -5,8 +5,10 @@ import {User} from "./User"; export class Chatroom { - constructor(private readonly id: number) { + public namespace: string; + constructor(public readonly id: number) { this.id = Number(id); + this.namespace = `/chat/${id}`; } /** diff --git a/src/lib/dataaccess/Post.ts b/src/lib/dataaccess/Post.ts index 7908f62..e4912c7 100644 --- a/src/lib/dataaccess/Post.ts +++ b/src/lib/dataaccess/Post.ts @@ -11,6 +11,19 @@ export class Post extends DataObject { private $author: number; private $type: string; + /** + * Returns the resolved data of the post. + */ + public async resolvedData() { + await this.loadDataIfNotExists(); + return { + authorId: this.$author, + content: this.$content, + createdAt: this.$createdAt, + id: this.id, + type: this.$type, + }; + } /** * Returns the upvotes of a post. */ diff --git a/src/lib/dataaccess/Request.ts b/src/lib/dataaccess/Request.ts index f161757..7e8f1bc 100644 --- a/src/lib/dataaccess/Request.ts +++ b/src/lib/dataaccess/Request.ts @@ -8,5 +8,17 @@ export class Request { constructor( public readonly sender: User, public readonly receiver: User, - public readonly type: dataaccess.RequestType) {} + 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/index.ts b/src/lib/dataaccess/index.ts index 3dccfcd..46a6649 100644 --- a/src/lib/dataaccess/index.ts +++ b/src/lib/dataaccess/index.ts @@ -2,6 +2,7 @@ import {Pool} from "pg"; import {ChatNotFoundError} from "../errors/ChatNotFoundError"; 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"; @@ -125,7 +126,9 @@ namespace dataaccess { text: "INSERT INTO posts (content, author, type) VALUES ($1, $2, $3) RETURNING *", values: [content, authorId, type], }); - return new Post(result.id, result); + const post = new Post(result.id, result); + globals.internalEmitter.emit(InternalEvents.POSTCREATE, post); + return post; } /** @@ -167,7 +170,9 @@ namespace dataaccess { } finally { transaction.release(); } - return new Chatroom(id); + const chat = new Chatroom(id); + globals.internalEmitter.emit(InternalEvents.CHATCREATE, chat); + return chat; } /** @@ -183,12 +188,28 @@ namespace dataaccess { text: "INSERT INTO chat_messages (chat, author, content) values ($1, $2, $3) RETURNING *", values: [chatId, authorId, content], }); - return new ChatMessage(new User(result.author), chat, result.created_at, result.content); + const message = new ChatMessage(new User(result.author), chat, result.created_at, result.content); + globals.internalEmitter.emit(InternalEvents.CHATMESSAGE, message); + return message; } else { throw new ChatNotFoundError(chatId); } } + /** + * Returns all chats. + */ + export async function getAllChats(): Promise { + const result = await queryHelper.all({ + text: "SELECT id FROM chats;", + }); + const chats = []; + for (const row of result) { + chats.push(new Chatroom(row.id)); + } + return chats; + } + /** * Sends a request to a user. * @param sender @@ -202,7 +223,9 @@ namespace dataaccess { text: "INSERT INTO requests (sender, receiver, type) VALUES ($1, $2, $3) RETURNING *", values: [sender, receiver, type], }); - return new Request(new User(result.sender), new User(result.receiver), result.type); + const request = new Request(new User(result.sender), new User(result.receiver), result.type); + globals.internalEmitter.emit(InternalEvents.REQUESTCREATE, Request); + return request; } /** diff --git a/src/lib/globals.ts b/src/lib/globals.ts index d3df954..cea8e3c 100644 --- a/src/lib/globals.ts +++ b/src/lib/globals.ts @@ -5,6 +5,7 @@ * Partly taken from {@link https://github.com/Trivernis/whooshy} */ +import {EventEmitter} from "events"; import * as fsx from "fs-extra"; import * as yaml from "js-yaml"; import * as winston from "winston"; @@ -42,6 +43,7 @@ namespace globals { }), ], }); + export const internalEmitter = new EventEmitter(); cache.on("set", (key) => logger.debug(`Caching '${key}'.`)); cache.on("miss", (key) => logger.debug(`Cache miss for '${key}'`)); cache.on("hit", (key) => logger.debug(`Cache hit for '${key}'`)); diff --git a/src/public/graphql/schema.graphql b/src/public/graphql/schema.graphql index 19a19eb..cefe527 100644 --- a/src/public/graphql/schema.graphql +++ b/src/public/graphql/schema.graphql @@ -189,6 +189,9 @@ type Request { "represents a chatroom" type ChatRoom { + "the socket.io namespace for the chatroom" + namespace: String + "the members of the chatroom" members: [User!] diff --git a/src/routes/home.ts b/src/routes/home.ts index 88ce8b0..614a8f7 100644 --- a/src/routes/home.ts +++ b/src/routes/home.ts @@ -1,17 +1,22 @@ import {Router} from "express"; import {GraphQLError} from "graphql"; import * as status from "http-status"; -import {Server} from "socket.io"; +import {Namespace, Server} from "socket.io"; import dataaccess from "../lib/dataaccess"; +import {ChatMessage} from "../lib/dataaccess/ChatMessage"; import {Chatroom} from "../lib/dataaccess/Chatroom"; import {Post} from "../lib/dataaccess/Post"; import {Profile} from "../lib/dataaccess/Profile"; +import {Request} from "../lib/dataaccess/Request"; import {User} from "../lib/dataaccess/User"; import {NotLoggedInGqlError} from "../lib/errors/graphqlErrors"; import globals from "../lib/globals"; +import {InternalEvents} from "../lib/InternalEvents"; import {is} from "../lib/regex"; import Route from "../lib/Route"; +const chatRooms: Namespace[] = []; + /** * Class for the home route. */ @@ -30,6 +35,33 @@ class HomeRoute extends Route { */ public async init(io: Server) { this.io = io; + + io.on("connection", (socket) => { + 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()); + } 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.GQLPOSTCREATE, async (post: Post) => { + socket.emit("post", await post.resolvedData()); + }); + }); + + const chats = await dataaccess.getAllChats(); + for (const chat of chats) { + chatRooms[chat.id] = this.getChatSocketNamespace(chat.id); + } + globals.internalEmitter.on(InternalEvents.CHATCREATE, (chat: Chatroom) => { + chatRooms[chat.id] = this.getChatSocketNamespace(chat.id); + }); } /** @@ -55,7 +87,7 @@ class HomeRoute extends Route { return new NotLoggedInGqlError(); } }, - async getUser({userId, handle}: {userId: number, handle: string}) { + async getUser({userId, handle}: { userId: number, handle: string }) { if (handle) { return await dataaccess.getUserByHandle(handle); } else if (userId) { @@ -65,7 +97,7 @@ class HomeRoute extends Route { return new GraphQLError("No userId or handle provided."); } }, - async getPost({postId}: {postId: number}) { + async getPost({postId}: { postId: number }) { if (postId) { return await dataaccess.getPost(postId); } else { @@ -73,7 +105,7 @@ class HomeRoute extends Route { return new GraphQLError("No postId given."); } }, - async getChat({chatId}: {chatId: number}) { + async getChat({chatId}: { chatId: number }) { if (chatId) { return new Chatroom(chatId); } else { @@ -85,7 +117,7 @@ class HomeRoute extends Route { req.session.cookiesAccepted = true; return true; }, - async login({email, passwordHash}: {email: string, passwordHash: string}) { + async login({email, passwordHash}: { email: string, passwordHash: string }) { if (email && passwordHash) { try { const user = await dataaccess.getUserByLogin(email, passwordHash); @@ -110,7 +142,7 @@ class HomeRoute extends Route { return new NotLoggedInGqlError(); } }, - async register({username, email, passwordHash}: {username: string, email: string, passwordHash: string}) { + async register({username, email, passwordHash}: { username: string, email: string, passwordHash: string }) { if (username && email && passwordHash) { if (!is.email(email)) { res.status(status.BAD_REQUEST); @@ -129,7 +161,7 @@ class HomeRoute extends Route { return new GraphQLError("No username, email or password given."); } }, - async vote({postId, type}: {postId: number, type: dataaccess.VoteType}) { + 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); @@ -142,10 +174,12 @@ class HomeRoute extends Route { return new GraphQLError("No postId or type given."); } }, - async createPost({content}: {content: string}) { + async createPost({content}: { content: string }) { if (content) { if (req.session.userId) { - return await dataaccess.createPost(content, req.session.userId); + const post = await dataaccess.createPost(content, req.session.userId); + globals.internalEmitter.emit(InternalEvents.GQLPOSTCREATE, post); + return post; } else { res.status(status.UNAUTHORIZED); return new NotLoggedInGqlError(); @@ -155,7 +189,7 @@ class HomeRoute extends Route { return new GraphQLError("Can't create empty post."); } }, - async deletePost({postId}: {postId: number}) { + async deletePost({postId}: { postId: number }) { if (postId) { const post = new Post(postId); if ((await post.author()).id === req.session.userId) { @@ -168,27 +202,28 @@ class HomeRoute extends Route { return new GraphQLError("No postId given."); } }, - async createChat({members}: {members: number[]}) { + async createChat({members}: { members: number[] }) { if (req.session.userId) { const chatMembers = [req.session.userId]; if (members) { chatMembers.push(...members); } return await dataaccess.createChat(...chatMembers); - } else { res.status(status.UNAUTHORIZED); return new NotLoggedInGqlError(); } }, - async sendMessage({chatId, content}: {chatId: number, content: string}) { + async sendMessage({chatId, content}: { chatId: number, content: string }) { if (!req.session.userId) { res.status(status.UNAUTHORIZED); return new NotLoggedInGqlError(); } if (chatId && content) { try { - return await dataaccess.sendChatMessage(req.session.userId, chatId, content); + const message = await dataaccess.sendChatMessage(req.session.userId, chatId, content); + globals.internalEmitter.emit(InternalEvents.GQLCHATMESSAGE, message); + return message; } catch (err) { res.status(status.BAD_REQUEST); return err.graphqlError; @@ -198,7 +233,7 @@ class HomeRoute extends Route { return new GraphQLError("No chatId or content given."); } }, - async sendRequest({receiver, type}: {receiver: number, type: dataaccess.RequestType}) { + async sendRequest({receiver, type}: { receiver: number, type: dataaccess.RequestType }) { if (!req.session.userId) { res.status(status.UNAUTHORIZED); return new NotLoggedInGqlError(); @@ -210,7 +245,7 @@ class HomeRoute extends Route { return new GraphQLError("No receiver or type given."); } }, - async denyRequest({sender, type}: {sender: number, type: dataaccess.RequestType}) { + async denyRequest({sender, type}: { sender: number, type: dataaccess.RequestType }) { if (!req.session.userId) { res.status(status.UNAUTHORIZED); return new NotLoggedInGqlError(); @@ -224,7 +259,7 @@ class HomeRoute extends Route { return new GraphQLError("No sender or type given."); } }, - async acceptRequest({sender, type}: {sender: number, type: dataaccess.RequestType}) { + async acceptRequest({sender, type}: { sender: number, type: dataaccess.RequestType }) { if (!req.session.userId) { res.status(status.UNAUTHORIZED); return new NotLoggedInGqlError(); @@ -245,6 +280,35 @@ class HomeRoute extends Route { }, }; } + + /** + * Returns the namespace socket for a chat socket. + * @param chatId + */ + private getChatSocketNamespace(chatId: number) { + if (chatRooms[chatId]) { + return chatRooms[chatId]; + } + const chatNs = this.io.of(`/chat/${chatId}`); + chatNs.on("connection", (socket) => { + socket.on("chatMessage", async (content) => { + 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()); + } else { + socket.emit("error", "Not logged in!"); + } + }); + globals.internalEmitter.on(InternalEvents.GQLCHATMESSAGE, (message: ChatMessage) => { + if (message.chat.id === chatId) { + socket.emit("chatMessage", message.resolvedContent()); + } + }); + }); + return chatNs; + } } export default HomeRoute; From bc8455f84b37094a606f7e6f6fac35d3e9dedd2a Mon Sep 17 00:00:00 2001 From: Trivernis Date: Tue, 1 Oct 2019 18:11:01 +0200 Subject: [PATCH 38/40] Errors and Structure - moved graphql stuff to a seperate directory - added error for already existing email in database --- src/app.ts | 5 +- src/graphql/resolvers.ts | 227 ++++++++++++++++++ src/{public => }/graphql/schema.graphql | 0 src/lib/Route.ts | 1 - src/lib/dataaccess/index.ts | 17 +- src/lib/errors/EmailAlreadyRegisteredError.ts | 8 + src/lib/regex.ts | 4 +- src/routes/home.ts | 219 +---------------- src/routes/index.ts | 10 - 9 files changed, 256 insertions(+), 235 deletions(-) create mode 100644 src/graphql/resolvers.ts rename src/{public => }/graphql/schema.graphql (100%) create mode 100644 src/lib/errors/EmailAlreadyRegisteredError.ts diff --git a/src/app.ts b/src/app.ts index 5801771..d7c3641 100644 --- a/src/app.ts +++ b/src/app.ts @@ -10,6 +10,7 @@ import {importSchema} from "graphql-import"; import * as http from "http"; import * as path from "path"; import * as socketIo from "socket.io"; +import {resolver} from "./graphql/resolvers"; import dataaccess, {queryHelper} from "./lib/dataaccess"; import globals from "./lib/globals"; import routes from "./routes"; @@ -72,8 +73,8 @@ class App { // @ts-ignore all context: {session: request.session}, graphiql: true, - rootValue: routes.resolvers(request, response), - schema: buildSchema(importSchema(path.join(__dirname, "./public/graphql/schema.graphql"))), + rootValue: resolver(request, response), + schema: buildSchema(importSchema(path.join(__dirname, "./graphql/schema.graphql"))), }; })); } diff --git a/src/graphql/resolvers.ts b/src/graphql/resolvers.ts new file mode 100644 index 0000000..943da91 --- /dev/null +++ b/src/graphql/resolvers.ts @@ -0,0 +1,227 @@ +import {GraphQLError} from "graphql"; +import * as status from "http-status"; +import dataaccess from "../lib/dataaccess"; +import {Chatroom} from "../lib/dataaccess/Chatroom"; +import {Post} from "../lib/dataaccess/Post"; +import {Profile} from "../lib/dataaccess/Profile"; +import {User} from "../lib/dataaccess/User"; +import {NotLoggedInGqlError} from "../lib/errors/graphqlErrors"; +import globals from "../lib/globals"; +import {InternalEvents} from "../lib/InternalEvents"; +import {is} from "../lib/regex"; + +/** + * Returns the resolvers for the graphql api. + * @param req - the request object + * @param res - the response object + */ +export function resolver(req: any, res: any): any { + return { + getSelf() { + if (req.session.userId) { + return new Profile(req.session.userId); + } else { + res.status(status.UNAUTHORIZED); + return new NotLoggedInGqlError(); + } + }, + async getUser({userId, handle}: { userId: number, handle: string }) { + if (handle) { + return await dataaccess.getUserByHandle(handle); + } else if (userId) { + return new User(userId); + } else { + res.status(status.BAD_REQUEST); + return new GraphQLError("No userId or handle provided."); + } + }, + async getPost({postId}: { postId: number }) { + if (postId) { + return await dataaccess.getPost(postId); + } else { + res.status(status.BAD_REQUEST); + return new GraphQLError("No postId given."); + } + }, + async getChat({chatId}: { chatId: number }) { + if (chatId) { + return new Chatroom(chatId); + } else { + res.status(status.BAD_REQUEST); + return new GraphQLError("No chatId given."); + } + }, + acceptCookies() { + req.session.cookiesAccepted = true; + return true; + }, + async login({email, passwordHash}: { email: string, passwordHash: string }) { + if (email && passwordHash) { + try { + const user = await dataaccess.getUserByLogin(email, passwordHash); + req.session.userId = user.id; + return user; + } catch (err) { + globals.logger.warn(err.message); + globals.logger.debug(err.stack); + res.status(status.BAD_REQUEST); + return err.graphqlError || err.message; + } + } else { + res.status(status.BAD_REQUEST); + return new GraphQLError("No email or password given."); + } + }, + logout() { + if (req.session.user) { + delete req.session.user; + return true; + } else { + res.status(status.UNAUTHORIZED); + return new NotLoggedInGqlError(); + } + }, + async register({username, email, passwordHash}: { username: string, email: string, passwordHash: string }) { + if (username && email && passwordHash) { + if (!is.email(email)) { + res.status(status.BAD_REQUEST); + return new GraphQLError(`'${email}' is not a valid email address!`); + } + try { + const user = await dataaccess.registerUser(username, email, passwordHash); + req.session.userId = user.id; + return user; + } catch (err) { + globals.logger.warn(err.message); + globals.logger.debug(err.stack); + res.status(status.BAD_REQUEST); + return err.graphqlError || err.message; + } + } else { + res.status(status.BAD_REQUEST); + return new GraphQLError("No username, email or password given."); + } + }, + 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); + } else { + res.status(status.UNAUTHORIZED); + return new NotLoggedInGqlError(); + } + } else { + res.status(status.BAD_REQUEST); + return new GraphQLError("No postId or type given."); + } + }, + async createPost({content}: { content: string }) { + if (content) { + if (req.session.userId) { + const post = await dataaccess.createPost(content, req.session.userId); + globals.internalEmitter.emit(InternalEvents.GQLPOSTCREATE, post); + return post; + } else { + res.status(status.UNAUTHORIZED); + return new NotLoggedInGqlError(); + } + } else { + res.status(status.BAD_REQUEST); + return new GraphQLError("Can't create empty post."); + } + }, + async deletePost({postId}: { postId: number }) { + if (postId) { + const post = new Post(postId); + if ((await post.author()).id === req.session.userId) { + return await dataaccess.deletePost(post.id); + } else { + res.status(status.FORBIDDEN); + return new GraphQLError("User is not author of the post."); + } + } else { + return new GraphQLError("No postId given."); + } + }, + async createChat({members}: { members: number[] }) { + if (req.session.userId) { + const chatMembers = [req.session.userId]; + if (members) { + chatMembers.push(...members); + } + return await dataaccess.createChat(...chatMembers); + } else { + res.status(status.UNAUTHORIZED); + return new NotLoggedInGqlError(); + } + }, + async sendMessage({chatId, content}: { chatId: number, content: string }) { + if (!req.session.userId) { + res.status(status.UNAUTHORIZED); + return new NotLoggedInGqlError(); + } + if (chatId && content) { + try { + const message = await dataaccess.sendChatMessage(req.session.userId, chatId, content); + globals.internalEmitter.emit(InternalEvents.GQLCHATMESSAGE, message); + return message; + } catch (err) { + globals.logger.warn(err.message); + globals.logger.debug(err.stack); + res.status(status.BAD_REQUEST); + return err.graphqlError || err.message; + } + } else { + res.status(status.BAD_REQUEST); + return new GraphQLError("No chatId or content given."); + } + }, + async sendRequest({receiver, type}: { receiver: number, type: dataaccess.RequestType }) { + if (!req.session.userId) { + res.status(status.UNAUTHORIZED); + return new NotLoggedInGqlError(); + } + if (receiver && type) { + return await dataaccess.createRequest(req.session.userId, receiver, type); + } else { + res.status(status.BAD_REQUEST); + return new GraphQLError("No receiver or type given."); + } + }, + async denyRequest({sender, type}: { sender: number, type: dataaccess.RequestType }) { + if (!req.session.userId) { + res.status(status.UNAUTHORIZED); + return new NotLoggedInGqlError(); + } + if (sender && type) { + const profile = new Profile(req.session.userId); + await profile.denyRequest(sender, type); + return true; + } else { + res.status(status.BAD_REQUEST); + return new GraphQLError("No sender or type given."); + } + }, + async acceptRequest({sender, type}: { sender: number, type: dataaccess.RequestType }) { + if (!req.session.userId) { + res.status(status.UNAUTHORIZED); + return new NotLoggedInGqlError(); + } + if (sender && type) { + try { + const profile = new Profile(req.session.userId); + await profile.acceptRequest(sender, type); + return true; + } catch (err) { + globals.logger.warn(err.message); + globals.logger.debug(err.stack); + res.status(status.BAD_REQUEST); + return err.graphqlError || err.message; + } + } else { + res.status(status.BAD_REQUEST); + return new GraphQLError("No sender or type given."); + } + }, + }; +} diff --git a/src/public/graphql/schema.graphql b/src/graphql/schema.graphql similarity index 100% rename from src/public/graphql/schema.graphql rename to src/graphql/schema.graphql diff --git a/src/lib/Route.ts b/src/lib/Route.ts index 63c25ac..37bed62 100644 --- a/src/lib/Route.ts +++ b/src/lib/Route.ts @@ -21,7 +21,6 @@ abstract class Route { public abstract async init(...params: any): Promise; public abstract async destroy(...params: any): Promise; - public abstract async resolver(request: any, response: any): Promise; } export default Route; diff --git a/src/lib/dataaccess/index.ts b/src/lib/dataaccess/index.ts index 46a6649..b160283 100644 --- a/src/lib/dataaccess/index.ts +++ b/src/lib/dataaccess/index.ts @@ -1,5 +1,6 @@ import {Pool} from "pg"; import {ChatNotFoundError} from "../errors/ChatNotFoundError"; +import {EmailAlreadyRegisteredError} from "../errors/EmailAlreadyRegisteredError"; import {UserNotFoundError} from "../errors/UserNotFoundError"; import globals from "../globals"; import {InternalEvents} from "../InternalEvents"; @@ -91,11 +92,19 @@ namespace dataaccess { * @param password */ export async function registerUser(username: string, email: string, password: string) { - 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], + const existResult = await queryHelper.first({ + text: "SELECT email FROM users WHERE email = $1;", + values: [email], }); - return new Profile(result.id, result); + if (!existResult || !existResult.email) { + const result = await queryHelper.first({ + text: "INSERT INTO users (name, handle, password, email) VALUES ($1, $2, $3, $4) RETURNING *", + values: [username, generateHandle(username), password, email], + }); + return new Profile(result.id, result); + } else { + throw new EmailAlreadyRegisteredError(email); + } } /** diff --git a/src/lib/errors/EmailAlreadyRegisteredError.ts b/src/lib/errors/EmailAlreadyRegisteredError.ts new file mode 100644 index 0000000..4a6fa09 --- /dev/null +++ b/src/lib/errors/EmailAlreadyRegisteredError.ts @@ -0,0 +1,8 @@ +import {BaseError} from "./BaseError"; + +export class EmailAlreadyRegisteredError extends BaseError { + constructor(email: string) { + super(`A user for '${email}' does already exist.`); + } + +} diff --git a/src/lib/regex.ts b/src/lib/regex.ts index 1ed6c11..281da4a 100644 --- a/src/lib/regex.ts +++ b/src/lib/regex.ts @@ -1,11 +1,11 @@ export namespace is { - const emailRegex = /\S+?@\S+?(\.\S+?)?\.\w{2,3}(.\w{2-3})?/g + const emailRegex = /\S+?@\S+?(\.\S+?)?\.\w{2,3}(.\w{2-3})?/g; /** * Tests if a string is a valid email. * @param testString */ export function email(testString: string) { - return emailRegex.test(testString) + return emailRegex.test(testString); } } diff --git a/src/routes/home.ts b/src/routes/home.ts index 614a8f7..726b890 100644 --- a/src/routes/home.ts +++ b/src/routes/home.ts @@ -1,20 +1,17 @@ import {Router} from "express"; -import {GraphQLError} from "graphql"; -import * as status from "http-status"; import {Namespace, Server} from "socket.io"; import dataaccess from "../lib/dataaccess"; import {ChatMessage} from "../lib/dataaccess/ChatMessage"; import {Chatroom} from "../lib/dataaccess/Chatroom"; import {Post} from "../lib/dataaccess/Post"; -import {Profile} from "../lib/dataaccess/Profile"; import {Request} from "../lib/dataaccess/Request"; -import {User} from "../lib/dataaccess/User"; -import {NotLoggedInGqlError} from "../lib/errors/graphqlErrors"; import globals from "../lib/globals"; import {InternalEvents} from "../lib/InternalEvents"; -import {is} from "../lib/regex"; import Route from "../lib/Route"; +/** + * list of chatroom socket namespaces. + */ const chatRooms: Namespace[] = []; /** @@ -69,216 +66,6 @@ class HomeRoute extends Route { */ public async destroy(): Promise { this.router = null; - this.resolver = null; - } - - /** - * Returns the resolvers for the graphql api. - * @param req - the request object - * @param res - the response object - */ - public resolver(req: any, res: any): any { - return { - getSelf() { - if (req.session.userId) { - return new Profile(req.session.userId); - } else { - res.status(status.UNAUTHORIZED); - return new NotLoggedInGqlError(); - } - }, - async getUser({userId, handle}: { userId: number, handle: string }) { - if (handle) { - return await dataaccess.getUserByHandle(handle); - } else if (userId) { - return new User(userId); - } else { - res.status(status.BAD_REQUEST); - return new GraphQLError("No userId or handle provided."); - } - }, - async getPost({postId}: { postId: number }) { - if (postId) { - return await dataaccess.getPost(postId); - } else { - res.status(status.BAD_REQUEST); - return new GraphQLError("No postId given."); - } - }, - async getChat({chatId}: { chatId: number }) { - if (chatId) { - return new Chatroom(chatId); - } else { - res.status(status.BAD_REQUEST); - return new GraphQLError("No chatId given."); - } - }, - acceptCookies() { - req.session.cookiesAccepted = true; - return true; - }, - async login({email, passwordHash}: { email: string, passwordHash: string }) { - if (email && passwordHash) { - try { - const user = await dataaccess.getUserByLogin(email, passwordHash); - req.session.userId = user.id; - return user; - } catch (err) { - globals.logger.verbose(`Failed to login user '${email}'`); - res.status(status.BAD_REQUEST); - return err.graphqlError; - } - } else { - res.status(status.BAD_REQUEST); - return new GraphQLError("No email or password given."); - } - }, - logout() { - if (req.session.user) { - delete req.session.user; - return true; - } else { - res.status(status.UNAUTHORIZED); - return new NotLoggedInGqlError(); - } - }, - async register({username, email, passwordHash}: { username: string, email: string, passwordHash: string }) { - if (username && email && passwordHash) { - if (!is.email(email)) { - res.status(status.BAD_REQUEST); - return new GraphQLError(`'${email}' is not a valid email address!`); - } - const user = await dataaccess.registerUser(username, email, passwordHash); - if (user) { - req.session.userId = user.id; - return user; - } else { - res.status(status.INTERNAL_SERVER_ERROR); - return new GraphQLError("Failed to create account."); - } - } else { - res.status(status.BAD_REQUEST); - return new GraphQLError("No username, email or password given."); - } - }, - 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); - } else { - res.status(status.UNAUTHORIZED); - return new NotLoggedInGqlError(); - } - } else { - res.status(status.BAD_REQUEST); - return new GraphQLError("No postId or type given."); - } - }, - async createPost({content}: { content: string }) { - if (content) { - if (req.session.userId) { - const post = await dataaccess.createPost(content, req.session.userId); - globals.internalEmitter.emit(InternalEvents.GQLPOSTCREATE, post); - return post; - } else { - res.status(status.UNAUTHORIZED); - return new NotLoggedInGqlError(); - } - } else { - res.status(status.BAD_REQUEST); - return new GraphQLError("Can't create empty post."); - } - }, - async deletePost({postId}: { postId: number }) { - if (postId) { - const post = new Post(postId); - if ((await post.author()).id === req.session.userId) { - return await dataaccess.deletePost(post.id); - } else { - res.status(status.FORBIDDEN); - return new GraphQLError("User is not author of the post."); - } - } else { - return new GraphQLError("No postId given."); - } - }, - async createChat({members}: { members: number[] }) { - if (req.session.userId) { - const chatMembers = [req.session.userId]; - if (members) { - chatMembers.push(...members); - } - return await dataaccess.createChat(...chatMembers); - } else { - res.status(status.UNAUTHORIZED); - return new NotLoggedInGqlError(); - } - }, - async sendMessage({chatId, content}: { chatId: number, content: string }) { - if (!req.session.userId) { - res.status(status.UNAUTHORIZED); - return new NotLoggedInGqlError(); - } - if (chatId && content) { - try { - const message = await dataaccess.sendChatMessage(req.session.userId, chatId, content); - globals.internalEmitter.emit(InternalEvents.GQLCHATMESSAGE, message); - return message; - } catch (err) { - res.status(status.BAD_REQUEST); - return err.graphqlError; - } - } else { - res.status(status.BAD_REQUEST); - return new GraphQLError("No chatId or content given."); - } - }, - async sendRequest({receiver, type}: { receiver: number, type: dataaccess.RequestType }) { - if (!req.session.userId) { - res.status(status.UNAUTHORIZED); - return new NotLoggedInGqlError(); - } - if (receiver && type) { - return await dataaccess.createRequest(req.session.userId, receiver, type); - } else { - res.status(status.BAD_REQUEST); - return new GraphQLError("No receiver or type given."); - } - }, - async denyRequest({sender, type}: { sender: number, type: dataaccess.RequestType }) { - if (!req.session.userId) { - res.status(status.UNAUTHORIZED); - return new NotLoggedInGqlError(); - } - if (sender && type) { - const profile = new Profile(req.session.userId); - await profile.denyRequest(sender, type); - return true; - } else { - res.status(status.BAD_REQUEST); - return new GraphQLError("No sender or type given."); - } - }, - async acceptRequest({sender, type}: { sender: number, type: dataaccess.RequestType }) { - if (!req.session.userId) { - res.status(status.UNAUTHORIZED); - return new NotLoggedInGqlError(); - } - if (sender && type) { - try { - const profile = new Profile(req.session.userId); - await profile.acceptRequest(sender, type); - return true; - } catch (err) { - res.status(status.BAD_REQUEST); - return err.graphqlError; - } - } else { - res.status(status.BAD_REQUEST); - return new GraphQLError("No sender or type given."); - } - }, - }; } /** diff --git a/src/routes/index.ts b/src/routes/index.ts index eb65750..201d8ea 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -22,16 +22,6 @@ namespace routes { router.use("/", homeRoute.router); - /** - * Asnyc function to create a graphql resolver that takes the request and response - * of express.js as arguments. - * @param request - * @param response - */ - export function resolvers(request: any, response: any): Promise { - return homeRoute.resolver(request, response); - } - /** * Assigns the io listeners or namespaces to the routes * @param io From cbc1609ad457aa43d9f3221b16bf095d103571c8 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Thu, 10 Oct 2019 17:03:18 +0200 Subject: [PATCH 39/40] Added docker compose --- Dockerfile | 11 +++++++++++ docker-compose.yml | 15 +++++++++++++++ package-lock.json | 6 +++--- package.json | 4 +++- 4 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 Dockerfile create mode 100644 docker-compose.yml diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..8921906 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM node:current-alpine + +COPY . /home/node/green +WORKDIR /home/node/green +RUN npm install -g gulp +RUN npm install --save-dev +RUN npm rebuild node-sass +RUN gulp +COPY . . +EXPOSE 8080 +CMD ["npm" , "run"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..b321777 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,15 @@ +version: "2" +services: + greenvironment: + build: + context: . + dockerfile: ./Dockerfile + user: "node" + working_dir: /home/node/green + environment: + - NODE_ENV=production + volumes: + - ./:/home/node/green + expose: + - "8080" + command: "npm start" diff --git a/package-lock.json b/package-lock.json index 7a07454..9142d18 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6934,9 +6934,9 @@ "dev": true }, "type": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/type/-/type-1.0.3.tgz", - "integrity": "sha512-51IMtNfVcee8+9GJvj0spSuFcZHe9vSib6Xtgsny1Km9ugyz2mbS08I3rsUIRYgJohFRFU1160sgRodYz378Hg==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", "dev": true }, "type-is": { diff --git a/package.json b/package.json index 52e9130..215b8cd 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,9 @@ "description": "Server for greenvironment network", "main": "./dist/index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "echo \"Error: no test specified\" && exit 1", + "build": "gulp", + "start": "node ./dist/index.js" }, "repository": { "type": "git", From 3d25186e491fbb68b055dcb9ec53efcfe06bf4ab Mon Sep 17 00:00:00 2001 From: Trivernis Date: Thu, 10 Oct 2019 17:26:40 +0200 Subject: [PATCH 40/40] Fixed docker compose --- Dockerfile | 1 + docker-compose.yml | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 8921906..9d6ca3e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,4 +8,5 @@ RUN npm rebuild node-sass RUN gulp COPY . . EXPOSE 8080 +EXPOSE 5432 CMD ["npm" , "run"] diff --git a/docker-compose.yml b/docker-compose.yml index b321777..00e4d8c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,4 @@ -version: "2" +version: "3" services: greenvironment: build: @@ -10,6 +10,6 @@ services: - NODE_ENV=production volumes: - ./:/home/node/green - expose: - - "8080" + ports: + - "8080:8080" command: "npm start"