diff --git a/.gitignore b/.gitignore index 57a246b1..3ecfd721 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ node_modules #config files ormconfig.json src/config.ts +prisma/.env # code testing coverage coverage diff --git a/README.md b/README.md index 0716db51..2c095c3b 100644 --- a/README.md +++ b/README.md @@ -31,13 +31,24 @@ Copy config file and set JsonWebToken secret key ## Database -The example codebase uses [Typeorm](http://typeorm.io/) with a mySQL database. +The codebase contains examples of two different database abstractions, namely [TypeORM](http://typeorm.io/) and [Prisma](https://www.prisma.io/). + +The branch `master` implements TypeORM with a mySQL database. + +The branch `prisma` implements Prisma with a mySQL database. + +---------- + +##### TypeORM + +---------- -Create a new mysql database with the name `nestjsrealworld` (or the name you specified in the ormconfig.json) +Create a new mysql database with the name `nestjsrealworld`\ +(or the name you specified in the ormconfig.json) -Copy Typeorm config example file for database settings +Copy TypeORM config example file for database settings - cp ormconfig.json.example `` + cp ormconfig.json.example Set mysql database settings in ormconfig.json @@ -56,6 +67,38 @@ Start local mysql server and create new database 'nestjsrealworld' On application start, tables for all entities will be created. +---------- + +##### Prisma + +---------- + +To run the example with Prisma checkout branch `prisma`, remove the node_modules and run `npm install` + +Create a new mysql database with the name `nestjsrealworld-prisma` (or the name you specified in `prisma/.env`) + +Copy prisma config example file for database settings + + cp prisma/.env.example prisma/.env + +Set mysql database settings in prisma/.env + + DATABASE_URL="mysql://USER:PASSWORD@HOST:PORT/DATABASE" + +To create all tables in the new database make the database migration from the prisma schema defined in prisma/schema.prisma + + npx prisma migrate save --experimental + npx prisma migrate up --experimental + +Now generate the prisma client from the migrated database with the following command + + npx prisma generate + +The database tables are now set up and the prisma client is generated. For more information see the docs: + +- https://www.prisma.io/docs/getting-started/setup-prisma/add-to-existing-project-typescript-mysql + + ---------- ## NPM scripts diff --git a/ormconfig.json.example b/ormconfig.json.example deleted file mode 100644 index ce23ee69..00000000 --- a/ormconfig.json.example +++ /dev/null @@ -1,10 +0,0 @@ -{ - "type": "mysql", - "host": "localhost", - "port": 3306, - "username": "your-mysql-username", - "password": "your-mysql-password", - "database": "nestjsrealworld", - "entities": ["src/**/**.entity{.ts,.js}"], - "synchronize": true -} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index f898f9d3..e995a99b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1128,21 +1128,6 @@ } } }, - "@nestjs/typeorm": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@nestjs/typeorm/-/typeorm-7.0.0.tgz", - "integrity": "sha512-oIiQRihMVNQPE6PzEaB7UDfGlktCtkHNxI2HeSf/Uyrjl7DspPSHNLg9IVVSztk+L9kJquYMXhzApr2PxoK3EQ==", - "requires": { - "uuid": "7.0.2" - }, - "dependencies": { - "uuid": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.2.tgz", - "integrity": "sha512-vy9V/+pKG+5ZTYKf+VcphF5Oc6EFiu3W8Nv3P3zIh0EqVI80ZxOzuPfe9EHjkFNvf8+xuTHVeei4Drydlx4zjw==" - } - } - }, "@nestjs/websockets": { "version": "7.0.5", "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-7.0.5.tgz", @@ -1212,6 +1197,17 @@ } } }, + "@prisma/cli": { + "version": "2.0.0-beta.4", + "resolved": "https://registry.npmjs.org/@prisma/cli/-/cli-2.0.0-beta.4.tgz", + "integrity": "sha512-z9si4cmn/dN7bxVx3hbdDFL6fQuosTzj45EhVrCYt6VCWcJ5jYn0Fzamr+npZvbR/Gl6eLhexXP1sa7iGe8gjA==", + "dev": true + }, + "@prisma/client": { + "version": "2.0.0-beta.4", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-2.0.0-beta.4.tgz", + "integrity": "sha512-LEtHIQVJrM7Y8H0G288sKxvsFv1od7tG8SiaWQvzv51AZo7IsK+n+wJ8A1/tJ6eL/7UQWHZQjZN7JLgpxetFvw==" + }, "@sinonjs/commons": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.7.2.tgz", @@ -1265,7 +1261,8 @@ "@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", + "dev": true }, "@types/graceful-fs": { "version": "4.1.3", @@ -1452,15 +1449,11 @@ "version": "3.2.0", "resolved": "https://npm.styque.de/ansi-styles/-/ansi-styles-3.2.0.tgz", "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", + "dev": true, "requires": { "color-convert": "^1.9.0" } }, - "any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" - }, "anymatch": { "version": "2.0.0", "resolved": "https://npm.styque.de/anymatch/-/anymatch-2.0.0.tgz", @@ -1471,11 +1464,6 @@ "normalize-path": "^2.1.1" } }, - "app-root-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.0.0.tgz", - "integrity": "sha512-qMcx+Gy2UZynHjOHOIXPNvpf+9cjvk3cWrBBK7zg4gH9+clobJRb9NGzcT7mQTcV/6Gm/1WelUtqxVXnNlrwcw==" - }, "append-field": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", @@ -1515,6 +1503,7 @@ "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" } @@ -1765,11 +1754,6 @@ "pascalcase": "^0.1.1" } }, - "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==" - }, "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -1916,15 +1900,6 @@ "node-int64": "^0.4.0" } }, - "buffer": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.5.0.tgz", - "integrity": "sha512-9FTEDjLjwoAkEwyMGDjYJQN2gfRgOKBKRfiglhvibGbpeeU/pQn1bJxQqm32OD/AIeEuHxU9roxXxg34Byp/Ww==", - "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4" - } - }, "buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://npm.styque.de/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", @@ -2204,120 +2179,11 @@ "timers-ext": "^0.1.7" } }, - "cli-highlight": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.4.tgz", - "integrity": "sha512-s7Zofobm20qriqDoU9sXptQx0t2R9PEgac92mENNm7xaEe1hn71IIMsXMK+6encA6WRCWWxIGQbipr3q998tlQ==", - "requires": { - "chalk": "^3.0.0", - "highlight.js": "^9.6.0", - "mz": "^2.4.0", - "parse5": "^5.1.1", - "parse5-htmlparser2-tree-adapter": "^5.1.1", - "yargs": "^15.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" - }, - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" - }, - "parse5": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", - "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==" - }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "requires": { - "has-flag": "^4.0.0" - } - }, - "yargs": { - "version": "15.3.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz", - "integrity": "sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==", - "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.1" - } - } - } - }, "cliui": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, "requires": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", @@ -2327,17 +2193,20 @@ "ansi-regex": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true }, "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true }, "string-width": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -2348,6 +2217,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, "requires": { "ansi-regex": "^5.0.0" } @@ -2613,7 +2483,8 @@ "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true }, "decode-uri-component": { "version": "0.2.0", @@ -2741,11 +2612,6 @@ "is-obj": "^1.0.0" } }, - "dotenv": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-6.2.0.tgz", - "integrity": "sha512-HygQCKUBSFl8wKQZBSemMywRWcEDNidvNbjGVyZu3nbZ8qq9ubiPoGLMdRDpfSrpkkm9BXYFkpKxxFX38o/76w==" - }, "duplexer3": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", @@ -2778,7 +2644,8 @@ "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true }, "encodeurl": { "version": "1.0.2", @@ -2878,7 +2745,8 @@ "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true }, "estraverse": { "version": "4.3.0", @@ -3212,11 +3080,6 @@ "bser": "2.1.1" } }, - "figlet": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/figlet/-/figlet-1.3.0.tgz", - "integrity": "sha512-f7A8aOJAfyehLJ7lQ6rEA8WJw7kOk3lfWRi5piSjkzbK5YkI5sqO8eiLHz1ehO+DM0QYB85i8VfA6XIGUbU1dg==" - }, "file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", @@ -3264,6 +3127,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, "requires": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -3272,7 +3136,8 @@ "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true } } }, @@ -3936,7 +3801,8 @@ "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true }, "get-stream": { "version": "3.0.0", @@ -4061,14 +3927,6 @@ "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=", - "requires": { - "ansi-regex": "^2.0.0" - } - }, "has-flag": { "version": "3.0.0", "resolved": "https://npm.styque.de/has-flag/-/has-flag-3.0.0.tgz", @@ -4111,11 +3969,6 @@ } } }, - "highlight.js": { - "version": "9.18.1", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.18.1.tgz", - "integrity": "sha512-OrVKYz70LHsnCgmbXctv/bfuvntIKDz177h0Co37DQ5jamGZLVmoCVMtjMtNZY3X9DrCcKfklHPNeA0uPZhSJg==" - }, "hosted-git-info": { "version": "2.8.8", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", @@ -4174,11 +4027,6 @@ "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==" - }, "ignore-by-default": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", @@ -6224,6 +6072,7 @@ "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" @@ -6437,6 +6286,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, "requires": { "p-locate": "^4.1.0" } @@ -6769,16 +6619,6 @@ } } }, - "mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "requires": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" - } - }, "nan": { "version": "2.14.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", @@ -7258,6 +7098,7 @@ "version": "2.2.2", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", + "dev": true, "requires": { "p-try": "^2.0.0" } @@ -7266,6 +7107,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, "requires": { "p-limit": "^2.2.0" } @@ -7273,7 +7115,8 @@ "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true }, "package-json": { "version": "4.0.1", @@ -7287,11 +7130,6 @@ "semver": "^5.1.0" } }, - "parent-require": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/parent-require/-/parent-require-1.0.0.tgz", - "integrity": "sha1-dGoWdjgIOoYLDu9nMssn7UbDKXc=" - }, "parse-json": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", @@ -7310,21 +7148,6 @@ "integrity": "sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==", "dev": true }, - "parse5-htmlparser2-tree-adapter": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-5.1.1.tgz", - "integrity": "sha512-CF+TKjXqoqyDwHqBhFQ+3l5t83xYi6fVT1tQNg+Ye0JRLnTxWvIroCjEp1A0k4lneHNBGnICUf0cfYVYGEazqw==", - "requires": { - "parse5": "^5.1.1" - }, - "dependencies": { - "parse5": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", - "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==" - } - } - }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -7412,11 +7235,6 @@ "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", "dev": true }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" - }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://npm.styque.de/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -7974,12 +7792,14 @@ "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true }, "require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true }, "resolve": { "version": "1.17.0", @@ -8232,15 +8052,6 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" }, - "sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, "shebang-command": { "version": "1.2.0", "resolved": "https://npm.styque.de/shebang-command/-/shebang-command-1.2.0.tgz", @@ -8520,7 +8331,8 @@ "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true }, "sqlstring": { "version": "2.3.1", @@ -8881,22 +8693,6 @@ } } }, - "thenify": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.0.tgz", - "integrity": "sha1-5p44obq+lpsBCCB5eLn2K4hgSDk=", - "requires": { - "any-promise": "^1.0.0" - } - }, - "thenify-all": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=", - "requires": { - "thenify": ">= 3.1.0 < 4" - } - }, "throat": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", @@ -9231,69 +9027,6 @@ "is-typedarray": "^1.0.0" } }, - "typeorm": { - "version": "0.2.24", - "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.2.24.tgz", - "integrity": "sha512-L9tQv6nNLRyh+gex/qc8/CyLs8u0kXKqk1OjYGF13k/KOg6N2oibwkuGgv0FuoTGYx2ta2NmqvuMUAMrHIY5ew==", - "requires": { - "app-root-path": "^3.0.0", - "buffer": "^5.1.0", - "chalk": "^2.4.2", - "cli-highlight": "^2.0.0", - "debug": "^4.1.1", - "dotenv": "^6.2.0", - "glob": "^7.1.2", - "js-yaml": "^3.13.1", - "mkdirp": "^0.5.1", - "reflect-metadata": "^0.1.13", - "sha.js": "^2.4.11", - "tslib": "^1.9.0", - "xml2js": "^0.4.17", - "yargonaut": "^1.1.2", - "yargs": "^13.2.1" - }, - "dependencies": { - "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==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "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==" - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, "typescript": { "version": "3.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz", @@ -9699,7 +9432,8 @@ "which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true }, "wide-align": { "version": "1.1.3", @@ -9728,6 +9462,7 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, "requires": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -9737,12 +9472,14 @@ "ansi-regex": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true }, "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, "requires": { "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" @@ -9752,6 +9489,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "requires": { "color-name": "~1.1.4" } @@ -9759,17 +9497,20 @@ "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true }, "string-width": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -9780,6 +9521,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, "requires": { "ansi-regex": "^5.0.0" } @@ -9820,20 +9562,6 @@ "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", "dev": true }, - "xml2js": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", - "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", - "requires": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - } - }, - "xmlbuilder": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" - }, "xmlchars": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", @@ -9848,7 +9576,8 @@ "y18n": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true }, "yallist": { "version": "2.1.2", @@ -9856,173 +9585,11 @@ "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", "dev": true }, - "yargonaut": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/yargonaut/-/yargonaut-1.1.4.tgz", - "integrity": "sha512-rHgFmbgXAAzl+1nngqOcwEljqHGG9uUZoPjsdZEs1w5JW9RXYzrSvH/u70C1JE5qFi0qjsdhnUX/dJRpWqitSA==", - "requires": { - "chalk": "^1.1.1", - "figlet": "^1.1.1", - "parent-require": "^1.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "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=", - "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=" - } - } - }, - "yargs": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.0.tgz", - "integrity": "sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==", - "requires": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.1.1" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" - }, - "cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", - "requires": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" - } - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "requires": { - "locate-path": "^3.0.0" - } - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "requires": { - "p-limit": "^2.0.0" - } - }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "requires": { - "ansi-regex": "^4.1.0" - } - }, - "wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", - "requires": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" - } - }, - "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" - }, - "yargs-parser": { - "version": "13.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz", - "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - } - } - }, "yargs-parser": { "version": "18.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.1.tgz", "integrity": "sha512-KRHEsOM16IX7XuLnMOqImcPNbLVXMNHYAoFc3BKR8Ortl5gzDbtXvvEoGx9imk5E+X1VeNKNlcHr8B8vi+7ipA==", + "dev": true, "requires": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" @@ -10031,7 +9598,8 @@ "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true } } }, diff --git a/package.json b/package.json index 734ace61..706af2e4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nestjs-realworld-example-app", - "version": "1.1.4", + "version": "2.0.0", "description": "[![Build Status](https://travis-ci.org/anishkny/node-express-realworld-example-app.svg?branch=master)](https://travis-ci.org/anishkny/node-express-realworld-example-app)", "main": "index.js", "scripts": { @@ -29,8 +29,8 @@ "@nestjs/platform-express": "^7.0.5", "@nestjs/swagger": "^4.4.0", "@nestjs/testing": "^7.0.5", - "@nestjs/typeorm": "^7.0.0", "@nestjs/websockets": "^7.0.5", + "@prisma/client": "^2.0.0-beta.4", "argon2": "^0.26.2", "class-transformer": "^0.2.3", "class-validator": "^0.11.1", @@ -43,10 +43,10 @@ "rxjs": "^6.5.5", "slug": "^1.1.0", "swagger-ui-express": "^4.1.4", - "typeorm": "^0.2.24", "typescript": "^3.8.3" }, "devDependencies": { + "@prisma/cli": "^2.0.0-beta.4", "@types/jest": "^25.2.1", "@types/node": "^13.13.4", "atob": ">=2.1.0", diff --git a/prisma/.env.example b/prisma/.env.example new file mode 100644 index 00000000..665a51bc --- /dev/null +++ b/prisma/.env.example @@ -0,0 +1,8 @@ +# Environment variables declared in this file are automatically made available to Prisma. +# See the documentation for more detail: https://pris.ly/d/prisma-schema#using-environment-variables + +# Prisma supports the native connection string format for PostgreSQL, MySQL and SQLite. +# See the documentation for all the connection string options: https://pris.ly/d/connection-strings + +# https://www.prisma.io/docs/reference/database-connectors/mysql +DATABASE_URL="mysql://USER:PASSWORD@HOST:PORT/DATABASE" \ No newline at end of file diff --git a/prisma/migrations/20200501152600/README.md b/prisma/migrations/20200501152600/README.md new file mode 100644 index 00000000..beb45997 --- /dev/null +++ b/prisma/migrations/20200501152600/README.md @@ -0,0 +1,87 @@ +# Migration `20200501152600` + +This migration has been generated by Lukas Jakob at 5/1/2020, 3:26:00 PM. +You can check out the [state of the schema](./schema.prisma) after the migration. + +## Database Steps + +```sql +CREATE TABLE `nestjsrealworld-prisma`.`User` ( +`bio` varchar(191) ,`email` varchar(191) NOT NULL ,`id` int NOT NULL AUTO_INCREMENT,`image` varchar(191) ,`password` varchar(191) ,`username` varchar(191) , + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci + +CREATE TABLE `nestjsrealworld-prisma`.`Article` ( +`authorId` int NOT NULL ,`body` varchar(191) NOT NULL ,`createdAt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ,`description` varchar(191) NOT NULL ,`favoriteCount` int NOT NULL ,`id` int NOT NULL AUTO_INCREMENT,`slug` varchar(191) NOT NULL ,`tagList` varchar(191) NOT NULL ,`title` varchar(191) NOT NULL ,`updatedAt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP , + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci + +CREATE TABLE `nestjsrealworld-prisma`.`_UserFavorites` ( +`A` int NOT NULL ,`B` int NOT NULL +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci + +CREATE UNIQUE INDEX `User.email` ON `nestjsrealworld-prisma`.`User`(`email`) + +CREATE UNIQUE INDEX `User.username` ON `nestjsrealworld-prisma`.`User`(`username`) + +CREATE UNIQUE INDEX `Article.slug` ON `nestjsrealworld-prisma`.`Article`(`slug`) + +CREATE UNIQUE INDEX `_UserFavorites_AB_unique` ON `nestjsrealworld-prisma`.`_UserFavorites`(`A`,`B`) + +CREATE INDEX `_UserFavorites_B_index` ON `nestjsrealworld-prisma`.`_UserFavorites`(`B`) + +ALTER TABLE `nestjsrealworld-prisma`.`Article` ADD FOREIGN KEY (`authorId`) REFERENCES `nestjsrealworld-prisma`.`User`(`id`) ON DELETE CASCADE ON UPDATE CASCADE + +ALTER TABLE `nestjsrealworld-prisma`.`_UserFavorites` ADD FOREIGN KEY (`A`) REFERENCES `nestjsrealworld-prisma`.`Article`(`id`) ON DELETE CASCADE ON UPDATE CASCADE + +ALTER TABLE `nestjsrealworld-prisma`.`_UserFavorites` ADD FOREIGN KEY (`B`) REFERENCES `nestjsrealworld-prisma`.`User`(`id`) ON DELETE CASCADE ON UPDATE CASCADE +``` + +## Changes + +```diff +diff --git schema.prisma schema.prisma +migration ..20200501152600 +--- datamodel.dml ++++ datamodel.dml +@@ -1,0 +1,37 @@ ++// This is your Prisma schema file, ++// learn more about it in the docs: https://pris.ly/d/prisma-schema ++ ++datasource db { ++ provider = "mysql" ++ url = env("DATABASE_URL") ++} ++ ++generator client { ++ provider = "prisma-client-js" ++} ++ ++model User { ++ bio String? ++ email String @unique ++ id Int @default(autoincrement()) @id ++ image String? ++ password String? ++ username String? @unique ++ articles Article[] @relation("UserArticles") ++ favorites Article[] @relation("UserFavorites", references: [id]) ++} ++ ++model Article { ++ id Int @default(autoincrement()) @id ++ slug String @unique ++ title String ++ description String ++ body String ++ createdAt DateTime @default(now()) ++ updatedAt DateTime @default(now()) ++ tagList String ++ favoriteCount Int ++ author User @relation("UserArticles", fields: [authorId], references: [id]) ++ authorId Int ++ favoritedBy User[] @relation("UserFavorites", references: [id]) ++} +``` + + diff --git a/prisma/migrations/20200501152600/schema.prisma b/prisma/migrations/20200501152600/schema.prisma new file mode 100644 index 00000000..18f1663b --- /dev/null +++ b/prisma/migrations/20200501152600/schema.prisma @@ -0,0 +1,37 @@ +// This is your Prisma schema file, +// learn more about it in the docs: https://pris.ly/d/prisma-schema + +datasource db { + provider = "mysql" + url = "***" +} + +generator client { + provider = "prisma-client-js" +} + +model User { + bio String? + email String @unique + id Int @default(autoincrement()) @id + image String? + password String? + username String? @unique + articles Article[] @relation("UserArticles") + favorites Article[] @relation("UserFavorites", references: [id]) +} + +model Article { + id Int @default(autoincrement()) @id + slug String @unique + title String + description String + body String + createdAt DateTime @default(now()) + updatedAt DateTime @default(now()) + tagList String + favoriteCount Int + author User @relation("UserArticles", fields: [authorId], references: [id]) + authorId Int + favoritedBy User[] @relation("UserFavorites", references: [id]) +} \ No newline at end of file diff --git a/prisma/migrations/20200501152600/steps.json b/prisma/migrations/20200501152600/steps.json new file mode 100644 index 00000000..73823f61 --- /dev/null +++ b/prisma/migrations/20200501152600/steps.json @@ -0,0 +1,486 @@ +{ + "version": "0.3.14-fixed", + "steps": [ + { + "tag": "CreateSource", + "source": "db" + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Source", + "source": "db" + }, + "argument": "provider", + "value": "\"mysql\"" + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Source", + "source": "db" + }, + "argument": "url", + "value": "env(\"DATABASE_URL\")" + }, + { + "tag": "CreateModel", + "model": "User" + }, + { + "tag": "CreateField", + "model": "User", + "field": "bio", + "type": "String", + "arity": "Optional" + }, + { + "tag": "CreateField", + "model": "User", + "field": "email", + "type": "String", + "arity": "Required" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "User", + "field": "email" + }, + "directive": "unique" + } + }, + { + "tag": "CreateField", + "model": "User", + "field": "id", + "type": "Int", + "arity": "Required" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "User", + "field": "id" + }, + "directive": "default" + } + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Directive", + "path": { + "tag": "Field", + "model": "User", + "field": "id" + }, + "directive": "default" + }, + "argument": "", + "value": "autoincrement()" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "User", + "field": "id" + }, + "directive": "id" + } + }, + { + "tag": "CreateField", + "model": "User", + "field": "image", + "type": "String", + "arity": "Optional" + }, + { + "tag": "CreateField", + "model": "User", + "field": "password", + "type": "String", + "arity": "Optional" + }, + { + "tag": "CreateField", + "model": "User", + "field": "username", + "type": "String", + "arity": "Optional" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "User", + "field": "username" + }, + "directive": "unique" + } + }, + { + "tag": "CreateField", + "model": "User", + "field": "articles", + "type": "Article", + "arity": "List" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "User", + "field": "articles" + }, + "directive": "relation" + } + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Directive", + "path": { + "tag": "Field", + "model": "User", + "field": "articles" + }, + "directive": "relation" + }, + "argument": "", + "value": "\"UserArticles\"" + }, + { + "tag": "CreateField", + "model": "User", + "field": "favorites", + "type": "Article", + "arity": "List" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "User", + "field": "favorites" + }, + "directive": "relation" + } + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Directive", + "path": { + "tag": "Field", + "model": "User", + "field": "favorites" + }, + "directive": "relation" + }, + "argument": "", + "value": "\"UserFavorites\"" + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Directive", + "path": { + "tag": "Field", + "model": "User", + "field": "favorites" + }, + "directive": "relation" + }, + "argument": "references", + "value": "[id]" + }, + { + "tag": "CreateModel", + "model": "Article" + }, + { + "tag": "CreateField", + "model": "Article", + "field": "id", + "type": "Int", + "arity": "Required" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "Article", + "field": "id" + }, + "directive": "default" + } + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Directive", + "path": { + "tag": "Field", + "model": "Article", + "field": "id" + }, + "directive": "default" + }, + "argument": "", + "value": "autoincrement()" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "Article", + "field": "id" + }, + "directive": "id" + } + }, + { + "tag": "CreateField", + "model": "Article", + "field": "slug", + "type": "String", + "arity": "Required" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "Article", + "field": "slug" + }, + "directive": "unique" + } + }, + { + "tag": "CreateField", + "model": "Article", + "field": "title", + "type": "String", + "arity": "Required" + }, + { + "tag": "CreateField", + "model": "Article", + "field": "description", + "type": "String", + "arity": "Required" + }, + { + "tag": "CreateField", + "model": "Article", + "field": "body", + "type": "String", + "arity": "Required" + }, + { + "tag": "CreateField", + "model": "Article", + "field": "createdAt", + "type": "DateTime", + "arity": "Required" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "Article", + "field": "createdAt" + }, + "directive": "default" + } + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Directive", + "path": { + "tag": "Field", + "model": "Article", + "field": "createdAt" + }, + "directive": "default" + }, + "argument": "", + "value": "now()" + }, + { + "tag": "CreateField", + "model": "Article", + "field": "updatedAt", + "type": "DateTime", + "arity": "Required" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "Article", + "field": "updatedAt" + }, + "directive": "default" + } + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Directive", + "path": { + "tag": "Field", + "model": "Article", + "field": "updatedAt" + }, + "directive": "default" + }, + "argument": "", + "value": "now()" + }, + { + "tag": "CreateField", + "model": "Article", + "field": "tagList", + "type": "String", + "arity": "Required" + }, + { + "tag": "CreateField", + "model": "Article", + "field": "favoriteCount", + "type": "Int", + "arity": "Required" + }, + { + "tag": "CreateField", + "model": "Article", + "field": "author", + "type": "User", + "arity": "Required" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "Article", + "field": "author" + }, + "directive": "relation" + } + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Directive", + "path": { + "tag": "Field", + "model": "Article", + "field": "author" + }, + "directive": "relation" + }, + "argument": "", + "value": "\"UserArticles\"" + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Directive", + "path": { + "tag": "Field", + "model": "Article", + "field": "author" + }, + "directive": "relation" + }, + "argument": "fields", + "value": "[authorId]" + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Directive", + "path": { + "tag": "Field", + "model": "Article", + "field": "author" + }, + "directive": "relation" + }, + "argument": "references", + "value": "[id]" + }, + { + "tag": "CreateField", + "model": "Article", + "field": "authorId", + "type": "Int", + "arity": "Required" + }, + { + "tag": "CreateField", + "model": "Article", + "field": "favoritedBy", + "type": "User", + "arity": "List" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "Article", + "field": "favoritedBy" + }, + "directive": "relation" + } + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Directive", + "path": { + "tag": "Field", + "model": "Article", + "field": "favoritedBy" + }, + "directive": "relation" + }, + "argument": "", + "value": "\"UserFavorites\"" + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Directive", + "path": { + "tag": "Field", + "model": "Article", + "field": "favoritedBy" + }, + "directive": "relation" + }, + "argument": "references", + "value": "[id]" + } + ] +} \ No newline at end of file diff --git a/prisma/migrations/20200501152654/README.md b/prisma/migrations/20200501152654/README.md new file mode 100644 index 00000000..d5212f37 --- /dev/null +++ b/prisma/migrations/20200501152654/README.md @@ -0,0 +1,50 @@ +# Migration `20200501152654` + +This migration has been generated by Lukas Jakob at 5/1/2020, 3:26:54 PM. +You can check out the [state of the schema](./schema.prisma) after the migration. + +## Database Steps + +```sql +CREATE TABLE `nestjsrealworld-prisma`.`_UserFollows` ( +`A` int NOT NULL ,`B` int NOT NULL +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci + +CREATE UNIQUE INDEX `_UserFollows_AB_unique` ON `nestjsrealworld-prisma`.`_UserFollows`(`A`,`B`) + +CREATE INDEX `_UserFollows_B_index` ON `nestjsrealworld-prisma`.`_UserFollows`(`B`) + +ALTER TABLE `nestjsrealworld-prisma`.`_UserFollows` ADD FOREIGN KEY (`A`) REFERENCES `nestjsrealworld-prisma`.`User`(`id`) ON DELETE CASCADE ON UPDATE CASCADE + +ALTER TABLE `nestjsrealworld-prisma`.`_UserFollows` ADD FOREIGN KEY (`B`) REFERENCES `nestjsrealworld-prisma`.`User`(`id`) ON DELETE CASCADE ON UPDATE CASCADE +``` + +## Changes + +```diff +diff --git schema.prisma schema.prisma +migration 20200501152600..20200501152654 +--- datamodel.dml ++++ datamodel.dml +@@ -2,9 +2,9 @@ + // learn more about it in the docs: https://pris.ly/d/prisma-schema + datasource db { + provider = "mysql" +- url = "***" ++ url = env("DATABASE_URL") + } + generator client { + provider = "prisma-client-js" +@@ -18,8 +18,10 @@ + password String? + username String? @unique + articles Article[] @relation("UserArticles") + favorites Article[] @relation("UserFavorites", references: [id]) ++ followedBy User[] @relation("UserFollows", references: [id]) ++ following User[] @relation("UserFollows", references: [id]) + } + model Article { + id Int @default(autoincrement()) @id +``` + + diff --git a/prisma/migrations/20200501152654/schema.prisma b/prisma/migrations/20200501152654/schema.prisma new file mode 100644 index 00000000..40e24957 --- /dev/null +++ b/prisma/migrations/20200501152654/schema.prisma @@ -0,0 +1,39 @@ +// This is your Prisma schema file, +// learn more about it in the docs: https://pris.ly/d/prisma-schema + +datasource db { + provider = "mysql" + url = "***" +} + +generator client { + provider = "prisma-client-js" +} + +model User { + bio String? + email String @unique + id Int @default(autoincrement()) @id + image String? + password String? + username String? @unique + articles Article[] @relation("UserArticles") + favorites Article[] @relation("UserFavorites", references: [id]) + followedBy User[] @relation("UserFollows", references: [id]) + following User[] @relation("UserFollows", references: [id]) +} + +model Article { + id Int @default(autoincrement()) @id + slug String @unique + title String + description String + body String + createdAt DateTime @default(now()) + updatedAt DateTime @default(now()) + tagList String + favoriteCount Int + author User @relation("UserArticles", fields: [authorId], references: [id]) + authorId Int + favoritedBy User[] @relation("UserFavorites", references: [id]) +} \ No newline at end of file diff --git a/prisma/migrations/20200501152654/steps.json b/prisma/migrations/20200501152654/steps.json new file mode 100644 index 00000000..a9dfe4b0 --- /dev/null +++ b/prisma/migrations/20200501152654/steps.json @@ -0,0 +1,97 @@ +{ + "version": "0.3.14-fixed", + "steps": [ + { + "tag": "CreateField", + "model": "User", + "field": "followedBy", + "type": "User", + "arity": "List" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "User", + "field": "followedBy" + }, + "directive": "relation" + } + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Directive", + "path": { + "tag": "Field", + "model": "User", + "field": "followedBy" + }, + "directive": "relation" + }, + "argument": "", + "value": "\"UserFollows\"" + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Directive", + "path": { + "tag": "Field", + "model": "User", + "field": "followedBy" + }, + "directive": "relation" + }, + "argument": "references", + "value": "[id]" + }, + { + "tag": "CreateField", + "model": "User", + "field": "following", + "type": "User", + "arity": "List" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "User", + "field": "following" + }, + "directive": "relation" + } + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Directive", + "path": { + "tag": "Field", + "model": "User", + "field": "following" + }, + "directive": "relation" + }, + "argument": "", + "value": "\"UserFollows\"" + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Directive", + "path": { + "tag": "Field", + "model": "User", + "field": "following" + }, + "directive": "relation" + }, + "argument": "references", + "value": "[id]" + } + ] +} \ No newline at end of file diff --git a/prisma/migrations/20200502154200/README.md b/prisma/migrations/20200502154200/README.md new file mode 100644 index 00000000..a4288942 --- /dev/null +++ b/prisma/migrations/20200502154200/README.md @@ -0,0 +1,55 @@ +# Migration `20200502154200` + +This migration has been generated by Lukas Jakob at 5/2/2020, 3:42:00 PM. +You can check out the [state of the schema](./schema.prisma) after the migration. + +## Database Steps + +```sql +CREATE TABLE `nestjsrealworld-prisma`.`Comment` ( +`articleId` int NOT NULL ,`authorId` int NOT NULL ,`body` varchar(191) NOT NULL ,`createdAt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ,`id` int NOT NULL AUTO_INCREMENT,`updatedAt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP , + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci + +ALTER TABLE `nestjsrealworld-prisma`.`Comment` ADD FOREIGN KEY (`articleId`) REFERENCES `nestjsrealworld-prisma`.`Article`(`id`) ON DELETE CASCADE ON UPDATE CASCADE + +ALTER TABLE `nestjsrealworld-prisma`.`Comment` ADD FOREIGN KEY (`authorId`) REFERENCES `nestjsrealworld-prisma`.`User`(`id`) ON DELETE CASCADE ON UPDATE CASCADE +``` + +## Changes + +```diff +diff --git schema.prisma schema.prisma +migration 20200501152654..20200502154200 +--- datamodel.dml ++++ datamodel.dml +@@ -2,9 +2,9 @@ + // learn more about it in the docs: https://pris.ly/d/prisma-schema + datasource db { + provider = "mysql" +- url = "***" ++ url = env("DATABASE_URL") + } + generator client { + provider = "prisma-client-js" +@@ -35,5 +35,17 @@ + favoriteCount Int + author User @relation("UserArticles", fields: [authorId], references: [id]) + authorId Int + favoritedBy User[] @relation("UserFavorites", references: [id]) ++ comments Comment[] ++} ++ ++model Comment { ++ id Int @default(autoincrement()) @id ++ createdAt DateTime @default(now()) ++ updatedAt DateTime @default(now()) ++ body String ++ article Article @relation(fields: [articleId], references: [id]) ++ articleId Int ++ author User @relation(fields: [authorId], references: [id]) ++ authorId Int + } +``` + + diff --git a/prisma/migrations/20200502154200/schema.prisma b/prisma/migrations/20200502154200/schema.prisma new file mode 100644 index 00000000..691be316 --- /dev/null +++ b/prisma/migrations/20200502154200/schema.prisma @@ -0,0 +1,51 @@ +// This is your Prisma schema file, +// learn more about it in the docs: https://pris.ly/d/prisma-schema + +datasource db { + provider = "mysql" + url = "***" +} + +generator client { + provider = "prisma-client-js" +} + +model User { + bio String? + email String @unique + id Int @default(autoincrement()) @id + image String? + password String? + username String? @unique + articles Article[] @relation("UserArticles") + favorites Article[] @relation("UserFavorites", references: [id]) + followedBy User[] @relation("UserFollows", references: [id]) + following User[] @relation("UserFollows", references: [id]) +} + +model Article { + id Int @default(autoincrement()) @id + slug String @unique + title String + description String + body String + createdAt DateTime @default(now()) + updatedAt DateTime @default(now()) + tagList String + favoriteCount Int + author User @relation("UserArticles", fields: [authorId], references: [id]) + authorId Int + favoritedBy User[] @relation("UserFavorites", references: [id]) + comments Comment[] +} + +model Comment { + id Int @default(autoincrement()) @id + createdAt DateTime @default(now()) + updatedAt DateTime @default(now()) + body String + article Article @relation(fields: [articleId], references: [id]) + articleId Int + author User @relation(fields: [authorId], references: [id]) + authorId Int +} \ No newline at end of file diff --git a/prisma/migrations/20200502154200/steps.json b/prisma/migrations/20200502154200/steps.json new file mode 100644 index 00000000..eef10545 --- /dev/null +++ b/prisma/migrations/20200502154200/steps.json @@ -0,0 +1,236 @@ +{ + "version": "0.3.14-fixed", + "steps": [ + { + "tag": "CreateModel", + "model": "Comment" + }, + { + "tag": "CreateField", + "model": "Comment", + "field": "id", + "type": "Int", + "arity": "Required" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "Comment", + "field": "id" + }, + "directive": "default" + } + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Directive", + "path": { + "tag": "Field", + "model": "Comment", + "field": "id" + }, + "directive": "default" + }, + "argument": "", + "value": "autoincrement()" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "Comment", + "field": "id" + }, + "directive": "id" + } + }, + { + "tag": "CreateField", + "model": "Comment", + "field": "createdAt", + "type": "DateTime", + "arity": "Required" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "Comment", + "field": "createdAt" + }, + "directive": "default" + } + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Directive", + "path": { + "tag": "Field", + "model": "Comment", + "field": "createdAt" + }, + "directive": "default" + }, + "argument": "", + "value": "now()" + }, + { + "tag": "CreateField", + "model": "Comment", + "field": "updatedAt", + "type": "DateTime", + "arity": "Required" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "Comment", + "field": "updatedAt" + }, + "directive": "default" + } + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Directive", + "path": { + "tag": "Field", + "model": "Comment", + "field": "updatedAt" + }, + "directive": "default" + }, + "argument": "", + "value": "now()" + }, + { + "tag": "CreateField", + "model": "Comment", + "field": "body", + "type": "String", + "arity": "Required" + }, + { + "tag": "CreateField", + "model": "Comment", + "field": "article", + "type": "Article", + "arity": "Required" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "Comment", + "field": "article" + }, + "directive": "relation" + } + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Directive", + "path": { + "tag": "Field", + "model": "Comment", + "field": "article" + }, + "directive": "relation" + }, + "argument": "fields", + "value": "[articleId]" + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Directive", + "path": { + "tag": "Field", + "model": "Comment", + "field": "article" + }, + "directive": "relation" + }, + "argument": "references", + "value": "[id]" + }, + { + "tag": "CreateField", + "model": "Comment", + "field": "articleId", + "type": "Int", + "arity": "Required" + }, + { + "tag": "CreateField", + "model": "Comment", + "field": "author", + "type": "User", + "arity": "Required" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "Comment", + "field": "author" + }, + "directive": "relation" + } + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Directive", + "path": { + "tag": "Field", + "model": "Comment", + "field": "author" + }, + "directive": "relation" + }, + "argument": "fields", + "value": "[authorId]" + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Directive", + "path": { + "tag": "Field", + "model": "Comment", + "field": "author" + }, + "directive": "relation" + }, + "argument": "references", + "value": "[id]" + }, + { + "tag": "CreateField", + "model": "Comment", + "field": "authorId", + "type": "Int", + "arity": "Required" + }, + { + "tag": "CreateField", + "model": "Article", + "field": "comments", + "type": "Comment", + "arity": "List" + } + ] +} \ No newline at end of file diff --git a/prisma/migrations/20200502162011/README.md b/prisma/migrations/20200502162011/README.md new file mode 100644 index 00000000..9a7d7e16 --- /dev/null +++ b/prisma/migrations/20200502162011/README.md @@ -0,0 +1,89 @@ +# Migration `20200502162011` + +This migration has been generated by Lukas Jakob at 5/2/2020, 4:20:11 PM. +You can check out the [state of the schema](./schema.prisma) after the migration. + +## Database Steps + +```sql +CREATE TABLE `nestjsrealworld-prisma`.`Tag` ( +`id` int NOT NULL AUTO_INCREMENT,`name` varchar(191) NOT NULL , + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci + +CREATE UNIQUE INDEX `Tag.name` ON `nestjsrealworld-prisma`.`Tag`(`name`) +``` + +## Changes + +```diff +diff --git schema.prisma schema.prisma +migration 20200502154200..20200502162011 +--- datamodel.dml ++++ datamodel.dml +@@ -2,9 +2,9 @@ + // learn more about it in the docs: https://pris.ly/d/prisma-schema + datasource db { + provider = "mysql" +- url = "***" ++ url = env("DATABASE_URL") + } + generator client { + provider = "prisma-client-js" +@@ -18,34 +18,39 @@ + password String? + username String? @unique + articles Article[] @relation("UserArticles") + favorites Article[] @relation("UserFavorites", references: [id]) +- followedBy User[] @relation("UserFollows", references: [id]) +- following User[] @relation("UserFollows", references: [id]) ++ followedBy User[] @relation("UserFollows", references: [id]) ++ following User[] @relation("UserFollows", references: [id]) + } + model Article { +- id Int @default(autoincrement()) @id +- slug String @unique ++ id Int @default(autoincrement()) @id ++ slug String @unique + title String + description String + body String +- createdAt DateTime @default(now()) +- updatedAt DateTime @default(now()) ++ createdAt DateTime @default(now()) ++ updatedAt DateTime @default(now()) + tagList String + favoriteCount Int +- author User @relation("UserArticles", fields: [authorId], references: [id]) ++ author User @relation("UserArticles", fields: [authorId], references: [id]) + authorId Int +- favoritedBy User[] @relation("UserFavorites", references: [id]) ++ favoritedBy User[] @relation("UserFavorites", references: [id]) + comments Comment[] + } + model Comment { +- id Int @default(autoincrement()) @id +- createdAt DateTime @default(now()) +- updatedAt DateTime @default(now()) +- body String +- article Article @relation(fields: [articleId], references: [id]) +- articleId Int +- author User @relation(fields: [authorId], references: [id]) +- authorId Int ++ id Int @default(autoincrement()) @id ++ createdAt DateTime @default(now()) ++ updatedAt DateTime @default(now()) ++ body String ++ article Article @relation(fields: [articleId], references: [id]) ++ articleId Int ++ author User @relation(fields: [authorId], references: [id]) ++ authorId Int ++} ++ ++model Tag { ++ id Int @default(autoincrement()) @id ++ name String @unique + } +``` + + diff --git a/prisma/migrations/20200502162011/schema.prisma b/prisma/migrations/20200502162011/schema.prisma new file mode 100644 index 00000000..031c775c --- /dev/null +++ b/prisma/migrations/20200502162011/schema.prisma @@ -0,0 +1,56 @@ +// This is your Prisma schema file, +// learn more about it in the docs: https://pris.ly/d/prisma-schema + +datasource db { + provider = "mysql" + url = "***" +} + +generator client { + provider = "prisma-client-js" +} + +model User { + bio String? + email String @unique + id Int @default(autoincrement()) @id + image String? + password String? + username String? @unique + articles Article[] @relation("UserArticles") + favorites Article[] @relation("UserFavorites", references: [id]) + followedBy User[] @relation("UserFollows", references: [id]) + following User[] @relation("UserFollows", references: [id]) +} + +model Article { + id Int @default(autoincrement()) @id + slug String @unique + title String + description String + body String + createdAt DateTime @default(now()) + updatedAt DateTime @default(now()) + tagList String + favoriteCount Int + author User @relation("UserArticles", fields: [authorId], references: [id]) + authorId Int + favoritedBy User[] @relation("UserFavorites", references: [id]) + comments Comment[] +} + +model Comment { + id Int @default(autoincrement()) @id + createdAt DateTime @default(now()) + updatedAt DateTime @default(now()) + body String + article Article @relation(fields: [articleId], references: [id]) + articleId Int + author User @relation(fields: [authorId], references: [id]) + authorId Int +} + +model Tag { + id Int @default(autoincrement()) @id + name String @unique +} \ No newline at end of file diff --git a/prisma/migrations/20200502162011/steps.json b/prisma/migrations/20200502162011/steps.json new file mode 100644 index 00000000..14f90a2c --- /dev/null +++ b/prisma/migrations/20200502162011/steps.json @@ -0,0 +1,70 @@ +{ + "version": "0.3.14-fixed", + "steps": [ + { + "tag": "CreateModel", + "model": "Tag" + }, + { + "tag": "CreateField", + "model": "Tag", + "field": "id", + "type": "Int", + "arity": "Required" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "Tag", + "field": "id" + }, + "directive": "default" + } + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Directive", + "path": { + "tag": "Field", + "model": "Tag", + "field": "id" + }, + "directive": "default" + }, + "argument": "", + "value": "autoincrement()" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "Tag", + "field": "id" + }, + "directive": "id" + } + }, + { + "tag": "CreateField", + "model": "Tag", + "field": "name", + "type": "String", + "arity": "Required" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "Tag", + "field": "name" + }, + "directive": "unique" + } + } + ] +} \ No newline at end of file diff --git a/prisma/migrations/20200502164559/README.md b/prisma/migrations/20200502164559/README.md new file mode 100644 index 00000000..3200533d --- /dev/null +++ b/prisma/migrations/20200502164559/README.md @@ -0,0 +1,40 @@ +# Migration `20200502164559` + +This migration has been generated by Lukas Jakob at 5/2/2020, 4:45:59 PM. +You can check out the [state of the schema](./schema.prisma) after the migration. + +## Database Steps + +```sql +ALTER TABLE `nestjsrealworld-prisma`.`Article` DROP COLUMN `favoriteCount`; +``` + +## Changes + +```diff +diff --git schema.prisma schema.prisma +migration 20200502162011..20200502164559 +--- datamodel.dml ++++ datamodel.dml +@@ -2,9 +2,9 @@ + // learn more about it in the docs: https://pris.ly/d/prisma-schema + datasource db { + provider = "mysql" +- url = "***" ++ url = env("DATABASE_URL") + } + generator client { + provider = "prisma-client-js" +@@ -31,9 +31,8 @@ + body String + createdAt DateTime @default(now()) + updatedAt DateTime @default(now()) + tagList String +- favoriteCount Int + author User @relation("UserArticles", fields: [authorId], references: [id]) + authorId Int + favoritedBy User[] @relation("UserFavorites", references: [id]) + comments Comment[] +``` + + diff --git a/prisma/migrations/20200502164559/schema.prisma b/prisma/migrations/20200502164559/schema.prisma new file mode 100644 index 00000000..575b4ee3 --- /dev/null +++ b/prisma/migrations/20200502164559/schema.prisma @@ -0,0 +1,55 @@ +// This is your Prisma schema file, +// learn more about it in the docs: https://pris.ly/d/prisma-schema + +datasource db { + provider = "mysql" + url = "***" +} + +generator client { + provider = "prisma-client-js" +} + +model User { + bio String? + email String @unique + id Int @default(autoincrement()) @id + image String? + password String? + username String? @unique + articles Article[] @relation("UserArticles") + favorites Article[] @relation("UserFavorites", references: [id]) + followedBy User[] @relation("UserFollows", references: [id]) + following User[] @relation("UserFollows", references: [id]) +} + +model Article { + id Int @default(autoincrement()) @id + slug String @unique + title String + description String + body String + createdAt DateTime @default(now()) + updatedAt DateTime @default(now()) + tagList String + author User @relation("UserArticles", fields: [authorId], references: [id]) + authorId Int + favoritedBy User[] @relation("UserFavorites", references: [id]) + comments Comment[] +} + +model Comment { + id Int @default(autoincrement()) @id + createdAt DateTime @default(now()) + updatedAt DateTime @default(now()) + body String + article Article @relation(fields: [articleId], references: [id]) + articleId Int + author User @relation(fields: [authorId], references: [id]) + authorId Int +} + +model Tag { + id Int @default(autoincrement()) @id + name String @unique +} \ No newline at end of file diff --git a/prisma/migrations/20200502164559/steps.json b/prisma/migrations/20200502164559/steps.json new file mode 100644 index 00000000..c250cd1f --- /dev/null +++ b/prisma/migrations/20200502164559/steps.json @@ -0,0 +1,10 @@ +{ + "version": "0.3.14-fixed", + "steps": [ + { + "tag": "DeleteField", + "model": "Article", + "field": "favoriteCount" + } + ] +} \ No newline at end of file diff --git a/prisma/migrations/migrate.lock b/prisma/migrations/migrate.lock new file mode 100644 index 00000000..afa8bada --- /dev/null +++ b/prisma/migrations/migrate.lock @@ -0,0 +1,10 @@ +# IF THERE'S A GIT CONFLICT IN THIS FILE, DON'T SOLVE IT MANUALLY! +# INSTEAD EXECUTE `prisma migrate fix` +# Prisma Migrate lockfile v1 +# Read more about conflict resolution here: TODO + +20200501152600 +20200501152654 +20200502154200 +20200502162011 +20200502164559 \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma new file mode 100644 index 00000000..0138fd0a --- /dev/null +++ b/prisma/schema.prisma @@ -0,0 +1,55 @@ +// This is your Prisma schema file, +// learn more about it in the docs: https://pris.ly/d/prisma-schema + +datasource db { + provider = "mysql" + url = env("DATABASE_URL") +} + +generator client { + provider = "prisma-client-js" +} + +model User { + bio String? + email String @unique + id Int @default(autoincrement()) @id + image String? + password String? + username String? @unique + articles Article[] @relation("UserArticles") + favorites Article[] @relation("UserFavorites", references: [id]) + followedBy User[] @relation("UserFollows", references: [id]) + following User[] @relation("UserFollows", references: [id]) +} + +model Article { + id Int @default(autoincrement()) @id + slug String @unique + title String + description String + body String + createdAt DateTime @default(now()) + updatedAt DateTime @default(now()) + tagList String + author User @relation("UserArticles", fields: [authorId], references: [id]) + authorId Int + favoritedBy User[] @relation("UserFavorites", references: [id]) + comments Comment[] +} + +model Comment { + id Int @default(autoincrement()) @id + createdAt DateTime @default(now()) + updatedAt DateTime @default(now()) + body String + article Article @relation(fields: [articleId], references: [id]) + articleId Int + author User @relation(fields: [authorId], references: [id]) + authorId Int +} + +model Tag { + id Int @default(autoincrement()) @id + name String @unique +} \ No newline at end of file diff --git a/src/app.module.ts b/src/app.module.ts index 3d5acb51..ff48b673 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -2,14 +2,11 @@ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { ArticleModule } from './article/article.module'; import { UserModule } from './user/user.module'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { Connection } from 'typeorm'; import { ProfileModule } from './profile/profile.module'; import { TagModule } from './tag/tag.module'; @Module({ imports: [ - TypeOrmModule.forRoot(), ArticleModule, UserModule, ProfileModule, @@ -20,6 +17,4 @@ import { TagModule } from './tag/tag.module'; ], providers: [] }) -export class ApplicationModule { - constructor(private readonly connection: Connection) {} -} +export class ApplicationModule {} diff --git a/src/article/article.controller.ts b/src/article/article.controller.ts index ea35c87c..8b7a0fb4 100644 --- a/src/article/article.controller.ts +++ b/src/article/article.controller.ts @@ -1,5 +1,4 @@ -import {Get, Post, Body, Put, Delete, Query, Param, Controller} from '@nestjs/common'; -import { Request } from 'express'; +import { Get, Post, Body, Put, Delete, Query, Param, Controller, HttpCode } from '@nestjs/common'; import { ArticleService } from './article.service'; import { CreateArticleDto, CreateCommentDto } from './dto'; import { ArticlesRO, ArticleRO } from './article.interface'; @@ -22,11 +21,10 @@ export class ArticleController { @ApiOperation({ summary: 'Get all articles' }) @ApiResponse({ status: 200, description: 'Return all articles.'}) @Get() - async findAll(@Query() query): Promise { - return await this.articleService.findAll(query); + async findAll(@User('id') userId: number, @Query() query): Promise { + return await this.articleService.findAll(userId, query); } - @ApiOperation({ summary: 'Get article feed' }) @ApiResponse({ status: 200, description: 'Return article feed.'}) @ApiResponse({ status: 403, description: 'Forbidden.' }) @@ -36,8 +34,8 @@ export class ArticleController { } @Get(':slug') - async findOne(@Param('slug') slug): Promise { - return await this.articleService.findOne({slug}); + async findOne(@User('id') userId: number, @Param('slug') slug): Promise { + return await this.articleService.findOne(userId, slug); } @Get(':slug/comments') @@ -57,13 +55,14 @@ export class ArticleController { @ApiResponse({ status: 201, description: 'The article has been successfully updated.'}) @ApiResponse({ status: 403, description: 'Forbidden.' }) @Put(':slug') - async update(@Param() params, @Body('article') articleData: CreateArticleDto) { + async update(@User('id') userId: number, @Param() params, @Body('article') articleData: CreateArticleDto) { // Todo: update slug also when title gets changed - return this.articleService.update(params.slug, articleData); + return this.articleService.update(userId, params.slug, articleData); } + @HttpCode(204) @ApiOperation({ summary: 'Delete article' }) - @ApiResponse({ status: 201, description: 'The article has been successfully deleted.'}) + @ApiResponse({ status: 204, description: 'The article has been successfully deleted.'}) @ApiResponse({ status: 403, description: 'Forbidden.' }) @Delete(':slug') async delete(@Param() params) { @@ -74,12 +73,13 @@ export class ArticleController { @ApiResponse({ status: 201, description: 'The comment has been successfully created.'}) @ApiResponse({ status: 403, description: 'Forbidden.' }) @Post(':slug/comments') - async createComment(@Param('slug') slug, @Body('comment') commentData: CreateCommentDto) { - return await this.articleService.addComment(slug, commentData); + async createComment(@User('id') userId: number, @Param('slug') slug, @Body('comment') payload: CreateCommentDto) { + return await this.articleService.addComment(userId, slug, payload); } + @HttpCode(204) @ApiOperation({ summary: 'Delete comment' }) - @ApiResponse({ status: 201, description: 'The article has been successfully deleted.'}) + @ApiResponse({ status: 204, description: 'The comment has been successfully deleted.'}) @ApiResponse({ status: 403, description: 'Forbidden.' }) @Delete(':slug/comments/:id') async deleteComment(@Param() params) { diff --git a/src/article/article.entity.ts b/src/article/article.entity.ts deleted file mode 100644 index 6c08be43..00000000 --- a/src/article/article.entity.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { Entity, PrimaryGeneratedColumn, Column, OneToOne, ManyToOne, OneToMany, JoinColumn, AfterUpdate, BeforeUpdate } from 'typeorm'; -import { UserEntity } from '../user/user.entity'; -import { Comment } from './comment.entity'; - -@Entity('article') -export class ArticleEntity { - - @PrimaryGeneratedColumn() - id: number; - - @Column() - slug: string; - - @Column() - title: string; - - @Column({default: ''}) - description: string; - - @Column({default: ''}) - body: string; - - @Column({ type: 'timestamp', default: () => "CURRENT_TIMESTAMP"}) - created: Date; - - @Column({ type: 'timestamp', default: () => "CURRENT_TIMESTAMP"}) - updated: Date; - - @BeforeUpdate() - updateTimestamp() { - this.updated = new Date; - } - - @Column('simple-array') - tagList: string[]; - - @ManyToOne(type => UserEntity, user => user.articles) - author: UserEntity; - - @OneToMany(type => Comment, comment => comment.article, {eager: true}) - @JoinColumn() - comments: Comment[]; - - @Column({default: 0}) - favoriteCount: number; -} \ No newline at end of file diff --git a/src/article/article.interface.ts b/src/article/article.interface.ts index a4faf563..b3dce9d1 100644 --- a/src/article/article.interface.ts +++ b/src/article/article.interface.ts @@ -1,32 +1,15 @@ -import { UserData } from '../user/user.interface'; -import { ArticleEntity } from './article.entity'; -interface Comment { - body: string; -} - -interface ArticleData { - slug: string; - title: string; - description: string; - body?: string; - tagList?: string[]; - createdAt?: Date - updatedAt?: Date - favorited?: boolean; - favoritesCount?: number; - author?: UserData; -} +import { Comment, Article } from '@prisma/client' export interface CommentsRO { comments: Comment[]; } export interface ArticleRO { - article: ArticleEntity; + article: Article; } export interface ArticlesRO { - articles: ArticleEntity[]; + articles: Article[]; articlesCount: number; } diff --git a/src/article/article.module.ts b/src/article/article.module.ts index 00308da1..88e3361e 100644 --- a/src/article/article.module.ts +++ b/src/article/article.module.ts @@ -1,17 +1,18 @@ import { MiddlewareConsumer, Module, NestModule, RequestMethod } from '@nestjs/common'; import { ArticleController } from './article.controller'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { ArticleEntity } from './article.entity'; -import { Comment } from './comment.entity'; -import { UserEntity } from '../user/user.entity'; -import { FollowsEntity } from '../profile/follows.entity'; import { ArticleService } from './article.service'; import { AuthMiddleware } from '../user/auth.middleware'; import { UserModule } from '../user/user.module'; +import { PrismaService } from '../shared/services/prisma.service'; @Module({ - imports: [TypeOrmModule.forFeature([ArticleEntity, Comment, UserEntity, FollowsEntity]), UserModule], - providers: [ArticleService], + imports: [ + UserModule + ], + providers: [ + ArticleService, + PrismaService + ], controllers: [ ArticleController ] diff --git a/src/article/article.service.ts b/src/article/article.service.ts index 4b0f6769..edbbd6d9 100644 --- a/src/article/article.service.ts +++ b/src/article/article.service.ts @@ -1,201 +1,240 @@ import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository, getRepository, DeleteResult } from 'typeorm'; -import { ArticleEntity } from './article.entity'; -import { Comment } from './comment.entity'; -import { UserEntity } from '../user/user.entity'; -import { FollowsEntity } from '../profile/follows.entity'; import { CreateArticleDto } from './dto'; - -import {ArticleRO, ArticlesRO, CommentsRO} from './article.interface'; +import { PrismaService } from '../shared/services/prisma.service'; const slug = require('slug'); +import { ArticleWhereInput, Enumerable } from '@prisma/client'; + +const articleAuthorSelect = { + email: true, + username: true, + bio: true, + image: true, + followedBy: { select: { id: true } } +}; + +const commentSelect = { + id: true, + createdAt: true, + updatedAt: true, + body: true, + author: { select: articleAuthorSelect } +}; + +const articleInclude = { + author: { select: articleAuthorSelect }, + favoritedBy: { select: { id: true }}, +}; + +// map dynamic value "following" (is the current user following this author) +const mapAuthorFollowing = (userId, {followedBy, ...rest}) => ({ + ...rest, + following: Array.isArray(followedBy) && followedBy.map(f => f.id).includes(userId), +}); + +// map dynamic values "following" and "favorited" (from favoritedBy) +const mapDynamicValues = (userId, {favoritedBy, author, ...rest}) => ({ + ...rest, + favorited: Array.isArray(favoritedBy) && favoritedBy.map(f => f.id).includes(userId), + author: mapAuthorFollowing(userId, author), +}); @Injectable() export class ArticleService { - constructor( - @InjectRepository(ArticleEntity) - private readonly articleRepository: Repository, - @InjectRepository(Comment) - private readonly commentRepository: Repository, - @InjectRepository(UserEntity) - private readonly userRepository: Repository, - @InjectRepository(FollowsEntity) - private readonly followsRepository: Repository - ) {} - - async findAll(query): Promise { + constructor(private prisma: PrismaService) {} + + async findAll(userId: number, query): Promise { + const andQueries = this.buildFindAllQuery(query); + let articles = await this.prisma.article.findMany({ + where: { AND: andQueries }, + orderBy: { createdAt: 'desc' }, + include: articleInclude, + ...('limit' in query ? {first: +query.limit} : {}), + ...('offset' in query ? {skip: +query.offset} : {}), + }); + const articlesCount = await this.prisma.article.count({ + where: { AND: andQueries }, + orderBy: { createdAt: 'desc' }, + }); + + articles = (articles as any).map((a) => mapDynamicValues(userId, a)); + + return { articles, articlesCount }; + } - const qb = await getRepository(ArticleEntity) - .createQueryBuilder('article') - .leftJoinAndSelect('article.author', 'author'); - qb.where("1 = 1"); + private buildFindAllQuery(query): Enumerable { + const queries = []; if ('tag' in query) { - qb.andWhere("article.tagList LIKE :tag", { tag: `%${query.tag}%` }); + queries.push({ + tagList: { + contains: query.tag + } + }); } if ('author' in query) { - const author = await this.userRepository.findOne({username: query.author}); - qb.andWhere("article.authorId = :id", { id: author.id }); + queries.push({ + author: { + username: { + equals: query.author + } + } + }); } if ('favorited' in query) { - const author = await this.userRepository.findOne({username: query.favorited}); - const ids = author.favorites.map(el => el.id); - qb.andWhere("article.authorId IN (:ids)", { ids }); + queries.push({ + favoritedBy: { + some: { + username: { + equals: query.favorited + } + } + } + }); } - qb.orderBy('article.created', 'DESC'); - - const articlesCount = await qb.getCount(); - - if ('limit' in query) { - qb.limit(query.limit); - } - - if ('offset' in query) { - qb.offset(query.offset); - } - - const articles = await qb.getMany(); - - return {articles, articlesCount}; + return queries; } - async findFeed(userId: number, query): Promise { - const _follows = await this.followsRepository.find( {followerId: userId}); - - if (!(Array.isArray(_follows) && _follows.length > 0)) { - return {articles: [], articlesCount: 0}; - } - - const ids = _follows.map(el => el.followingId); - - const qb = await getRepository(ArticleEntity) - .createQueryBuilder('article') - .where('article.authorId IN (:ids)', { ids }); - - qb.orderBy('article.created', 'DESC'); - - const articlesCount = await qb.getCount(); - - if ('limit' in query) { - qb.limit(query.limit); - } + async findFeed(userId: number, query): Promise { + const where = { + author: { + followedBy: { some: { id: +userId } } + } + }; + let articles = await this.prisma.article.findMany({ + where, + orderBy: { createdAt: 'desc' }, + include: articleInclude, + ...('limit' in query ? {first: +query.limit} : {}), + ...('offset' in query ? {skip: +query.offset} : {}), + }); + const articlesCount = await this.prisma.article.count({ + where, + orderBy: { createdAt: 'desc' }, + }); + + articles = (articles as any).map((a) => mapDynamicValues(userId, a)); + + return { articles, articlesCount }; + } - if ('offset' in query) { - qb.offset(query.offset); - } + async findOne(userId: number, slug: string): Promise { + let article: any = await this.prisma.article.findOne({ + where: { slug }, + include: articleInclude, + }); - const articles = await qb.getMany(); + article = mapDynamicValues(userId, article); - return {articles, articlesCount}; + return { article }; } - async findOne(where): Promise { - const article = await this.articleRepository.findOne(where); - return {article}; + async addComment(userId: number, slug: string, {body}): Promise { + const comment = await this.prisma.comment.create({ + data: { + body, + article: { + connect: { slug } + }, + author: { + connect: { id: userId } + } + }, + select: commentSelect + }); + + return { comment }; } - async addComment(slug: string, commentData): Promise { - let article = await this.articleRepository.findOne({slug}); - - const comment = new Comment(); - comment.body = commentData.body; - - article.comments.push(comment); - - await this.commentRepository.save(comment); - article = await this.articleRepository.save(article); - return {article} + async deleteComment(slug: string, id: string): Promise { + // @Todo: no clue why API specs require a slug if the comment id is unique?! + await this.prisma.comment.delete({ where: { id: +id }}); } - async deleteComment(slug: string, id: string): Promise { - let article = await this.articleRepository.findOne({slug}); - - const comment = await this.commentRepository.findOne(id); - const deleteIndex = article.comments.findIndex(_comment => _comment.id === comment.id); - - if (deleteIndex >= 0) { - const deleteComments = article.comments.splice(deleteIndex, 1); - await this.commentRepository.delete(deleteComments[0].id); - article = await this.articleRepository.save(article); - return {article}; - } else { - return {article}; - } - + async favorite(userId: number, slug: string): Promise { + let article: any = await this.prisma.article.update( + { + where: { slug }, + data: { + favoritedBy: { + connect: { id: userId } + } + }, + include: articleInclude + } + ); + + article = mapDynamicValues(userId, article); + + return { article }; } - async favorite(id: number, slug: string): Promise { - let article = await this.articleRepository.findOne({slug}); - const user = await this.userRepository.findOne(id); - - const isNewFavorite = user.favorites.findIndex(_article => _article.id === article.id) < 0; - if (isNewFavorite) { - user.favorites.push(article); - article.favoriteCount++; - - await this.userRepository.save(user); - article = await this.articleRepository.save(article); - } - - return {article}; + async unFavorite(userId: number, slug: string): Promise { + let article: any = await this.prisma.article.update( + { + where: { slug }, + data: { + favoritedBy: { + disconnect: { id: userId } + } + }, + include: articleInclude + } + ); + + article = mapDynamicValues(userId, article); + + return { article }; } - async unFavorite(id: number, slug: string): Promise { - let article = await this.articleRepository.findOne({slug}); - const user = await this.userRepository.findOne(id); - - const deleteIndex = user.favorites.findIndex(_article => _article.id === article.id); - - if (deleteIndex >= 0) { - - user.favorites.splice(deleteIndex, 1); - article.favoriteCount--; - - await this.userRepository.save(user); - article = await this.articleRepository.save(article); - } - - return {article}; + async findComments(slug: string): Promise { + const comments = await this.prisma.comment.findMany({ + where: { article: { slug } }, + orderBy: { createdAt: 'desc' }, + select: commentSelect + }); + return { comments }; } - async findComments(slug: string): Promise { - const article = await this.articleRepository.findOne({slug}); - return {comments: article.comments}; + async create(userId: number, payload: CreateArticleDto): Promise { + const data = { + ...payload, + slug: this.slugify(payload.title), + tagList: payload.tagList.join(','), + author: { + connect: { id: userId } + } + }; + let article: any = await this.prisma.article.create({ + data, + include: articleInclude + }); + + article = mapDynamicValues(userId, article); + + return { article }; } - async create(userId: number, articleData: CreateArticleDto): Promise { - - let article = new ArticleEntity(); - article.title = articleData.title; - article.description = articleData.description; - article.slug = this.slugify(articleData.title); - article.tagList = articleData.tagList || []; - article.comments = []; + async update(userId: number, slug: string, data: any): Promise { + let article: any = await this.prisma.article.update({ + where: { slug }, + data: { + ...data, + updatedAt: new Date() + }, + include: articleInclude, + }); - const newArticle = await this.articleRepository.save(article); - - const author = await this.userRepository.findOne({ where: { id: userId }, relations: ['articles'] }); - author.articles.push(article); - - await this.userRepository.save(author); - - return newArticle; - - } + article = mapDynamicValues(userId, article); - async update(slug: string, articleData: any): Promise { - let toUpdate = await this.articleRepository.findOne({ slug: slug}); - let updated = Object.assign(toUpdate, articleData); - const article = await this.articleRepository.save(updated); - return {article}; + return { article }; } - async delete(slug: string): Promise { - return await this.articleRepository.delete({ slug: slug}); + async delete(slug: string): Promise { + await this.prisma.article.delete({ where: { slug } }); } slugify(title: string) { diff --git a/src/article/comment.entity.ts b/src/article/comment.entity.ts deleted file mode 100644 index 6be584be..00000000 --- a/src/article/comment.entity.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from 'typeorm'; -import { ArticleEntity } from './article.entity'; - -@Entity() -export class Comment { - - @PrimaryGeneratedColumn() - id: number; - - @Column() - body: string; - - @ManyToOne(type => ArticleEntity, article => article.comments) - article: ArticleEntity; -} \ No newline at end of file diff --git a/src/profile/follows.entity.ts b/src/profile/follows.entity.ts deleted file mode 100644 index b06f940e..00000000 --- a/src/profile/follows.entity.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"; - -@Entity('follows') -export class FollowsEntity { - - @PrimaryGeneratedColumn() - id: number; - - @Column() - followerId: number; - - @Column() - followingId: number; - -} diff --git a/src/profile/profile.controller.ts b/src/profile/profile.controller.ts index 27391462..a1a079f8 100644 --- a/src/profile/profile.controller.ts +++ b/src/profile/profile.controller.ts @@ -21,13 +21,12 @@ export class ProfileController { } @Post(':username/follow') - async follow(@User('email') email: string, @Param('username') username: string): Promise { - return await this.profileService.follow(email, username); + async follow(@User('id') userId: string, @Param('username') username: string): Promise { + return await this.profileService.follow(userId, username); } @Delete(':username/follow') async unFollow(@User('id') userId: number, @Param('username') username: string): Promise { return await this.profileService.unFollow(userId, username); } - } \ No newline at end of file diff --git a/src/profile/profile.module.ts b/src/profile/profile.module.ts index c2164f75..a41bd0c9 100644 --- a/src/profile/profile.module.ts +++ b/src/profile/profile.module.ts @@ -1,15 +1,18 @@ -import {MiddlewareConsumer, Module, NestModule, RequestMethod} from '@nestjs/common'; +import { MiddlewareConsumer, Module, NestModule, RequestMethod } from '@nestjs/common'; import { ProfileController } from './profile.controller'; -import { TypeOrmModule } from '@nestjs/typeorm'; import { ProfileService } from './profile.service'; import { UserModule } from '../user/user.module'; -import {UserEntity} from "../user/user.entity"; -import {FollowsEntity} from "./follows.entity"; -import {AuthMiddleware} from "../user/auth.middleware"; +import { AuthMiddleware } from '../user/auth.middleware'; +import { PrismaService } from '../shared/services/prisma.service'; @Module({ - imports: [TypeOrmModule.forFeature([UserEntity, FollowsEntity]), UserModule], - providers: [ProfileService], + imports: [ + UserModule + ], + providers: [ + ProfileService, + PrismaService + ], controllers: [ ProfileController ], diff --git a/src/profile/profile.service.ts b/src/profile/profile.service.ts index 5a0cfc94..92bc5713 100644 --- a/src/profile/profile.service.ts +++ b/src/profile/profile.service.ts @@ -1,104 +1,118 @@ import { HttpStatus, Injectable} from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; -import { UserEntity } from '../user/user.entity'; -import { DeepPartial } from 'typeorm/common/DeepPartial'; -import { ProfileRO, ProfileData } from './profile.interface'; -import {FollowsEntity} from "./follows.entity"; -import {HttpException} from "@nestjs/common/exceptions/http.exception"; +import { ProfileRO } from './profile.interface'; +import { HttpException } from '@nestjs/common/exceptions/http.exception'; +import { PrismaService } from '../shared/services/prisma.service'; + +const profileSelect = { + username: true, + bio: true, + image: true, + id: true +}; @Injectable() export class ProfileService { - constructor( - @InjectRepository(UserEntity) - private readonly userRepository: Repository, - @InjectRepository(FollowsEntity) - private readonly followsRepository: Repository - ) {} - - async findAll(): Promise { - return await this.userRepository.find(); - } - - async findOne(options?: DeepPartial): Promise { - const user = await this.userRepository.findOne(options); - delete user.id; - if (user) delete user.password; - return {profile: user}; - } + constructor(private prisma: PrismaService) {} - async findProfile(id: number, followingUsername: string): Promise { - const _profile = await this.userRepository.findOne( {username: followingUsername}); + async findProfile(userId: number, followingUsername: string): Promise { + const followed = await this.prisma.user.findOne({ + where: { username: followingUsername }, + select: profileSelect + }); - if(!_profile) return; + if (!followed) { + throw new HttpException({errors: { user: 'not found' }}, HttpStatus.UNPROCESSABLE_ENTITY); + } - let profile: ProfileData = { - username: _profile.username, - bio: _profile.bio, - image: _profile.image + const meFollowing = await this.prisma.user.findMany({ + where: { + AND: [ + { + id: followed.id + }, + { + followedBy: { + some: { id: +userId } + } + } + ] + } + }); + + const { id, ...rest } = followed; + const profile = { + ...rest, + following: Array.isArray(meFollowing) && meFollowing.length > 0 }; - const follows = await this.followsRepository.findOne( {followerId: id, followingId: _profile.id}); - - if (id) { - profile.following = !!follows; - } - - return {profile}; + return { profile }; } - async follow(followerEmail: string, username: string): Promise { - if (!followerEmail || !username) { - throw new HttpException('Follower email and username not provided.', HttpStatus.BAD_REQUEST); - } - - const followingUser = await this.userRepository.findOne({username}); - const followerUser = await this.userRepository.findOne({email: followerEmail}); - - if (followingUser.email === followerEmail) { - throw new HttpException('FollowerEmail and FollowingId cannot be equal.', HttpStatus.BAD_REQUEST); + async follow(userId: string, username: string): Promise { + if (!username) { + throw new HttpException('Follower username not provided.', HttpStatus.BAD_REQUEST); } - const _follows = await this.followsRepository.findOne( {followerId: followerUser.id, followingId: followingUser.id}); + const followed = await this.prisma.user.findOne({ + where: { username }, + select: profileSelect, + }); - if (!_follows) { - const follows = new FollowsEntity(); - follows.followerId = followerUser.id; - follows.followingId = followingUser.id; - await this.followsRepository.save(follows); + if (!followed) { + throw new HttpException('User to follow not found.', HttpStatus.UNPROCESSABLE_ENTITY); } - let profile: ProfileData = { - username: followingUser.username, - bio: followingUser.bio, - image: followingUser.image, + await this.prisma.user.update({ + where: { id: +userId }, + data: { + following: { + connect: { + id: followed.id + } + } + } + }); + + const { id, ...rest } = followed; + const profile = { + ...rest, following: true }; - return {profile}; + return { profile }; } - async unFollow(followerId: number, username: string): Promise { - if (!followerId || !username) { - throw new HttpException('FollowerId and username not provided.', HttpStatus.BAD_REQUEST); + async unFollow(userId: number, username: string): Promise { + if (!username) { + throw new HttpException('Follower username not provided.', HttpStatus.BAD_REQUEST); } - const followingUser = await this.userRepository.findOne({username}); + const followed = await this.prisma.user.findOne({ + where: { username }, + select: profileSelect, + }); - if (followingUser.id === followerId) { - throw new HttpException('FollowerId and FollowingId cannot be equal.', HttpStatus.BAD_REQUEST); + if (!followed) { + throw new HttpException('User to follow not found.', HttpStatus.UNPROCESSABLE_ENTITY); } - const followingId = followingUser.id; - await this.followsRepository.delete({followerId, followingId}); - let profile: ProfileData = { - username: followingUser.username, - bio: followingUser.bio, - image: followingUser.image, + await this.prisma.user.update({ + where: { id: +userId }, + data: { + following: { + disconnect: { + id: followed.id + } + } + } + }); + + const { id, ...rest } = followed; + const profile = { + ...rest, following: false }; - return {profile}; + return { profile }; } - } diff --git a/src/shared/services/prisma.service.ts b/src/shared/services/prisma.service.ts new file mode 100644 index 00000000..cdfd7f52 --- /dev/null +++ b/src/shared/services/prisma.service.ts @@ -0,0 +1,22 @@ +import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common'; +import { PrismaClient } from '@prisma/client'; + +@Injectable() +export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy { + constructor() { + super(); + } + + async onModuleInit() { + try { + + await this.connect(); + } catch (e) { + console.log(e) + } + } + + async onModuleDestroy() { + await this.disconnect(); + } +} diff --git a/src/tag/tag.controller.spec.ts b/src/tag/tag.controller.spec.ts index 13ad829a..f8960098 100644 --- a/src/tag/tag.controller.spec.ts +++ b/src/tag/tag.controller.spec.ts @@ -1,8 +1,8 @@ import { Test } from '@nestjs/testing'; import { TagController } from './tag.controller'; import { TagService } from './tag.service'; -import {TypeOrmModule} from "@nestjs/typeorm"; -import {TagEntity} from "./tag.entity"; +import { UserModule } from '../user/user.module'; +import { PrismaService } from '../shared/services/prisma.service'; describe('TagController', () => { let tagController: TagController; @@ -10,9 +10,9 @@ describe('TagController', () => { beforeEach(async () => { const module = await Test.createTestingModule({ - imports: [TypeOrmModule.forRoot(), TypeOrmModule.forFeature([TagEntity])], + imports: [UserModule], controllers: [TagController], - providers: [TagService], + providers: [TagService, PrismaService], }).compile(); tagService = module.get(TagService); @@ -21,15 +21,12 @@ describe('TagController', () => { describe('findAll', () => { it('should return an array of tags', async () => { - const tags : TagEntity[] = []; - const createTag = (id, name) => { - const tag = new TagEntity(); - tag.id = id; - tag.tag = name; - return tag; - } - tags.push(createTag(1, 'angularjs')); - tags.push(createTag(2, 'reactjs')); + const tags : any[] = []; + const createTag = (name) => { + return name; + }; + tags.push(createTag('angularjs')); + tags.push(createTag('reactjs')); jest.spyOn(tagService, 'findAll').mockImplementation(() => Promise.resolve(tags)); diff --git a/src/tag/tag.controller.ts b/src/tag/tag.controller.ts index 27aeeb66..6ac11b64 100644 --- a/src/tag/tag.controller.ts +++ b/src/tag/tag.controller.ts @@ -1,21 +1,17 @@ -import {Get, Controller } from '@nestjs/common'; - -import { TagEntity } from './tag.entity'; +import { Get, Controller } from '@nestjs/common'; import { TagService } from './tag.service'; - -import { - ApiBearerAuth, ApiTags, -} from '@nestjs/swagger'; +import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; @ApiBearerAuth() @ApiTags('tags') @Controller('tags') export class TagController { - constructor(private readonly tagService: TagService) {} + @ApiOperation({ summary: 'Get all tags' }) + @ApiResponse({ status: 200, description: 'Return all tags.'}) @Get() - async findAll(): Promise { + async findAll(): Promise { return await this.tagService.findAll(); } diff --git a/src/tag/tag.entity.ts b/src/tag/tag.entity.ts deleted file mode 100644 index a2227125..00000000 --- a/src/tag/tag.entity.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm'; - -@Entity('tag') -export class TagEntity { - - @PrimaryGeneratedColumn() - id: number; - - @Column() - tag: string; - -} diff --git a/src/tag/tag.module.ts b/src/tag/tag.module.ts index 5b3bef3c..850133a9 100644 --- a/src/tag/tag.module.ts +++ b/src/tag/tag.module.ts @@ -1,13 +1,17 @@ -import {MiddlewareConsumer, Module, NestModule, RequestMethod} from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; +import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; import { UserModule } from '../user/user.module'; import { TagService } from './tag.service'; -import { TagEntity } from './tag.entity'; import { TagController } from './tag.controller'; +import { PrismaService } from '../shared/services/prisma.service'; @Module({ - imports: [TypeOrmModule.forFeature([TagEntity]), UserModule], - providers: [TagService], + imports: [ + UserModule + ], + providers: [ + TagService, + PrismaService, + ], controllers: [ TagController ], diff --git a/src/tag/tag.service.ts b/src/tag/tag.service.ts index 700a59dc..d63f3e32 100644 --- a/src/tag/tag.service.ts +++ b/src/tag/tag.service.ts @@ -1,17 +1,14 @@ import { Injectable} from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; -import { TagEntity } from './tag.entity'; +import { PrismaService } from '../shared/services/prisma.service'; +import { Tag } from '@prisma/client'; @Injectable() export class TagService { - constructor( - @InjectRepository(TagEntity) - private readonly tagRepository: Repository - ) {} + constructor(private prisma: PrismaService) {} - async findAll(): Promise { - return await this.tagRepository.find(); + async findAll(): Promise { + const res = await this.prisma.tag.findMany(); + const tags = res.map((t: Tag) => t.name); + return { tags }; } - } diff --git a/src/user/auth.middleware.ts b/src/user/auth.middleware.ts index 560fbde5..bed515c1 100644 --- a/src/user/auth.middleware.ts +++ b/src/user/auth.middleware.ts @@ -1,6 +1,5 @@ import { HttpException } from '@nestjs/common/exceptions/http.exception'; import { NestMiddleware, HttpStatus, Injectable } from '@nestjs/common'; -import { ExtractJwt, Strategy } from 'passport-jwt'; import { Request, Response, NextFunction } from 'express'; import * as jwt from 'jsonwebtoken'; import { SECRET } from '../config'; diff --git a/src/user/user.controller.ts b/src/user/user.controller.ts index 55e7121d..0236d321 100644 --- a/src/user/user.controller.ts +++ b/src/user/user.controller.ts @@ -1,9 +1,7 @@ import { Get, Post, Body, Put, Delete, Param, Controller, UsePipes } from '@nestjs/common'; -import { Request } from 'express'; import { UserService } from './user.service'; import { UserRO } from './user.interface'; import { CreateUserDto, UpdateUserDto, LoginUserDto } from './dto'; -import { HttpException } from '@nestjs/common/exceptions/http.exception'; import { User } from './user.decorator'; import { ValidationPipe } from '../shared/pipes/validation.pipe'; @@ -12,7 +10,7 @@ import { } from '@nestjs/swagger'; @ApiBearerAuth() -@ApiTags('user') +@ApiTags('users') @Controller() export class UserController { @@ -34,22 +32,15 @@ export class UserController { return this.userService.create(userData); } - @Delete('users/:slug') + @Delete('user/:slug') async delete(@Param() params) { + console.log(params); return await this.userService.delete(params.slug); } @UsePipes(new ValidationPipe()) @Post('users/login') async login(@Body('user') loginUserDto: LoginUserDto): Promise { - const _user = await this.userService.findOne(loginUserDto); - - const errors = {User: ' not found'}; - if (!_user) throw new HttpException({errors}, 401); - - const token = await this.userService.generateJWT(_user); - const {email, username, bio, image} = _user; - const user = {email, token, username, bio, image}; - return {user} + return await this.userService.login(loginUserDto); } } diff --git a/src/user/user.entity.ts b/src/user/user.entity.ts deleted file mode 100644 index be82274d..00000000 --- a/src/user/user.entity.ts +++ /dev/null @@ -1,39 +0,0 @@ -import {Entity, PrimaryGeneratedColumn, Column, BeforeInsert, JoinTable, ManyToMany, OneToMany} from 'typeorm'; -import { IsEmail } from 'class-validator'; -import * as argon2 from 'argon2'; -import { ArticleEntity } from '../article/article.entity'; - -@Entity('user') -export class UserEntity { - - @PrimaryGeneratedColumn() - id: number; - - @Column() - username: string; - - @Column() - @IsEmail() - email: string; - - @Column({default: ''}) - bio: string; - - @Column({default: ''}) - image: string; - - @Column() - password: string; - - @BeforeInsert() - async hashPassword() { - this.password = await argon2.hash(this.password); - } - - @ManyToMany(type => ArticleEntity) - @JoinTable() - favorites: ArticleEntity[]; - - @OneToMany(type => ArticleEntity, article => article.author) - articles: ArticleEntity[]; -} diff --git a/src/user/user.interface.ts b/src/user/user.interface.ts index 6758219f..128c66cc 100644 --- a/src/user/user.interface.ts +++ b/src/user/user.interface.ts @@ -1,7 +1,7 @@ export interface UserData { username: string; email: string; - token: string; + token?: string; bio: string; image?: string; } diff --git a/src/user/user.module.ts b/src/user/user.module.ts index f357bc83..fff1b01d 100644 --- a/src/user/user.module.ts +++ b/src/user/user.module.ts @@ -1,13 +1,14 @@ -import {MiddlewareConsumer, Module, NestModule, RequestMethod} from '@nestjs/common'; +import { MiddlewareConsumer, Module, NestModule, RequestMethod } from '@nestjs/common'; import { UserController } from './user.controller'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { UserEntity } from './user.entity'; import { UserService } from './user.service'; import { AuthMiddleware } from './auth.middleware'; +import { PrismaService } from '../shared/services/prisma.service'; @Module({ - imports: [TypeOrmModule.forFeature([UserEntity])], - providers: [UserService], + providers: [ + UserService, + PrismaService + ], controllers: [ UserController ], diff --git a/src/user/user.service.ts b/src/user/user.service.ts index 985ca4f4..d2721a28 100644 --- a/src/user/user.service.ts +++ b/src/user/user.service.ts @@ -1,103 +1,98 @@ import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository, getRepository, DeleteResult } from 'typeorm'; -import { UserEntity } from './user.entity'; -import {CreateUserDto, LoginUserDto, UpdateUserDto} from './dto'; +import { CreateUserDto, LoginUserDto, UpdateUserDto } from './dto'; const jwt = require('jsonwebtoken'); import { SECRET } from '../config'; import { UserRO } from './user.interface'; -import { validate } from 'class-validator'; import { HttpException } from '@nestjs/common/exceptions/http.exception'; import { HttpStatus } from '@nestjs/common'; import * as argon2 from 'argon2'; +import { PrismaService } from '../shared/services/prisma.service'; + +const select = { + email: true, + username: true, + bio: true, + image: true +}; @Injectable() export class UserService { constructor( - @InjectRepository(UserEntity) - private readonly userRepository: Repository + private prisma: PrismaService ) {} - async findAll(): Promise { - return await this.userRepository.find(); + async findAll(): Promise { + return await this.prisma.user.findMany({ select }); } - async findOne({email, password}: LoginUserDto): Promise { - const user = await this.userRepository.findOne({email}); - if (!user) { - return null; + async login(payload: LoginUserDto): Promise { + const _user = await this.prisma.user.findOne({ + where: {email: payload.email} + }); + + const errors = { User: 'email or password wrong' }; + + if (!_user) { + throw new HttpException({errors}, 401); } - if (await argon2.verify(user.password, password)) { - return user; + const authenticated = await argon2.verify(_user.password, payload.password); + + if (!authenticated) { + throw new HttpException({errors}, 401); } - return null; + const token = await this.generateJWT(_user); + const {password, ...user} = _user; + return { + user: {token, ...user} + }; } async create(dto: CreateUserDto): Promise { - - // check uniqueness of username/email const {username, email, password} = dto; - const qb = await getRepository(UserEntity) - .createQueryBuilder('user') - .where('user.username = :username', { username }) - .orWhere('user.email = :email', { email }); - const user = await qb.getOne(); + // check uniqueness of username/email + const userNotUnique = await this.prisma.user.findOne({ + where: {email} + }); - if (user) { + if (userNotUnique) { const errors = {username: 'Username and email must be unique.'}; throw new HttpException({message: 'Input data validation failed', errors}, HttpStatus.BAD_REQUEST); - } - // create new user - let newUser = new UserEntity(); - newUser.username = username; - newUser.email = email; - newUser.password = password; - newUser.articles = []; - - const errors = await validate(newUser); - if (errors.length > 0) { - const _errors = {username: 'Userinput is not valid.'}; - throw new HttpException({message: 'Input data validation failed', _errors}, HttpStatus.BAD_REQUEST); - - } else { - const savedUser = await this.userRepository.save(newUser); - return this.buildUserRO(savedUser); - } + const hashedPassword = await argon2.hash(password); + const data = { + username, + email, + password: hashedPassword, + }; + const user = await this.prisma.user.create({ data, select }); + + return {user}; } - async update(id: number, dto: UpdateUserDto): Promise { - let toUpdate = await this.userRepository.findOne(id); - delete toUpdate.password; - delete toUpdate.favorites; + async update(id: number, data: UpdateUserDto): Promise { + const where = { id }; + const user = await this.prisma.user.update({ where, data, select }); - let updated = Object.assign(toUpdate, dto); - return await this.userRepository.save(updated); + return {user}; } - async delete(email: string): Promise { - return await this.userRepository.delete({ email: email}); + async delete(email: string): Promise { + return await this.prisma.user.delete({ where: { email }, select }); } - async findById(id: number): Promise{ - const user = await this.userRepository.findOne(id); - - if (!user) { - const errors = {User: ' not found'}; - throw new HttpException({errors}, 401); - } - - return this.buildUserRO(user); + async findById(id: number): Promise{ + const user = await this.prisma.user.findOne({ where: { id }, select: {id: true, ...select} }); + return { user }; } - async findByEmail(email: string): Promise{ - const user = await this.userRepository.findOne({email: email}); - return this.buildUserRO(user); + async findByEmail(email: string): Promise{ + const user = await this.prisma.user.findOne({ where: { email }, select }); + return { user }; } public generateJWT(user) { @@ -112,17 +107,4 @@ export class UserService { exp: exp.getTime() / 1000, }, SECRET); }; - - private buildUserRO(user: UserEntity) { - const userRO = { - id: user.id, - username: user.username, - email: user.email, - bio: user.bio, - token: this.generateJWT(user), - image: user.image - }; - - return {user: userRO}; - } }