This commit is contained in:
Bastian Wagner
2024-09-12 21:33:11 +02:00
parent 6abfdcb632
commit c00aad559d
36 changed files with 1118 additions and 397 deletions

View File

@@ -4,4 +4,9 @@ MYSQL_PASSWORD=PAssword123
MYSQL_DATABASE=local_db
DATABASE_HOST=localhost
DATABASE_PORT=3306
MYSQL_ROOT_PASSWORD=kjsdahflöijsdiu
MYSQL_ROOT_PASSWORD=kjsdahflöijsdiu
# SECURITY
# SECURITY
JWT_SECRET=
JWT_EXPIRES_IN=10m

View File

@@ -1,4 +1,6 @@
{
"singleQuote": true,
"trailingComma": "all"
"trailingComma": "all",
"endOfLine": "auto",
"tabWidth": 2
}

201
api/package-lock.json generated
View File

@@ -9,11 +9,16 @@
"version": "0.0.1",
"license": "UNLICENSED",
"dependencies": {
"@nestjs/axios": "^3.0.3",
"@nestjs/common": "^10.0.0",
"@nestjs/config": "^3.2.3",
"@nestjs/core": "^10.0.0",
"@nestjs/jwt": "^10.2.0",
"@nestjs/platform-express": "^10.0.0",
"@nestjs/typeorm": "^10.0.2",
"axios": "^1.7.7",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"mysql2": "^3.11.2",
"reflect-metadata": "^0.2.0",
"rxjs": "^7.8.1",
@@ -1551,6 +1556,16 @@
"node": ">=8"
}
},
"node_modules/@nestjs/axios": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@nestjs/axios/-/axios-3.0.3.tgz",
"integrity": "sha512-h6TCn3yJwD6OKqqqfmtRS5Zo4E46Ip2n+gK1sqwzNBC+qxQ9xpCu+ODVRFur6V3alHSCSBxb3nNtt73VEdluyA==",
"peerDependencies": {
"@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0",
"axios": "^1.3.1",
"rxjs": "^6.0.0 || ^7.0.0"
}
},
"node_modules/@nestjs/cli": {
"version": "10.4.5",
"resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-10.4.5.tgz",
@@ -1688,6 +1703,18 @@
}
}
},
"node_modules/@nestjs/jwt": {
"version": "10.2.0",
"resolved": "https://registry.npmjs.org/@nestjs/jwt/-/jwt-10.2.0.tgz",
"integrity": "sha512-x8cG90SURkEiLOehNaN2aRlotxT0KZESUliOPKKnjWiyJOcWurkF3w345WOX0P4MgFzUjGoZ1Sy0aZnxeihT0g==",
"dependencies": {
"@types/jsonwebtoken": "9.0.5",
"jsonwebtoken": "9.0.2"
},
"peerDependencies": {
"@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0"
}
},
"node_modules/@nestjs/platform-express": {
"version": "10.4.1",
"resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.4.1.tgz",
@@ -2049,6 +2076,14 @@
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
"dev": true
},
"node_modules/@types/jsonwebtoken": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.5.tgz",
"integrity": "sha512-VRLSGzik+Unrup6BsouBeHsf4d1hOEgYWTm/7Nmw1sXoN1+tRly/Gy/po3yeahnP4jfnQWWAhQAqcNfH7ngOkA==",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/methods": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz",
@@ -2065,7 +2100,6 @@
"version": "20.16.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.5.tgz",
"integrity": "sha512-VwYCweNo3ERajwy0IUlqqcyZ8/A7Zwa9ZP3MnENWcB11AejO+tLy3pu850goUW2FC/IJMdZUfKpX/yxL1gymCA==",
"devOptional": true,
"dependencies": {
"undici-types": "~6.19.2"
}
@@ -2137,6 +2171,11 @@
"@types/superagent": "^8.1.0"
}
},
"node_modules/@types/validator": {
"version": "13.12.1",
"resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.12.1.tgz",
"integrity": "sha512-w0URwf7BQb0rD/EuiG12KP0bailHKHP5YVviJG9zw3ykAokL0TuxU2TUqMB7EwZ59bDHYdeTIvjI5m0S7qHfOA=="
},
"node_modules/@types/yargs": {
"version": "17.0.33",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz",
@@ -2741,8 +2780,7 @@
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"dev": true
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"node_modules/aws-ssl-profiles": {
"version": "1.1.2",
@@ -2752,6 +2790,16 @@
"node": ">= 6.0.0"
}
},
"node_modules/axios": {
"version": "1.7.7",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz",
"integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/babel-jest": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz",
@@ -3065,6 +3113,11 @@
"ieee754": "^1.1.13"
}
},
"node_modules/buffer-equal-constant-time": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
},
"node_modules/buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
@@ -3229,6 +3282,21 @@
"integrity": "sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==",
"dev": true
},
"node_modules/class-transformer": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz",
"integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw=="
},
"node_modules/class-validator": {
"version": "0.14.1",
"resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.1.tgz",
"integrity": "sha512-2VEG9JICxIqTpoK1eMzZqaV+u/EiwEJkMGzTrZf6sU/fwsnOITVgYJ8yojSy6CaXtO9V0Cc6ZQZ8h8m4UBuLwQ==",
"dependencies": {
"@types/validator": "^13.11.8",
"libphonenumber-js": "^1.10.53",
"validator": "^13.9.0"
}
},
"node_modules/cli-cursor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
@@ -3422,7 +3490,6 @@
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"dev": true,
"dependencies": {
"delayed-stream": "~1.0.0"
},
@@ -3698,7 +3765,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"dev": true,
"engines": {
"node": ">=0.4.0"
}
@@ -3813,6 +3879,14 @@
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
},
"node_modules/ecdsa-sig-formatter": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
"dependencies": {
"safe-buffer": "^5.0.1"
}
},
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -4531,6 +4605,25 @@
"integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==",
"dev": true
},
"node_modules/follow-redirects": {
"version": "1.15.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/foreground-child": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz",
@@ -4600,7 +4693,6 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"dev": true,
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
@@ -6136,6 +6228,46 @@
"graceful-fs": "^4.1.6"
}
},
"node_modules/jsonwebtoken": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
"integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
"dependencies": {
"jws": "^3.2.2",
"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",
"semver": "^7.5.4"
},
"engines": {
"node": ">=12",
"npm": ">=6"
}
},
"node_modules/jwa": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
"integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
"dependencies": {
"buffer-equal-constant-time": "1.0.1",
"ecdsa-sig-formatter": "1.0.11",
"safe-buffer": "^5.0.1"
}
},
"node_modules/jws": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
"integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
"dependencies": {
"jwa": "^1.4.1",
"safe-buffer": "^5.0.1"
}
},
"node_modules/keyv": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@@ -6176,6 +6308,11 @@
"node": ">= 0.8.0"
}
},
"node_modules/libphonenumber-js": {
"version": "1.11.8",
"resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.11.8.tgz",
"integrity": "sha512-0fv/YKpJBAgXKy0kaS3fnqoUVN8901vUYAKIGD/MWZaDfhJt1nZjPL3ZzdZBt/G8G8Hw2J1xOIrXWdNHFHPAvg=="
},
"node_modules/lines-and-columns": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
@@ -6211,6 +6348,36 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"node_modules/lodash.includes": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="
},
"node_modules/lodash.isboolean": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="
},
"node_modules/lodash.isinteger": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
"integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="
},
"node_modules/lodash.isnumber": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
"integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="
},
"node_modules/lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="
},
"node_modules/lodash.isstring": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="
},
"node_modules/lodash.memoize": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
@@ -6223,6 +6390,11 @@
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
"dev": true
},
"node_modules/lodash.once": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
},
"node_modules/log-symbols": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
@@ -7098,6 +7270,11 @@
"node": ">= 0.10"
}
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
},
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -7529,7 +7706,6 @@
"version": "7.6.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
"dev": true,
"bin": {
"semver": "bin/semver.js"
},
@@ -8605,8 +8781,7 @@
"node_modules/undici-types": {
"version": "6.19.8",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
"devOptional": true
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="
},
"node_modules/universalify": {
"version": "2.0.1",
@@ -8709,6 +8884,14 @@
"node": ">=10.12.0"
}
},
"node_modules/validator": {
"version": "13.12.0",
"resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz",
"integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==",
"engines": {
"node": ">= 0.10"
}
},
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",

View File

@@ -20,11 +20,16 @@
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@nestjs/axios": "^3.0.3",
"@nestjs/common": "^10.0.0",
"@nestjs/config": "^3.2.3",
"@nestjs/core": "^10.0.0",
"@nestjs/jwt": "^10.2.0",
"@nestjs/platform-express": "^10.0.0",
"@nestjs/typeorm": "^10.0.2",
"axios": "^1.7.7",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"mysql2": "^3.11.2",
"reflect-metadata": "^0.2.0",
"rxjs": "^7.8.1",

View File

@@ -1,22 +0,0 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AppController } from './app.controller';
import { AppService } from './app.service';
describe('AppController', () => {
let appController: AppController;
beforeEach(async () => {
const app: TestingModule = await Test.createTestingModule({
controllers: [AppController],
providers: [AppService],
}).compile();
appController = app.get<AppController>(AppController);
});
describe('root', () => {
it('should return "Hello World!"', () => {
expect(appController.getHello()).toBe('Hello World!');
});
});
});

View File

@@ -1,5 +1,6 @@
import { Controller, Get } from '@nestjs/common';
import { Body, Controller, Get, Post } from '@nestjs/common';
import { AppService } from './app.service';
import { LoginDTO } from './model/dto/login.dto';
@Controller()
export class AppController {
@@ -9,4 +10,9 @@ export class AppController {
getHello(): any {
return this.appService.getHello();
}
@Post()
login(@Body() createUserDto: LoginDTO) {
return { success: createUserDto };
}
}

View File

@@ -1,8 +1,9 @@
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule } from '@nestjs/config';
import { DatabaseModule } from './shared/database/database.module';
import { AuthModule } from './modules/auth/auth.module';
@Module({
imports: [
@@ -10,28 +11,10 @@ import { TypeOrmModule } from '@nestjs/typeorm';
envFilePath: ['.env'],
isGlobal: true,
}),
TypeOrmModule.forRootAsync({
useFactory: () => ({
type: 'mysql',
host: process.env.DATABASE_HOST,
port: parseInt(process.env.DATABASE_PORT) || 3306,
username: process.env.MYSQL_USER,
password: process.env.MYSQL_PASSWORD,
database: process.env.MYSQL_DATABASE,
synchronize: true,
autoLoadEntities: true,
retryAttempts: 5,
retryDelay: 10000,
logging: ['error'],
logger: 'file',
}),
}),
DatabaseModule,
AuthModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {
constructor(private config: ConfigService) {
console.log(this.config.get('MYSQL_USER'))
}
}
export class AppModule {}

View File

@@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
constructor() {}
getHello(): any {
return { success: true, date: new Date() };
}

View File

@@ -1,8 +1,11 @@
import { NestFactory } from '@nestjs/core';
import { NestFactory, Reflector } from '@nestjs/core';
import { AppModule } from './app.module';
import { ClassSerializerInterceptor, ValidationPipe } from '@nestjs/common';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalInterceptors(new ClassSerializerInterceptor(app.get(Reflector)));
app.useGlobalPipes(new ValidationPipe());
await app.listen(4000);
}
bootstrap();

View File

@@ -0,0 +1,6 @@
import { IsNotEmpty } from 'class-validator';
export class AuthCodeDto {
@IsNotEmpty()
code: string;
}

View File

@@ -0,0 +1,9 @@
import { IsEmail, IsNotEmpty } from 'class-validator';
export class CreateUserDto {
@IsEmail()
username: string;
@IsNotEmpty()
externalId: string;
}

View File

@@ -0,0 +1,2 @@
export * from './login.dto';
export * from './auth-code.dto';

View File

@@ -0,0 +1,9 @@
import { IsEmail, IsNotEmpty } from 'class-validator';
export class LoginDTO {
@IsEmail()
username: string;
@IsNotEmpty()
password: string;
}

View File

@@ -0,0 +1,2 @@
export * from './sso.user.entity';
export * from './user.entity';

View File

@@ -0,0 +1,18 @@
import { Column, Entity, JoinColumn, OneToOne, PrimaryColumn } from 'typeorm';
import { User } from './user.entity';
@Entity()
export class SSOUser {
@PrimaryColumn({ type: 'uuid', unique: true })
externalId: string;
@OneToOne(() => User, (user) => user.external)
@JoinColumn()
user: User;
@Column({ nullable: true, type: 'text' })
accessToken: string;
@Column({ nullable: true, type: 'text' })
refreshToken: string;
}

View File

@@ -0,0 +1,44 @@
import { Exclude } from 'class-transformer';
import {
Column,
CreateDateColumn,
Entity,
OneToOne,
PrimaryGeneratedColumn,
} from 'typeorm';
import { IUser } from '../interface';
import { SSOUser } from './sso.user.entity';
import { IsEmail } from 'class-validator';
@Entity()
export class User implements IUser {
@PrimaryGeneratedColumn('uuid')
id: string;
@IsEmail()
@Column({ unique: true })
username: string;
@Column({ name: 'first_name', default: '' })
firstName: string;
@Column({ name: 'last_name', default: '' })
lastName: string;
@CreateDateColumn({ name: 'created_at' })
createdAt: Date;
@Column({ default: null })
lastLogin: Date;
@Exclude()
@OneToOne(() => SSOUser, (sso) => sso.user, { eager: true, cascade: true })
external: SSOUser;
@Exclude()
@Column({ default: true })
isActive: boolean;
accessToken?: string;
refreshToken?: string;
}

View File

@@ -0,0 +1,10 @@
export interface IExternalAccessPayload {
username: string;
id: string;
firstName: string;
lastName: string;
iss: string;
aud: string;
iat: number;
exp: number;
}

View File

@@ -0,0 +1,3 @@
export * from './user.interface';
export * from './external-access-token.payload.interface';
export * from './payload.interface';

View File

@@ -0,0 +1,5 @@
export interface IPayload {
id: string;
username: string;
type: 'access' | 'refresh';
}

View File

@@ -0,0 +1,10 @@
export interface IUser {
id: string;
username: string;
firstName: string;
lastName: string;
accessToken?: string;
refreshToken?: string;
}

View File

@@ -0,0 +1,2 @@
export * from './user.repository';
export * from './ssouser.repository';

View File

@@ -0,0 +1,18 @@
import { Injectable } from '@nestjs/common';
import { Repository, DataSource } from 'typeorm';
import { SSOUser } from '../entitites';
@Injectable()
export class SsoUserRepository extends Repository<SSOUser> {
constructor(dataSource: DataSource) {
super(SSOUser, dataSource.createEntityManager());
}
findOneByUserId(id: string): Promise<SSOUser> {
return this.findOne({ where: { user: { id: id } } });
}
findByExternalId(id: string): Promise<SSOUser> {
return this.findOne({ where: { externalId: id } });
}
}

View File

@@ -0,0 +1,61 @@
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { Repository, DataSource } from 'typeorm';
import { User } from '../entitites';
import { CreateUserDto } from '../dto/create-user.dto';
import { SsoUserRepository } from './ssouser.repository';
@Injectable()
export class UserRepository extends Repository<User> {
constructor(
dataSource: DataSource,
private ssoRepo: SsoUserRepository,
) {
super(User, dataSource.createEntityManager());
}
findByUsername(username: string): Promise<User> {
return this.findOne({ where: { username }, relations: ['external'] });
}
findById(id: string): Promise<User> {
return this.findOne({ where: { id }, relations: ['external'] });
}
async createUser(createUserDto: CreateUserDto): Promise<User> {
if (
!(await this.checkIfCanInserted(
createUserDto.username,
createUserDto.externalId,
))
) {
throw new HttpException('user_exists', HttpStatus.UNPROCESSABLE_ENTITY);
}
try {
const created = this.create(createUserDto);
const sso = this.ssoRepo.create({
user: created,
externalId: createUserDto.externalId,
});
created.external = sso;
const user = await this.save(created);
sso.user = user;
this.ssoRepo.save(sso);
return user;
} catch {
throw new HttpException(
'not_successfull',
HttpStatus.UNPROCESSABLE_ENTITY,
);
}
}
private async checkIfCanInserted(
username: string,
externalId: string,
): Promise<boolean> {
const user = await this.findOneBy({ username });
const sso = await this.ssoRepo.findByExternalId(externalId);
return user == null && sso == null;
}
}

View File

@@ -0,0 +1,26 @@
import {
Body,
Controller,
HttpException,
HttpStatus,
Post,
} from '@nestjs/common';
import { AuthService } from './auth.service';
import { AuthCodeDto } from 'src/model/dto';
import { User } from 'src/model/entitites';
@Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}
@Post('auth-code')
async registerOrLoginWithAuthCode(
@Body() authDto: AuthCodeDto,
): Promise<User> {
const user = await this.authService.registerOrLoginWithAuthCode(authDto);
if (user == null) {
throw new HttpException('forbidden', HttpStatus.FORBIDDEN);
}
return user;
}
}

View File

@@ -0,0 +1,26 @@
import { Module } from '@nestjs/common';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { DatabaseModule } from 'src/shared/database/database.module';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { HttpModule } from '@nestjs/axios';
import { JwtModule } from '@nestjs/jwt';
@Module({
controllers: [AuthController],
providers: [AuthService],
imports: [
DatabaseModule,
ConfigModule,
HttpModule,
JwtModule.registerAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: async (config: ConfigService) => ({
secret: config.get('JWT_SECRET'),
signOptions: { expiresIn: config.get('JWT_EXPIRES_IN') },
}),
}),
],
})
export class AuthModule {}

View File

@@ -0,0 +1,106 @@
import { Injectable } from '@nestjs/common';
import { AuthCodeDto } from 'src/model/dto';
import { CreateUserDto } from 'src/model/dto/create-user.dto';
import { UserRepository } from 'src/model/repositories';
import { HttpService } from '@nestjs/axios';
import { ConfigService } from '@nestjs/config';
import { JwtService } from '@nestjs/jwt';
import { IExternalAccessPayload, IPayload } from 'src/model/interface';
import { User } from 'src/model/entitites';
@Injectable()
export class AuthService {
constructor(
private userRepo: UserRepository,
private readonly http: HttpService,
private configService: ConfigService,
private jwt: JwtService,
) {}
register(register: CreateUserDto) {
return this.userRepo.createUser(register);
}
async registerOrLoginWithAuthCode(auth: AuthCodeDto): Promise<User> {
console.log(auth);
const body = this.createAuthCodeFormData(auth.code, 'authorization_code');
const url = this.configService.get('SSO_TOKEN_URL');
return new Promise<User>((resolve) => {
this.http.post(url, body).subscribe({
next: async (response) => {
const user = await this.saveExternalTokens(response.data as any);
this.generateTokens(user);
resolve(user);
},
error: () => {
resolve(null);
},
});
});
}
private async saveExternalTokens({
access_token,
refresh_token,
}: {
access_token: string;
refresh_token: string;
}): Promise<User> {
console.log(access_token, refresh_token);
const payload: IExternalAccessPayload = this.jwt.decode(access_token);
return new Promise<User>(async (resolve) => {
let user = await this.userRepo.findByUsername(payload.username);
if (!user) {
user = await this.userRepo.createUser({
username: payload.username,
externalId: payload.id,
});
}
user.firstName = payload.firstName;
user.lastName = payload.lastName;
user.external.accessToken = access_token;
user.external.refreshToken = refresh_token;
await this.userRepo.save(user);
resolve(user);
});
}
private generateTokens(user: User) {
const payload: IPayload = {
username: user.username,
id: user.id,
type: 'access',
};
const token = this.jwt.sign(payload);
user.accessToken = token;
const rPay: IPayload = {
username: user.username,
id: user.id,
type: 'refresh',
};
user.refreshToken = this.jwt.sign(rPay);
}
private createAuthCodeFormData(
code: string,
grant_type = 'authorization_code',
): FormData {
const bodyFormData = new FormData();
bodyFormData.append('client_id', this.configService.get('SSO_CLIENT_ID'));
bodyFormData.append(
'client_secret',
this.configService.get('SSO_CLIENT_SECRET'),
);
bodyFormData.append('code', code);
bodyFormData.append('grant_type', grant_type);
bodyFormData.append(
'redirect_uri',
this.configService.get('SSO_REDIRECT_URI'),
);
return bodyFormData;
}
}

View File

@@ -0,0 +1,32 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { SSOUser, User } from 'src/model/entitites';
import { SsoUserRepository, UserRepository } from 'src/model/repositories';
const ENTITIES = [User, SSOUser];
const REPOSITORIES = [UserRepository, SsoUserRepository];
@Module({
imports: [
TypeOrmModule.forRootAsync({
useFactory: () => ({
type: 'mysql',
host: process.env.DATABASE_HOST,
port: parseInt(process.env.DATABASE_PORT) || 3306,
username: process.env.MYSQL_USER,
password: process.env.MYSQL_PASSWORD,
database: process.env.MYSQL_DATABASE,
synchronize: true,
autoLoadEntities: true,
retryAttempts: 5,
retryDelay: 10000,
logging: ['error'],
logger: 'file',
entities: [...ENTITIES],
}),
}),
],
providers: [...REPOSITORIES],
exports: [TypeOrmModule, ...REPOSITORIES],
})
export class DatabaseModule {}