diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..a11aa7c --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +node_modules +dist + +frontend/node_modules \ No newline at end of file diff --git a/.gitignore b/.gitignore index 1f14a74..48daf7a 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ /dist /node_modules + # Logs logs *.log diff --git a/docker-compose.yml b/docker-compose.yml index 67ca79d..fe1a809 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -26,7 +26,7 @@ services: - 3000 labels: - "traefik.enable=true" # Enable reverse-proxy for this service - - "traefik.http.routers.api.rule=PathPrefix(`/api/v1`)" + - "traefik.http.routers.api.rule=PathPrefix(`/api`)" - "traefik.http.routers.api.entrypoints=web" networks: - web @@ -52,7 +52,7 @@ services: proxy: image: traefik command: - - --log.level=debug + #- --log.level=debug #- --accesslog=true - --api # Enables the web UI - --api.insecure=true diff --git a/package-lock.json b/package-lock.json index 15ae55a..6651fc1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -615,6 +615,38 @@ "uuid": "3.4.0" } }, + "@nestjs/jwt": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@nestjs/jwt/-/jwt-6.1.1.tgz", + "integrity": "sha512-XZEYC+p69N+Accktjho0B98TkwAKCZNt91+t08eukw7Gwk6FvfJB+aBzHCmQEaWUiAOpAo4eObgac86P12XOkw==", + "requires": { + "@types/jsonwebtoken": "7.2.8", + "jsonwebtoken": "8.4.0" + }, + "dependencies": { + "jsonwebtoken": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.4.0.tgz", + "integrity": "sha512-coyXjRTCy0pw5WYBpMvWOMN+Kjaik2MwTUIq9cna/W7NpO9E+iYbumZONAz3hcr+tXFJECoQVrtmIoC3Oz0gvg==", + "requires": { + "jws": "^3.1.5", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "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==" + } + } + }, "@nestjs/passport": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/@nestjs/passport/-/passport-6.1.1.tgz", @@ -632,6 +664,15 @@ "tslib": "1.10.0" } }, + "@nestjs/schedule": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@nestjs/schedule/-/schedule-0.2.0.tgz", + "integrity": "sha512-VkPPQYzsKexltKYf3yE2f1ZmUebGlkd4hfJ8kNtzLYfcfn4ZN+nn7zsJ0yyNvWlCeq3oD1cFLW3Y5qoS4+Glag==", + "requires": { + "cron": "1.8.2", + "uuid": "3.4.0" + } + }, "@nestjs/schematics": { "version": "6.8.2", "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-6.8.2.tgz", @@ -643,6 +684,15 @@ "fs-extra": "8.1.0" } }, + "@nestjs/swagger": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-4.2.3.tgz", + "integrity": "sha512-5QFy6/PRFG65m0FO1VR9XYHtXo6fdbqXJlkQKw4QmtTmIdcGgVQcByeBafE+OTArWYlDCnpMTI5G1Rh7OPGhlQ==", + "requires": { + "lodash": "4.17.15", + "path-to-regexp": "3.2.0" + } + }, "@nestjs/testing": { "version": "6.11.1", "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-6.11.1.tgz", @@ -739,6 +789,7 @@ "version": "1.17.1", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.17.1.tgz", "integrity": "sha512-RoX2EZjMiFMjZh9lmYrwgoP9RTpAjSHiJxdp4oidAQVO02T7HER3xj9UKue5534ULWeqVEkujhWcyvUce+d68w==", + "dev": true, "requires": { "@types/connect": "*", "@types/node": "*" @@ -753,6 +804,7 @@ "version": "3.4.33", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.33.tgz", "integrity": "sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A==", + "dev": true, "requires": { "@types/node": "*" } @@ -767,38 +819,23 @@ "version": "4.17.2", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.2.tgz", "integrity": "sha512-5mHFNyavtLoJmnusB8OKJ5bshSzw+qkMIBAobLrIM48HJvunFva9mOa6aBwh64lBFyNwBbs0xiEFuj4eU/NjCA==", + "dev": true, "requires": { "@types/body-parser": "*", "@types/express-serve-static-core": "*", "@types/serve-static": "*" } }, - "@types/express-jwt": { - "version": "0.0.42", - "resolved": "https://registry.npmjs.org/@types/express-jwt/-/express-jwt-0.0.42.tgz", - "integrity": "sha512-WszgUddvM1t5dPpJ3LhWNH8kfNN8GPIBrAGxgIYXVCEGx6Bx4A036aAuf/r5WH9DIEdlmp7gHOYvSM6U87B0ag==", - "requires": { - "@types/express": "*", - "@types/express-unless": "*" - } - }, "@types/express-serve-static-core": { "version": "4.17.2", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.2.tgz", "integrity": "sha512-El9yMpctM6tORDAiBwZVLMcxoTMcqqRO9dVyYcn7ycLWbvR8klrDn8CAOwRfZujZtWD7yS/mshTdz43jMOejbg==", + "dev": true, "requires": { "@types/node": "*", "@types/range-parser": "*" } }, - "@types/express-unless": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@types/express-unless/-/express-unless-0.5.1.tgz", - "integrity": "sha512-5fuvg7C69lemNgl0+v+CUxDYWVPSfXHhJPst4yTLcqi4zKJpORCxnDrnnilk3k0DTq/WrAUdvXFs01+vUqUZHw==", - "requires": { - "@types/express": "*" - } - }, "@types/istanbul-lib-coverage": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz", @@ -839,25 +876,66 @@ "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, + "@types/jsonwebtoken": { + "version": "7.2.8", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-7.2.8.tgz", + "integrity": "sha512-XENN3YzEB8D6TiUww0O8SRznzy1v+77lH7UmuN54xq/IHIsyWjWOzZuFFTtoiRuaE782uAoRwBe/wwow+vQXZw==", + "requires": { + "@types/node": "*" + } + }, "@types/mime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz", - "integrity": "sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==" + "integrity": "sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==", + "dev": true }, "@types/node": { "version": "12.12.25", "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.25.tgz", "integrity": "sha512-nf1LMGZvgFX186geVZR1xMZKKblJiRfiASTHw85zED2kI1yDKHDwTKMdkaCbTlXoRKlGKaDfYywt+V0As30q3w==" }, + "@types/passport": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.2.tgz", + "integrity": "sha512-Pf39AYKf8q+YoONym3150cEwfUD66dtwHJWvbeOzKxnA0GZZ/vAXhNWv9vMhKyRQBQZiQyWQnhYBEBlKW6G8wg==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, + "@types/passport-jwt": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/passport-jwt/-/passport-jwt-3.0.3.tgz", + "integrity": "sha512-RlOCXiTitE8kazj9jZc6/BfGCSqnv2w/eYPDm3+3iNsquHn7ratu7oIUskZx9ZtnwMdpvdpy+Z/QYClocH5NvQ==", + "dev": true, + "requires": { + "@types/express": "*", + "@types/jsonwebtoken": "*", + "@types/passport-strategy": "*" + } + }, + "@types/passport-strategy": { + "version": "0.2.35", + "resolved": "https://registry.npmjs.org/@types/passport-strategy/-/passport-strategy-0.2.35.tgz", + "integrity": "sha512-o5D19Jy2XPFoX2rKApykY15et3Apgax00RRLf0RUotPDUsYrQa7x4howLYr9El2mlUApHmCMv5CZ1IXqKFQ2+g==", + "dev": true, + "requires": { + "@types/express": "*", + "@types/passport": "*" + } + }, "@types/range-parser": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", - "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==" + "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==", + "dev": true }, "@types/serve-static": { "version": "1.13.3", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.3.tgz", "integrity": "sha512-oprSwp094zOglVrXdlo/4bAHtKTAxX6VT8FOZlBKrmyLbNvE1zxZyJ6yikMVtHIvwP45+ZQGJn+FdXGKTozq0g==", + "dev": true, "requires": { "@types/express-serve-static-core": "*", "@types/mime": "*" @@ -917,6 +995,11 @@ } } }, + "@types/validator": { + "version": "10.11.3", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-10.11.3.tgz", + "integrity": "sha512-GKF2VnEkMmEeEGvoo03ocrP9ySMuX1ypKazIYMlsjfslfBMhOAtC5dmEWKdJioW4lJN7MZRS88kalTsVClyQ9w==" + }, "@types/webpack": { "version": "4.41.3", "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.3.tgz", @@ -1202,6 +1285,7 @@ "version": "6.9.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.9.1.tgz", "integrity": "sha512-XDN92U311aINL77ieWHmqCcNlwjoP5cHXDxIxbf2MaPYuCXOHS7gHH8jktxeK5omgd52XbSTX6a4Piwd1pQmzA==", + "dev": true, "requires": { "fast-deep-equal": "^2.0.1", "fast-json-stable-stringify": "^2.0.0", @@ -1324,6 +1408,7 @@ "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, "requires": { "safer-buffer": "~2.1.0" } @@ -1369,7 +1454,8 @@ "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true }, "assign-symbols": { "version": "1.0.0", @@ -1398,7 +1484,8 @@ "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true }, "atob": { "version": "2.1.2", @@ -1409,12 +1496,14 @@ "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true }, "aws4": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz", - "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==" + "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==", + "dev": true }, "axios": { "version": "0.19.2", @@ -1591,6 +1680,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, "requires": { "tweetnacl": "^0.14.3" } @@ -1952,7 +2042,8 @@ "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true }, "chalk": { "version": "2.4.2", @@ -2022,6 +2113,11 @@ "safe-buffer": "^5.0.1" } }, + "class-transformer": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.2.3.tgz", + "integrity": "sha512-qsP+0xoavpOlJHuYsQJsN58HXSl8Jvveo+T37rEvCEeRfMWoytAyR0Ua/YsFgpM6AZYZ/og2PJwArwzJl1aXtQ==" + }, "class-utils": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", @@ -2045,6 +2141,16 @@ } } }, + "class-validator": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.11.0.tgz", + "integrity": "sha512-niAmmSPFku9xsnpYYrddy8NZRrCX3yyoZ/rgPKOilE5BG0Ma1eVCIxpR4X0LasL/6BzbYzsutG+mSbAXlh4zNw==", + "requires": { + "@types/validator": "10.11.3", + "google-libphonenumber": "^3.1.6", + "validator": "12.0.0" + } + }, "cli-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-2.0.0.tgz", @@ -2332,6 +2438,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, "requires": { "delayed-stream": "~1.0.0" } @@ -2550,6 +2657,14 @@ "sha.js": "^2.4.8" } }, + "cron": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/cron/-/cron-1.8.2.tgz", + "integrity": "sha512-Gk2c4y6xKEO8FSAUTklqtfSr7oTq0CiPQeLBG5Fl0qoXpZyMcj1SG59YL+hqq04bu6/IuEA7lMkYDAplQNKkyg==", + "requires": { + "moment-timezone": "^0.5.x" + } + }, "cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", @@ -2616,6 +2731,7 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, "requires": { "assert-plus": "^1.0.0" } @@ -2731,7 +2847,8 @@ "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true }, "depd": { "version": "1.1.2", @@ -2864,6 +2981,7 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, "requires": { "jsbn": "~0.1.0", "safer-buffer": "^2.1.0" @@ -3274,41 +3392,6 @@ } } }, - "express-session": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.0.tgz", - "integrity": "sha512-t4oX2z7uoSqATbMfsxWMbNjAL0T5zpvcJCk3Z9wnPPN7ibddhnmDZXHfEcoBMG2ojKXZoCyPMc5FbtK+G7SoDg==", - "requires": { - "cookie": "0.4.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "~2.0.0", - "on-headers": "~1.0.2", - "parseurl": "~1.3.3", - "safe-buffer": "5.2.0", - "uid-safe": "~2.1.5" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" - }, - "safe-buffer": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", - "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" - } - } - }, "ext": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz", @@ -3327,7 +3410,8 @@ "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true }, "extend-shallow": { "version": "3.0.2", @@ -3429,12 +3513,14 @@ "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true }, "fast-deep-equal": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true }, "fast-diff": { "version": "1.2.0", @@ -3445,7 +3531,8 @@ "fast-json-stable-stringify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true }, "fast-levenshtein": { "version": "2.0.6", @@ -3619,7 +3706,8 @@ "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true }, "fork-ts-checker-webpack-plugin": { "version": "4.0.2", @@ -3640,6 +3728,7 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.6", @@ -4335,6 +4424,7 @@ "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, "requires": { "assert-plus": "^1.0.0" } @@ -4379,6 +4469,11 @@ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true }, + "google-libphonenumber": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/google-libphonenumber/-/google-libphonenumber-3.2.6.tgz", + "integrity": "sha512-6QCQAaKJlSd/1dUqvdQf7zzfb3uiZHsG8yhCfOdCVRfMuPZ/VDIEB47y5SYwjPQJPs7ebfW5jj6PeobB9JJ4JA==" + }, "graceful-fs": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", @@ -4394,12 +4489,14 @@ "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true }, "har-validator": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "dev": true, "requires": { "ajv": "^6.5.5", "har-schema": "^2.0.0" @@ -4538,6 +4635,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, "requires": { "assert-plus": "^1.0.0", "jsprim": "^1.2.2", @@ -4940,7 +5038,8 @@ "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true }, "is-windows": { "version": "1.0.2", @@ -4974,7 +5073,8 @@ "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true }, "istanbul-lib-coverage": { "version": "2.0.5", @@ -5538,7 +5638,8 @@ "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true }, "jsdom": { "version": "11.12.0", @@ -5597,17 +5698,20 @@ "json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true }, "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true }, "json5": { "version": "1.0.1", @@ -5663,6 +5767,7 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, "requires": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", @@ -5680,35 +5785,6 @@ "safe-buffer": "^5.0.1" } }, - "jwks-rsa": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-1.6.2.tgz", - "integrity": "sha512-c/mFFq/wVXSkHzHGH+hLUwLeRKSCofNHJZKPzHho4YmO9LGwAazk7akfABvWhduS9OejWvqBS2jA69YeruEvNA==", - "requires": { - "@types/express-jwt": "0.0.42", - "debug": "^4.1.0", - "jsonwebtoken": "^8.5.1", - "limiter": "^1.1.4", - "lru-memoizer": "^2.0.1", - "ms": "^2.1.2", - "request": "^2.88.0" - }, - "dependencies": { - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - } - } - }, "jws": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", @@ -5752,11 +5828,6 @@ "type-check": "~0.3.2" } }, - "limiter": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", - "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" - }, "lines-and-columns": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", @@ -5812,13 +5883,7 @@ "lodash": { "version": "4.17.15", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - }, - "lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" }, "lodash.debounce": { "version": "4.0.8", @@ -5916,31 +5981,6 @@ "yallist": "^3.0.2" } }, - "lru-memoizer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.0.1.tgz", - "integrity": "sha512-kGl+zlIqdQL24f0Q9IUSUZeSvA7nqXPFLA7suFh00v4KVqfXkZJtkPfTfXV/oQMSPfNr6VT4xGkRAUPhFnGyxQ==", - "requires": { - "lodash.clonedeep": "^4.5.0", - "lru-cache": "~4.0.0" - }, - "dependencies": { - "lru-cache": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz", - "integrity": "sha1-HRdnnAac2l0ECZGgnbwsDbN35V4=", - "requires": { - "pseudomap": "^1.0.1", - "yallist": "^2.0.0" - } - }, - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" - } - } - }, "lru-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", @@ -6223,6 +6263,19 @@ "minimist": "0.0.8" } }, + "moment": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", + "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" + }, + "moment-timezone": { + "version": "0.5.27", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.27.tgz", + "integrity": "sha512-EIKQs7h5sAsjhPCqN6ggx6cEbs94GK050254TIJySD1bzoM5JTYDwAU1IoVOeTOL6Gm27kYJ51/uuvq1kIlrbw==", + "requires": { + "moment": ">= 2.9.0" + } + }, "move-concurrently": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", @@ -6268,11 +6321,6 @@ "xtend": "^4.0.0" } }, - "mustache": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.0.0.tgz", - "integrity": "sha512-FJgjyX/IVkbXBXYUwH+OYwQKqWpFPLaLVESd70yHjSDunwzV2hZOoTBvPf4KLoxesUzzyfTH6F784Uqd7Wm5yA==" - }, "mute-stream": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", @@ -6538,7 +6586,8 @@ "oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true }, "object-assign": { "version": "4.1.1", @@ -6641,11 +6690,6 @@ "ee-first": "1.1.1" } }, - "on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" - }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -6994,16 +7038,6 @@ "pause": "0.0.1" } }, - "passport-auth0": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/passport-auth0/-/passport-auth0-1.3.1.tgz", - "integrity": "sha512-HT7qbvw1dlRrNAIsisgsACaiDBcLLBMUFIFCt4dZ4vLrJF8bkNWj9QgeTAcXjQoYJ5sXfpaD/wYUGBSe98cG0g==", - "requires": { - "passport-oauth": "^1.0.0", - "passport-oauth2": "^1.5.0", - "request": "^2.88.0" - } - }, "passport-jwt": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.0.tgz", @@ -7044,6 +7078,16 @@ "utils-merge": "1.x.x" } }, + "passport-spotify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/passport-spotify/-/passport-spotify-1.1.0.tgz", + "integrity": "sha512-jLVtH7Z+5XW854H0mAsdpGJq5dMSwxQQu1jcXhq+3JlB8HuP5xkCLMuQ5K24S7WYh+cAHLrEKJPAoeo1D8CbUg==", + "requires": { + "passport-oauth": "1.0.0", + "querystring": "~0.2.0", + "util": "~0.11.0" + } + }, "passport-strategy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", @@ -7126,7 +7170,8 @@ "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true }, "pg": { "version": "7.17.1", @@ -7329,15 +7374,11 @@ "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", "dev": true }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" - }, "psl": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.7.0.tgz", - "integrity": "sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ==" + "integrity": "sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ==", + "dev": true }, "public-encrypt": { "version": "4.0.3", @@ -7389,7 +7430,8 @@ "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true }, "qs": { "version": "6.7.0", @@ -7399,8 +7441,7 @@ "querystring": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", - "dev": true + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" }, "querystring-es3": { "version": "0.2.1", @@ -7408,11 +7449,6 @@ "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", "dev": true }, - "random-bytes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", - "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" - }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -7575,6 +7611,7 @@ "version": "2.88.0", "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "dev": true, "requires": { "aws-sign2": "~0.7.0", "aws4": "^1.8.0", @@ -7601,17 +7638,20 @@ "punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true }, "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true }, "tough-cookie": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "dev": true, "requires": { "psl": "^1.1.24", "punycode": "^1.4.1" @@ -8186,6 +8226,7 @@ "version": "1.16.1", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dev": true, "requires": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", @@ -8527,6 +8568,19 @@ "has-flag": "^3.0.0" } }, + "swagger-ui-dist": { + "version": "3.25.0", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.25.0.tgz", + "integrity": "sha512-vwvJPPbdooTvDwLGzjIXinOXizDJJ6U1hxnJL3y6U3aL1d2MSXDmKg2139XaLBhsVZdnQJV2bOkX4reB+RXamg==" + }, + "swagger-ui-express": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.1.3.tgz", + "integrity": "sha512-f8SEn4YWkKh/HGK0ZjuA2VqA78i1aY6OIa5cqYNgOkBobfHV6Mz4dphQW/us8HYhEFfbENq329PyfIonWfzFrw==", + "requires": { + "swagger-ui-dist": "^3.18.1" + } + }, "symbol-observable": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", @@ -9021,6 +9075,7 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, "requires": { "safe-buffer": "^5.0.1" } @@ -9028,7 +9083,8 @@ "tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true }, "type": { "version": "1.2.0", @@ -9120,14 +9176,6 @@ "integrity": "sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==", "dev": true }, - "uid-safe": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", - "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", - "requires": { - "random-bytes": "~1.0.0" - } - }, "uid2": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.3.tgz", @@ -9230,6 +9278,7 @@ "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, "requires": { "punycode": "^2.1.0" } @@ -9268,7 +9317,6 @@ "version": "0.11.1", "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", - "dev": true, "requires": { "inherits": "2.0.3" } @@ -9310,6 +9358,11 @@ "spdx-expression-parse": "^3.0.0" } }, + "validator": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-12.0.0.tgz", + "integrity": "sha512-r5zA1cQBEOgYlesRmSEwc9LkbfNLTtji+vWyaHzRZUxCTHdsX3bd+sdHfs5tGZ2W6ILGGsxWxCNwT/h3IY/3ng==" + }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -9319,6 +9372,7 @@ "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, "requires": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", diff --git a/package.json b/package.json index 737e4e1..c813072 100644 --- a/package.json +++ b/package.json @@ -23,19 +23,22 @@ "@nestjs/common": "^6.7.2", "@nestjs/config": "^0.1.0", "@nestjs/core": "^6.7.2", + "@nestjs/jwt": "^6.1.1", "@nestjs/passport": "^6.1.1", "@nestjs/platform-express": "^6.7.2", + "@nestjs/schedule": "^0.2.0", + "@nestjs/swagger": "^4.2.3", "@nestjs/typeorm": "^6.2.0", - "express-session": "^1.17.0", - "jwks-rsa": "^1.6.2", - "mustache": "^4.0.0", + "class-transformer": "^0.2.3", + "class-validator": "^0.11.0", "passport": "^0.4.1", - "passport-auth0": "^1.3.1", "passport-jwt": "^4.0.0", + "passport-spotify": "^1.1.0", "pg": "^7.17.1", "reflect-metadata": "^0.1.13", "rimraf": "^3.0.0", "rxjs": "^6.5.3", + "swagger-ui-express": "^4.1.3", "typeorm": "^0.2.22" }, "devDependencies": { @@ -45,6 +48,7 @@ "@types/express": "^4.17.1", "@types/jest": "^24.0.18", "@types/node": "^12.7.5", + "@types/passport-jwt": "^3.0.3", "@types/supertest": "^2.0.8", "jest": "^24.9.0", "prettier": "^1.18.2", diff --git a/src/app.module.ts b/src/app.module.ts index fea78ad..bb89b9a 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,17 +1,17 @@ import { Module } from "@nestjs/common"; import { ConfigModule } from "@nestjs/config"; -import { AuthenticationModule } from "./authentication/authentication.module"; +import { AuthModule } from "./auth/auth.module"; import { DatabaseModule } from "./database/database.module"; -import { ConnectionsModule } from "./connections/connections.module"; -import { FrontendModule } from './frontend/frontend.module'; +import { SourcesModule } from "./sources/sources.module"; +import { UsersModule } from "./users/users.module"; @Module({ imports: [ ConfigModule.forRoot({ isGlobal: true }), DatabaseModule, - AuthenticationModule, - ConnectionsModule, - FrontendModule + AuthModule, + UsersModule, + SourcesModule ] }) export class AppModule {} diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts new file mode 100644 index 0000000..d72c8f7 --- /dev/null +++ b/src/auth/auth.controller.ts @@ -0,0 +1,40 @@ +import { Controller, Get, Res, UseGuards } from "@nestjs/common"; +import { AuthGuard } from "@nestjs/passport"; +import { Response } from "express"; +import { User } from "../users/user.entity"; +import { ReqUser } from "./decorators/req-user.decorator"; +import { AuthService } from "./auth.service"; +import { ConfigService } from "@nestjs/config"; + +@Controller("api/v1/auth") +export class AuthController { + constructor( + private readonly authService: AuthService, + private readonly config: ConfigService + ) {} + + @Get("spotify") + @UseGuards(AuthGuard("spotify")) + spotifyRedirect() { + // User is redirected by AuthGuard + } + + @Get("spotify/callback") + @UseGuards(AuthGuard("spotify")) + async spotifyCallback(@ReqUser() user: User, @Res() res: Response) { + const { accessToken } = await this.authService.createToken(user); + + // Transmit accessToken to Frontend + res.cookie("listory_access_token", accessToken, { + // SPA will directly read cookie, save it to local storage and delete it + // 15 Minutes should be enough + maxAge: 15 * 60 * 1000, + + // Must be readable by SPA + httpOnly: false + }); + + // Redirect User to SPA + res.redirect("/"); + } +} diff --git a/src/auth/auth.module.ts b/src/auth/auth.module.ts new file mode 100644 index 0000000..bde99a3 --- /dev/null +++ b/src/auth/auth.module.ts @@ -0,0 +1,27 @@ +import { Module } from "@nestjs/common"; +import { ConfigService } from "@nestjs/config"; +import { JwtModule } from "@nestjs/jwt"; +import { PassportModule } from "@nestjs/passport"; +import { UsersModule } from "../users/users.module"; +import { AuthController } from "./auth.controller"; +import { AuthService } from "./auth.service"; +import { JwtStrategy } from "./jwt.strategy"; +import { SpotifyStrategy } from "./spotify.strategy"; + +@Module({ + imports: [ + PassportModule.register({ defaultStrategy: "jwt" }), + JwtModule.registerAsync({ + useFactory: (config: ConfigService) => ({ + secret: config.get("JWT_SECRET"), + signOptions: { expiresIn: config.get("JWT_EXPIRATION_TIME") } + }), + inject: [ConfigService] + }), + UsersModule + ], + providers: [AuthService, SpotifyStrategy, JwtStrategy], + exports: [PassportModule], + controllers: [AuthController] +}) +export class AuthModule {} diff --git a/src/connections/connections.service.spec.ts b/src/auth/auth.service.spec.ts similarity index 51% rename from src/connections/connections.service.spec.ts rename to src/auth/auth.service.spec.ts index f83651e..641fa7a 100644 --- a/src/connections/connections.service.spec.ts +++ b/src/auth/auth.service.spec.ts @@ -1,15 +1,15 @@ import { Test, TestingModule } from "@nestjs/testing"; -import { ConnectionsService } from "./connections.service"; +import { AuthService } from "./auth.service"; -describe("ConnectionsService", () => { - let service: ConnectionsService; +describe("AuthService", () => { + let service: AuthService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ - providers: [ConnectionsService] + providers: [AuthService] }).compile(); - service = module.get(ConnectionsService); + service = module.get(AuthService); }); it("should be defined", () => { diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts new file mode 100644 index 0000000..2fd876d --- /dev/null +++ b/src/auth/auth.service.ts @@ -0,0 +1,47 @@ +import { Injectable } from "@nestjs/common"; +import { User } from "../users/user.entity"; +import { UsersService } from "../users/users.service"; +import { LoginDto } from "./dto/login.dto"; +import { JwtService } from "@nestjs/jwt"; + +@Injectable() +export class AuthService { + constructor( + private readonly usersService: UsersService, + private readonly jwtService: JwtService + ) {} + + async spotifyLogin({ + accessToken, + refreshToken, + profile + }: LoginDto): Promise { + const user = await this.usersService.createOrUpdate({ + displayName: profile.displayName, + photo: profile.photos.length > 0 ? profile.photos[0] : null, + spotify: { + id: profile.id, + accessToken, + refreshToken + } + }); + + return user; + } + + async createToken(user: User): Promise<{ accessToken }> { + const payload = { + sub: user.id, + name: user.displayName, + picture: user.photo + }; + + const token = await this.jwtService.signAsync(payload); + + return { accessToken: token }; + } + + async findUser(id: string): Promise { + return this.usersService.findById(id); + } +} diff --git a/src/auth/decorators/auth.decorator.ts b/src/auth/decorators/auth.decorator.ts new file mode 100644 index 0000000..566a4bc --- /dev/null +++ b/src/auth/decorators/auth.decorator.ts @@ -0,0 +1,11 @@ +import { applyDecorators, UseGuards } from "@nestjs/common"; +import { AuthGuard } from "@nestjs/passport"; +import { ApiBearerAuth, ApiUnauthorizedResponse } from "@nestjs/swagger"; + +export function Auth() { + return applyDecorators( + UseGuards(AuthGuard("jwt")), + ApiBearerAuth(), + ApiUnauthorizedResponse({ description: 'Unauthorized"' }) + ); +} diff --git a/src/auth/decorators/req-user.decorator.ts b/src/auth/decorators/req-user.decorator.ts new file mode 100644 index 0000000..c9686f9 --- /dev/null +++ b/src/auth/decorators/req-user.decorator.ts @@ -0,0 +1,3 @@ +import { createParamDecorator } from "@nestjs/common"; + +export const ReqUser = createParamDecorator((data, req) => req.user); diff --git a/src/auth/dto/login.dto.ts b/src/auth/dto/login.dto.ts new file mode 100644 index 0000000..d9905b1 --- /dev/null +++ b/src/auth/dto/login.dto.ts @@ -0,0 +1,9 @@ +export class LoginDto { + accessToken: string; + refreshToken: string; + profile: { + id: string; + displayName: string; + photos: string[]; + }; +} diff --git a/src/auth/jwt.strategy.ts b/src/auth/jwt.strategy.ts new file mode 100644 index 0000000..307ea28 --- /dev/null +++ b/src/auth/jwt.strategy.ts @@ -0,0 +1,23 @@ +import { Injectable } from "@nestjs/common"; +import { ConfigService } from "@nestjs/config"; +import { PassportStrategy } from "@nestjs/passport"; +import { Strategy, ExtractJwt } from "passport-jwt"; +import { AuthService } from "./auth.service"; + +@Injectable() +export class JwtStrategy extends PassportStrategy(Strategy) { + constructor( + private readonly config: ConfigService, + private readonly authService: AuthService + ) { + super({ + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + ignoreExpiration: false, + secretOrKey: config.get("JWT_SECRET") + }); + } + + async validate(payload: any) { + return this.authService.findUser(payload.sub); + } +} diff --git a/src/auth/spotify.strategy.ts b/src/auth/spotify.strategy.ts new file mode 100644 index 0000000..e31c9b0 --- /dev/null +++ b/src/auth/spotify.strategy.ts @@ -0,0 +1,33 @@ +import { Injectable } from "@nestjs/common"; +import { ConfigService } from "@nestjs/config"; +import { PassportStrategy } from "@nestjs/passport"; +import { Strategy } from "passport-spotify"; +import { AuthService } from "./auth.service"; + +@Injectable() +export class SpotifyStrategy extends PassportStrategy(Strategy) { + constructor( + private readonly config: ConfigService, + private readonly authService: AuthService + ) { + super({ + clientID: config.get("SPOTIFY_CLIENT_ID"), + clientSecret: config.get("SPOTIFY_CLIENT_SECRET"), + callbackURL: `${config.get("BASE_DOMAIN") || + "http://localhost:3000"}/api/v1/auth/spotify/callback`, + scope: [ + "user-read-private", + "user-read-email", + "user-read-recently-played" + ] + }); + } + + async validate(accessToken: string, refreshToken: string, profile: any) { + return await this.authService.spotifyLogin({ + accessToken, + refreshToken, + profile + }); + } +} diff --git a/src/authentication/authentication.module.ts b/src/authentication/authentication.module.ts deleted file mode 100644 index 81cb14e..0000000 --- a/src/authentication/authentication.module.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Module } from "@nestjs/common"; -import { PassportModule } from "@nestjs/passport"; -import { JwtStrategy } from "./jwt.strategy"; - -@Module({ - imports: [PassportModule.register({ defaultStrategy: "jwt" })], - providers: [JwtStrategy], - exports: [PassportModule] -}) -export class AuthenticationModule {} diff --git a/src/authentication/jwt.strategy.ts b/src/authentication/jwt.strategy.ts deleted file mode 100644 index 72f2d40..0000000 --- a/src/authentication/jwt.strategy.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Injectable } from "@nestjs/common"; -import { PassportStrategy } from "@nestjs/passport"; -import { passportJwtSecret } from "jwks-rsa"; -import { ExtractJwt, Strategy } from "passport-jwt"; -import { ConfigService } from "@nestjs/config"; - -@Injectable() -export class JwtStrategy extends PassportStrategy(Strategy) { - constructor(private readonly config: ConfigService) { - super({ - secretOrKeyProvider: passportJwtSecret({ - cache: true, - rateLimit: true, - jwksRequestsPerMinute: 5, - jwksUri: `${config.get("AUTH0_DOMAIN")}.well-known/jwks.json` - }), - - jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), - audience: config.get("AUTH0_AUDIENCE"), - issuer: config.get("AUTH0_DOMAIN"), - algorithms: ["RS256"] - }); - } - - validate(payload: any) { - return payload; - } -} diff --git a/src/connections/connection-type.enum.ts b/src/connections/connection-type.enum.ts deleted file mode 100644 index 9b06251..0000000 --- a/src/connections/connection-type.enum.ts +++ /dev/null @@ -1,3 +0,0 @@ -export enum ConnectionType { - SPOTIFY = "spotify" -} diff --git a/src/connections/connection.entity.ts b/src/connections/connection.entity.ts deleted file mode 100644 index d4eb50b..0000000 --- a/src/connections/connection.entity.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Entity, Column, PrimaryGeneratedColumn } from "typeorm"; -import { ConnectionType } from "./connection-type.enum"; - -@Entity() -export class Connection { - @PrimaryGeneratedColumn("uuid") - id: string; - - @Column() - userID: string; - - @Column() - type: ConnectionType; -} diff --git a/src/connections/connections.controller.spec.ts b/src/connections/connections.controller.spec.ts deleted file mode 100644 index 6c11c40..0000000 --- a/src/connections/connections.controller.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from "@nestjs/testing"; -import { ConnectionsController } from "./connections.controller"; - -describe("Connections Controller", () => { - let controller: ConnectionsController; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - controllers: [ConnectionsController] - }).compile(); - - controller = module.get(ConnectionsController); - }); - - it("should be defined", () => { - expect(controller).toBeDefined(); - }); -}); diff --git a/src/connections/connections.controller.ts b/src/connections/connections.controller.ts deleted file mode 100644 index 6906fac..0000000 --- a/src/connections/connections.controller.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Controller, Get, UseGuards } from "@nestjs/common"; -import { AuthGuard } from "@nestjs/passport"; - -@Controller("api/v1/connections") -export class ConnectionsController { - @Get() - @UseGuards(AuthGuard("jwt")) - get() { - return { msg: "Success!" }; - } -} diff --git a/src/connections/connections.module.ts b/src/connections/connections.module.ts deleted file mode 100644 index c4d4e4f..0000000 --- a/src/connections/connections.module.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Module } from "@nestjs/common"; -import { TypeOrmModule } from "@nestjs/typeorm"; -import { ConnectionsController } from "./connections.controller"; -import { ConnectionsRepository } from "./connections.repository"; -import { ConnectionsService } from "./connections.service"; - -@Module({ - imports: [TypeOrmModule.forFeature([ConnectionsRepository])], - controllers: [ConnectionsController], - providers: [ConnectionsService] -}) -export class ConnectionsModule {} diff --git a/src/connections/connections.repository.ts b/src/connections/connections.repository.ts deleted file mode 100644 index e35de47..0000000 --- a/src/connections/connections.repository.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { EntityRepository, Repository } from "typeorm"; -import { Connection } from "./connection.entity"; - -@EntityRepository(Connection) -export class ConnectionsRepository extends Repository {} diff --git a/src/connections/connections.service.ts b/src/connections/connections.service.ts deleted file mode 100644 index cff2c0a..0000000 --- a/src/connections/connections.service.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Injectable } from "@nestjs/common"; -import { ConnectionsRepository } from "./connections.repository"; - -@Injectable() -export class ConnectionsService { - constructor(private readonly connectionRepository: ConnectionsRepository) {} -} diff --git a/src/database/database.module.ts b/src/database/database.module.ts index 59b9e44..45a0fb7 100644 --- a/src/database/database.module.ts +++ b/src/database/database.module.ts @@ -1,4 +1,4 @@ -import { ConfigModule, ConfigService } from "@nestjs/config"; +import { ConfigService } from "@nestjs/config"; import { TypeOrmModule } from "@nestjs/typeorm"; import { join } from "path"; @@ -17,9 +17,9 @@ export const DatabaseModule = TypeOrmModule.forRootAsync({ entities: [join(__dirname, "..", "**/*.entity.{ts,js}")], // Migrations - migrationsRun: true, - migrations: [join(__dirname, "migrations", "*.{ts,js}")], - synchronize: false + // migrationsRun: true, + // migrations: [join(__dirname, "migrations", "*.{ts,js}")], + synchronize: true }), inject: [ConfigService] }); diff --git a/src/frontend/frontend.controller.ts b/src/frontend/frontend.controller.ts deleted file mode 100644 index 95376d1..0000000 --- a/src/frontend/frontend.controller.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Controller, Get } from "@nestjs/common"; - -@Controller("") -export class FrontendController { - @Get() - index() {} -} diff --git a/src/frontend/frontend.module.ts b/src/frontend/frontend.module.ts deleted file mode 100644 index e19cf75..0000000 --- a/src/frontend/frontend.module.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Module } from '@nestjs/common'; -import { FrontendController } from './frontend.controller'; - -@Module({ - controllers: [FrontendController] -}) -export class FrontendModule {} diff --git a/src/main.ts b/src/main.ts index 228820c..ee25cfa 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,14 +1,23 @@ import { NestFactory } from "@nestjs/core"; import { NestExpressApplication } from "@nestjs/platform-express"; -import { join } from "path"; +import { DocumentBuilder, SwaggerModule } from "@nestjs/swagger"; import { AppModule } from "./app.module"; async function bootstrap() { const app = await NestFactory.create(AppModule); - app.useStaticAssets(join(__dirname, "frontend", "public")); - app.setBaseViewsDir(join(__dirname, "frontend", "views")); - app.setViewEngine("mustache"); + // Setup API Docs + const options = new DocumentBuilder() + .setTitle("Listory") + .setDescription("Track and analyze your Spotify Listens") + .setVersion("1.0") + .addBearerAuth() + .addTag("user") + .addTag("listens") + .addTag("auth") + .build(); + const document = SwaggerModule.createDocument(app, options); + SwaggerModule.setup("api/docs", app, document); await app.listen(3000); } diff --git a/src/sources/sources.module.ts b/src/sources/sources.module.ts new file mode 100644 index 0000000..a061706 --- /dev/null +++ b/src/sources/sources.module.ts @@ -0,0 +1,7 @@ +import { Module } from '@nestjs/common'; +import { SpotifyModule } from './spotify/spotify.module'; + +@Module({ + imports: [SpotifyModule] +}) +export class SourcesModule {} diff --git a/src/sources/spotify/spotify-connection.entity.ts b/src/sources/spotify/spotify-connection.entity.ts new file mode 100644 index 0000000..f765443 --- /dev/null +++ b/src/sources/spotify/spotify-connection.entity.ts @@ -0,0 +1,12 @@ +import { Column } from "typeorm"; + +export class SpotifyConnection { + @Column() + id: string; + + @Column() + accessToken: string; + + @Column() + refreshToken: string; +} diff --git a/src/sources/spotify/spotify.module.ts b/src/sources/spotify/spotify.module.ts new file mode 100644 index 0000000..89d3187 --- /dev/null +++ b/src/sources/spotify/spotify.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { SpotifyService } from './spotify.service'; +import { SpotifyApiModule } from './spotify-api/spotify-api.module'; + +@Module({ + providers: [SpotifyService], + imports: [SpotifyApiModule] +}) +export class SpotifyModule {} diff --git a/src/sources/spotify/spotify.service.spec.ts b/src/sources/spotify/spotify.service.spec.ts new file mode 100644 index 0000000..5067a85 --- /dev/null +++ b/src/sources/spotify/spotify.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { SpotifyService } from './spotify.service'; + +describe('SpotifyService', () => { + let service: SpotifyService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [SpotifyService], + }).compile(); + + service = module.get(SpotifyService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/src/sources/spotify/spotify.service.ts b/src/sources/spotify/spotify.service.ts new file mode 100644 index 0000000..612df07 --- /dev/null +++ b/src/sources/spotify/spotify.service.ts @@ -0,0 +1,8 @@ +import { Injectable } from '@nestjs/common'; +import {Interval} from "@nestjs/cron" + +@Injectable() +export class SpotifyService { + @Interval + +} diff --git a/src/users/dto/create-or-update.dto.ts b/src/users/dto/create-or-update.dto.ts new file mode 100644 index 0000000..2bbbfaf --- /dev/null +++ b/src/users/dto/create-or-update.dto.ts @@ -0,0 +1,10 @@ +export class CreateOrUpdateDto { + displayName: string; + photo?: string; + + spotify: { + id: string; + accessToken: string; + refreshToken: string; + }; +} diff --git a/src/users/user.entity.ts b/src/users/user.entity.ts new file mode 100644 index 0000000..64460d4 --- /dev/null +++ b/src/users/user.entity.ts @@ -0,0 +1,17 @@ +import { Entity, Column, PrimaryGeneratedColumn, OneToOne } from "typeorm"; +import { SpotifyConnection } from "../sources/spotify/spotify-connection.entity"; + +@Entity() +export class User { + @PrimaryGeneratedColumn("uuid") + id: string; + + @Column() + displayName: string; + + @Column({ nullable: true }) + photo?: string; + + @Column(type => SpotifyConnection) + spotify: SpotifyConnection; +} diff --git a/src/users/user.repository.ts b/src/users/user.repository.ts new file mode 100644 index 0000000..023ff9f --- /dev/null +++ b/src/users/user.repository.ts @@ -0,0 +1,5 @@ +import { EntityRepository, Repository } from "typeorm"; +import { User } from "./user.entity"; + +@EntityRepository(User) +export class UserRepository extends Repository {} diff --git a/src/frontend/frontend.controller.spec.ts b/src/users/users.controller.spec.ts similarity index 51% rename from src/frontend/frontend.controller.spec.ts rename to src/users/users.controller.spec.ts index fd6cb4f..5bd21ba 100644 --- a/src/frontend/frontend.controller.spec.ts +++ b/src/users/users.controller.spec.ts @@ -1,15 +1,15 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { FrontendController } from './frontend.controller'; +import { UsersController } from './users.controller'; -describe('Frontend Controller', () => { - let controller: FrontendController; +describe('Users Controller', () => { + let controller: UsersController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ - controllers: [FrontendController], + controllers: [UsersController], }).compile(); - controller = module.get(FrontendController); + controller = module.get(UsersController); }); it('should be defined', () => { diff --git a/src/users/users.controller.ts b/src/users/users.controller.ts new file mode 100644 index 0000000..25f2878 --- /dev/null +++ b/src/users/users.controller.ts @@ -0,0 +1,17 @@ +import { Controller, Get } from "@nestjs/common"; +import { Auth } from "../auth/decorators/auth.decorator"; +import { ReqUser } from "../auth/decorators/req-user.decorator"; +import { User } from "./user.entity"; + +@Controller("api/v1/users") +export class UsersController { + @Get("me") + @Auth() + getMe(@ReqUser() user: User): Omit { + return { + id: user.id, + displayName: user.displayName, + photo: user.photo + }; + } +} diff --git a/src/users/users.module.ts b/src/users/users.module.ts new file mode 100644 index 0000000..7c46165 --- /dev/null +++ b/src/users/users.module.ts @@ -0,0 +1,13 @@ +import { Module } from "@nestjs/common"; +import { TypeOrmModule } from "@nestjs/typeorm"; +import { UserRepository } from "./user.repository"; +import { UsersService } from "./users.service"; +import { UsersController } from './users.controller'; + +@Module({ + imports: [TypeOrmModule.forFeature([UserRepository])], + providers: [UsersService], + exports: [UsersService], + controllers: [UsersController] +}) +export class UsersModule {} diff --git a/src/users/users.service.spec.ts b/src/users/users.service.spec.ts new file mode 100644 index 0000000..20d2ca5 --- /dev/null +++ b/src/users/users.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from "@nestjs/testing"; +import { UsersService } from "./users.service"; + +describe("UsersService", () => { + let service: UsersService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [UsersService] + }).compile(); + + service = module.get(UsersService); + }); + + it("should be defined", () => { + expect(service).toBeDefined(); + }); +}); diff --git a/src/users/users.service.ts b/src/users/users.service.ts new file mode 100644 index 0000000..12b4053 --- /dev/null +++ b/src/users/users.service.ts @@ -0,0 +1,42 @@ +import { Injectable, NotFoundException } from "@nestjs/common"; +import { CreateOrUpdateDto } from "./dto/create-or-update.dto"; +import { User } from "./user.entity"; +import { UserRepository } from "./user.repository"; + +@Injectable() +export class UsersService { + constructor(private readonly userRepository: UserRepository) {} + + async findById(id: string): Promise { + const user = await this.userRepository.findOne(id); + + if (!user) { + throw new NotFoundException("UserNotFound"); + } + + return user; + } + + async createOrUpdate(data: CreateOrUpdateDto): Promise { + let user = await this.userRepository.findOne({ + where: { spotify: { id: data.spotify.id } } + }); + + if (!user) { + user = this.userRepository.create({ + spotify: { + id: data.spotify.id + } + }); + } + + user.spotify.accessToken = data.spotify.accessToken; + user.spotify.refreshToken = data.spotify.refreshToken; + user.displayName = data.displayName; + user.photo = data.photo; + + await this.userRepository.save(user); + + return user; + } +}