diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index e48ae801b..202449e05 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -2,10 +2,6 @@ -## 작업 내용 - - - ## 참고 사항 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index d4998967f..ad6b1f28d 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,24 +1,33 @@ name: Lint on: - push: - branches: - - develop - - main - pull_request: - branches: - - develop - - main + push: + branches: + - develop + - main + pull_request: + branches: + - develop + - main jobs: build: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v2 + - name: check current path + run : pwd + - name: list directories + run : ls + - name: Change to directory + run: cd ./srcs/nest/src + - name: Set up Node.js uses: actions/setup-node@v2 with: - node-version: '14' + node-version: '18' - name: Install dependencies run: npm install + working-directory: /home/runner/work/Backend/Backend/srcs/nest/src - name: Run ESLint run: npm run lint + working-directory: /home/runner/work/Backend/Backend/srcs/nest/src diff --git a/srcs/nest/package-lock.json b/srcs/nest/package-lock.json index 25d339753..51794d1ba 100644 --- a/srcs/nest/package-lock.json +++ b/srcs/nest/package-lock.json @@ -31,6 +31,7 @@ "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "cookie-parser": "^1.4.6", + "express-http-proxy": "^2.0.0", "g": "^2.0.1", "handlebars": "^4.7.8", "jsonwebtoken": "^9.0.2", @@ -43,6 +44,7 @@ "pg": "^8.11.3", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1", + "stringify": "^5.2.0", "swagger-ui-express": "^5.0.0", "typeorm": "^0.3.17" }, @@ -2413,6 +2415,14 @@ "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.1.tgz", "integrity": "sha512-/K3ds8TRAfBvi5vfjuz8y6+GiAYBZ0x4tXv1Av6CWBWn0IlADc+ZX9pMq7oU0fNQPnBwIZl3rmeLp6SBApbxSQ==" }, + "node_modules/@types/http-proxy": { + "version": "1.17.11", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.11.tgz", + "integrity": "sha512-HC8G7c1WmaF2ekqpnFq626xd3Zz0uvaqFmBJNRZCGEZCXkvSdJoNFn/8Ygbd9fKNQj8UzLdCETaI0UWPAjK7IA==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", @@ -3566,6 +3576,15 @@ "node": ">=8" } }, + "node_modules/browserify-transform-tools": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/browserify-transform-tools/-/browserify-transform-tools-1.7.0.tgz", + "integrity": "sha512-D4/vMGx4ILHI/+Qokdo2x7cxPJqy7uXt0zugOBbDvnCcrQL9/WrgK71GJgrNHF/L4XLErA4cMGlTVmc2sICRnA==", + "dependencies": { + "falafel": "^2.0.0", + "through": "^2.3.7" + } + }, "node_modules/browserslist": { "version": "4.21.10", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz", @@ -5170,6 +5189,11 @@ "integrity": "sha512-vZK7T0N2CBmBOixhmjdqx2gWVbFZ4DXZ/NyRMZVlJXPa7CyFS+/a4QQsDGDQy9ZfEzxFuNEsMLeQJnKP2p5/JA==", "dev": true }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" + }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -5534,6 +5558,11 @@ "node": ">= 0.6" } }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -5633,6 +5662,27 @@ "node": ">= 0.10.0" } }, + "node_modules/express-http-proxy": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/express-http-proxy/-/express-http-proxy-2.0.0.tgz", + "integrity": "sha512-TXxcPFTWVUMSEmyM6iX2sT/JtmqhqngTq29P+eXTVFdtxZrTmM8THUYK59rUXiln0FfPGvxEpGRnVrgvHksXDw==", + "dependencies": { + "debug": "^3.0.1", + "es6-promise": "^4.1.1", + "raw-body": "^2.3.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/express-http-proxy/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "^2.1.1" + } + }, "node_modules/express/node_modules/body-parser": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", @@ -5718,6 +5768,34 @@ "style-data": "^2.0.1" } }, + "node_modules/falafel": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/falafel/-/falafel-2.2.5.tgz", + "integrity": "sha512-HuC1qF9iTnHDnML9YZAdCDQwT0yKl/U55K4XSUXqGAA2GLoafFgWRqdAbhWJxXaYD4pyoVxAJ8wH670jMpI9DQ==", + "dependencies": { + "acorn": "^7.1.1", + "isarray": "^2.0.1" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/falafel/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/falafel/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -6405,6 +6483,11 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, + "node_modules/graceful-readlink": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", + "integrity": "sha512-8tLu60LgxF6XpdbK8OW3FA+IfTNBn1ZHGHKF4KQbEeSkajYw5PlYJcKluntgegDPTg8UkHjpet1T82vk6TQ68w==" + }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", @@ -6619,6 +6702,19 @@ "node": ">= 0.8" } }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/http-proxy-agent": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", @@ -6632,6 +6728,29 @@ "node": ">= 6" } }, + "node_modules/http-proxy-middleware": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", + "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, "node_modules/https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -6963,6 +7082,17 @@ "node": ">=8" } }, + "node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-promise": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", @@ -8373,7 +8503,6 @@ "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, "dependencies": { "braces": "^3.0.2", "picomatch": "^2.3.1" @@ -9012,6 +9141,17 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/ncname": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ncname/-/ncname-1.0.0.tgz", + "integrity": "sha512-VLkyYr2kmPzVzrmkER9i13RJIdGbjNr855gfh2VvuboO1eYnb9k+nFS+JygfSVgtbo/HMpLz5pEYLK4Xjy7XGg==", + "dependencies": { + "xml-char-classes": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -10399,6 +10539,11 @@ "node": ">=0.10.0" } }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, "node_modules/resolve": { "version": "1.22.4", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.4.tgz", @@ -11091,6 +11236,97 @@ "node": ">=8" } }, + "node_modules/stringify": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/stringify/-/stringify-5.2.0.tgz", + "integrity": "sha512-n0JeEVfYUtukDmUQ7gsO2aTFUa+pI8c+TChB6q8w9X5VBElFOfNbemhPlSrvTXhtAhCLMKEZp9bu7ADeXDtV0w==", + "dependencies": { + "browserify-transform-tools": "^1.5.3", + "html-minifier": "3.5.2" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/stringify/node_modules/clean-css": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.1.11.tgz", + "integrity": "sha512-a3ZEe58u+LizPdSCHM0jIGeKu1hN+oqqXXc1i70mnV0x2Ox3/ho1pE6Y8HD6yhDts5lEQs028H9kutlihP77uQ==", + "dependencies": { + "source-map": "0.5.x" + }, + "engines": { + "node": ">= 4.0" + } + }, + "node_modules/stringify/node_modules/commander": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", + "integrity": "sha512-bmkUukX8wAOjHdN26xj5c4ctEV22TQ7dQYhSmuckKhToXrkUn0iIaolHdIxYYqD55nhpSPA9zPQ1yP57GdXP2A==", + "dependencies": { + "graceful-readlink": ">= 1.0.0" + }, + "engines": { + "node": ">= 0.6.x" + } + }, + "node_modules/stringify/node_modules/he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha512-z/GDPjlRMNOa2XJiB4em8wJpuuBfrFOlYKTZxtpkdr1uPdibHI8rYA3MY0KDObpVyaes0e/aunid/t88ZI2EKA==", + "bin": { + "he": "bin/he" + } + }, + "node_modules/stringify/node_modules/html-minifier": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.2.tgz", + "integrity": "sha512-CpXODZQ75jOxqF5CR0vqPKV9LuHw96ijVRbEsSPTPFs4gKd5uuMNEUsAvRgz9OSXS/D4fItq0X8362oXMyjZPw==", + "dependencies": { + "camel-case": "3.0.x", + "clean-css": "4.1.x", + "commander": "2.9.x", + "he": "1.1.x", + "ncname": "1.0.x", + "param-case": "2.1.x", + "relateurl": "0.2.x", + "uglify-js": "3.0.x" + }, + "bin": { + "html-minifier": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/stringify/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stringify/node_modules/uglify-js": { + "version": "3.0.28", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.0.28.tgz", + "integrity": "sha512-0h/qGay016GG2lVav3Kz174F3T2Vjlz2v6HCt+WDQpoXfco0hWwF5gHK9yh88mUYvIC+N7Z8NT8WpjSp1yoqGA==", + "dependencies": { + "commander": "~2.11.0", + "source-map": "~0.5.1" + }, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/stringify/node_modules/uglify-js/node_modules/commander": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", + "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==" + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -11465,8 +11701,7 @@ "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" }, "node_modules/titleize": { "version": "3.0.0", diff --git a/srcs/nest/package.json b/srcs/nest/package.json index eca181b6c..bf7349e9c 100644 --- a/srcs/nest/package.json +++ b/srcs/nest/package.json @@ -40,6 +40,7 @@ "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "cookie-parser": "^1.4.6", + "express-http-proxy": "^2.0.0", "g": "^2.0.1", "handlebars": "^4.7.8", "jsonwebtoken": "^9.0.2", @@ -52,6 +53,7 @@ "pg": "^8.11.3", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1", + "stringify": "^5.2.0", "swagger-ui-express": "^5.0.0", "typeorm": "^0.3.17" }, diff --git a/srcs/nest/src/auth/auth.module.ts b/srcs/nest/src/auth/auth.module.ts index a9a469025..e4042a30b 100644 --- a/srcs/nest/src/auth/auth.module.ts +++ b/srcs/nest/src/auth/auth.module.ts @@ -6,7 +6,7 @@ import { AuthController } from './auth.controller'; import { ConfigModule, ConfigService } from '@nestjs/config'; import { PassportModule } from '@nestjs/passport'; import { FortytwoAuthGuard } from './fortytwo.guard'; -import { FortytwoStrategy } from './fortytwo.strategy'; +import { FortytwoStrategy } from './fortyTwo.strategy'; import { Auth42Dto } from './dto/auth42.dto'; import { UserModule } from 'src/user/user.module'; import { HttpModule } from '@nestjs/axios'; diff --git a/srcs/nest/src/auth/auth.service.ts b/srcs/nest/src/auth/auth.service.ts index c62bd36fe..3cf98e478 100644 --- a/srcs/nest/src/auth/auth.service.ts +++ b/srcs/nest/src/auth/auth.service.ts @@ -55,6 +55,7 @@ export class AuthService { return undefined; } + async regenerateJwt(request) { //헤더 에서 jwt, refreshToken 추출 후 DB에 저장해둔 것과 비교하여 검증 //jwt payload 에서 id추출. @@ -64,7 +65,11 @@ export class AuthService { throw new UnauthorizedException('no token '); } let payload; + let payload; try { + payload = await this.jwtService.verifyAsync(token, { + secret: process.env.JWT_SECRET_KEY, + }); payload = await this.jwtService.verifyAsync(token, { secret: process.env.JWT_SECRET_KEY, }); diff --git a/srcs/nest/src/auth/jwt2fa.guard.ts b/srcs/nest/src/auth/jwt2fa.guard.ts index ed724077d..1b7c34c79 100644 --- a/srcs/nest/src/auth/jwt2fa.guard.ts +++ b/srcs/nest/src/auth/jwt2fa.guard.ts @@ -14,6 +14,8 @@ export class Jwt2faGuard implements CanActivate { if (!token) { console.log('2faJwt: no token'); throw new UnauthorizedException('2faJwt: no token'); + console.log('2faJwt: no token'); + throw new UnauthorizedException('2faJwt: no token'); } try { const payload = await this.jwtService.verifyAsync(token, { @@ -27,6 +29,7 @@ export class Jwt2faGuard implements CanActivate { throw new UnauthorizedException("2faJwt: can't verify token"); } console.log('2faJwt guard okay'); + console.log('2faJwt guard okay'); return true; } diff --git a/srcs/nest/src/auth/jwtEnroll.guard.ts b/srcs/nest/src/auth/jwtEnroll.guard.ts new file mode 100644 index 000000000..12af51a11 --- /dev/null +++ b/srcs/nest/src/auth/jwtEnroll.guard.ts @@ -0,0 +1,39 @@ +import { + Injectable, + CanActivate, + UnauthorizedException, + ExecutionContext, +} from '@nestjs/common'; +import { JwtService } from '@nestjs/jwt'; +import { Request } from 'express'; + +@Injectable() +export class JwtEnrollGuard implements CanActivate { + constructor(private jwtService: JwtService) {} + + async canActivate(context: ExecutionContext): Promise { + const request = context.switchToHttp().getRequest(); + const token = this.extractTokenFromHeader(request); + if (!token) { + console.log('enrollJwt: no token'); + throw new UnauthorizedException('enrollJwt: no token'); + } + try { + const payload = await this.jwtService.verifyAsync(token, { + secret: process.env.JWT_ENROLL_SECRET, + }); + + request['authDto'] = [payload]; + } catch (error) { + console.log('enrollJwt: token not right'); + throw new UnauthorizedException("enrollJwt: can't verify token"); + } + console.log('enrollJwt guard okay'); + return true; + } + + private extractTokenFromHeader(request: Request): string | undefined { + const [type, token] = request.headers.authorization?.split(' ') ?? []; + return type === 'Bearer' ? token : undefined; + } +} diff --git a/srcs/nest/src/main.ts b/srcs/nest/src/main.ts index f88c86f00..79c95ffe4 100644 --- a/srcs/nest/src/main.ts +++ b/srcs/nest/src/main.ts @@ -21,4 +21,4 @@ async function bootstrap() { await app.listen(3000); } -bootstrap(); +bootstrap(); \ No newline at end of file diff --git a/srcs/nest/src/user/dto/userProfile.dto.ts b/srcs/nest/src/user/dto/userProfile.dto.ts index 2a47c18c1..438bc4f9e 100644 --- a/srcs/nest/src/user/dto/userProfile.dto.ts +++ b/srcs/nest/src/user/dto/userProfile.dto.ts @@ -1,5 +1,6 @@ import { IsNotEmpty, IsString, IsNumber } from 'class-validator'; +//TODO 프로필에 보여줄 내용 정해야함 export class UserProfileDto { @IsNotEmpty() @IsNumber() diff --git a/srcs/nest/src/user/user.controller.ts b/srcs/nest/src/user/user.controller.ts index dd4641cb5..3f0b07e33 100644 --- a/srcs/nest/src/user/user.controller.ts +++ b/srcs/nest/src/user/user.controller.ts @@ -92,7 +92,7 @@ export class UserController { const { image, mimeType } = await this.userService.getUserProfileImage(slackId); if (image === undefined || mimeType === undefined) return; res.setHeader('Content-Type', mimeType); // 이미지의 MIME 타입 설정 - return res.send(image); // 이미지 파일을 클라이언트로 전송 + res.send(image); // 이미지 파일을 클라이언트로 전송 } catch (error) { //ERROR HANDLE this.logger.error(`getProfileImage : ${error.name}`); diff --git a/srcs/nest/src/user/user.module.ts b/srcs/nest/src/user/user.module.ts index 41af1dabf..2cb81c677 100644 --- a/srcs/nest/src/user/user.module.ts +++ b/srcs/nest/src/user/user.module.ts @@ -4,7 +4,7 @@ import { UserController } from './user.controller'; import { UserService } from './user.service'; import { User } from './user.entity'; import { UserRepository } from './user.repository'; -import { JwtAuthGuard } from 'src/auth/jwtAuth.guard'; +import { JwtEnrollGuard } from 'src/auth/jwtEnroll.guard'; import { JwtModule } from '@nestjs/jwt'; import { MulterModule } from '@nestjs/platform-express'; import { diskStorage } from 'multer'; diff --git a/srcs/nest/src/user/users.service.ts b/srcs/nest/src/user/users.service.ts new file mode 100644 index 000000000..0d15cd8fd --- /dev/null +++ b/srcs/nest/src/user/users.service.ts @@ -0,0 +1,58 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository, Like } from 'typeorm'; +import { Users } from './users.entity'; + +@Injectable() +export class UsersService { + constructor( + @InjectRepository(Users) + private usersRepository: Repository, + ) {} + + async findAll(): Promise { + return this.usersRepository.find(); + } + + async addOne(user: Users): Promise { + const newUser = this.usersRepository.create(user); + return this.usersRepository.save(newUser); + } + + async searchOne(name: string): Promise { + return this.usersRepository.findOne({ + where: { + name: name, + } + }); + } + + async getUserListByFistSlackId(slackId: string): Promise { + const found = this.usersRepository.find({ + where: { + slackId: Like(`${slackId}%`), + }, + order: { + name: 'ASC', // Ascending order (alphabetically) + }, + }); + + if(!found){ + return new NotFoundException(); + } + + return found; + } + + async deleteOne(id: number): Promise { + const user = await this.usersRepository.findOne({ + where: { + id: id, + } + }); + if (user) { + await this.usersRepository.remove(user); + } + } + +} diff --git a/srcs/next/.eslintrc.json b/srcs/next/.eslintrc.json new file mode 100644 index 000000000..0bb8ddf26 --- /dev/null +++ b/srcs/next/.eslintrc.json @@ -0,0 +1,22 @@ +module.exports = { + parser: '@typescript-eslint/parser', + parserOptions: { + project: 'tsconfig.json', + tsconfigRootDir: __dirname, + sourceType: 'module', + }, + plugins: ['@typescript-eslint/eslint-plugin'], + extends: ['plugin:@typescript-eslint/recommended', 'prettier'], + root: true, + env: { + node: true, + jest: true, + }, + ignorePatterns: ['.eslintrc.js'], + rules: { + '@typescript-eslint/interface-name-prefix': 'off', + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-explicit-any': 'off', + }, +}; diff --git a/srcs/next/next.config.js b/srcs/next/next.config.js index a6bf133d5..f506fdb18 100644 --- a/srcs/next/next.config.js +++ b/srcs/next/next.config.js @@ -4,3 +4,4 @@ const nextConfig = { } module.exports = nextConfig + \ No newline at end of file diff --git a/srcs/next/src/app/Cookie.tsx b/srcs/next/src/app/Cookie.tsx new file mode 100644 index 000000000..9d9cda382 --- /dev/null +++ b/srcs/next/src/app/Cookie.tsx @@ -0,0 +1,11 @@ +import {Cookies} from 'react-cookie'; + +const cookies = new Cookies(); + +export const setCookie = (name: string, value: string, options?: any) => { + return cookies.set(name, value, {...options}); +} + +export const getCookie = (name: string) => { + return cookies.get(name); +} \ No newline at end of file diff --git a/srcs/next/src/app/main/page.tsx b/srcs/next/src/app/main/page.tsx new file mode 100644 index 000000000..bc58433dd --- /dev/null +++ b/srcs/next/src/app/main/page.tsx @@ -0,0 +1,13 @@ +'use client' + +import React, { useState } from "react"; + +const MainHome: React.FC = () => { + return ( + + HOME! + + ); +}; + +export default MainHome; \ No newline at end of file diff --git a/srcs/next/src/app/page.tsx b/srcs/next/src/app/page.tsx index 343a9c78b..40f4696af 100644 --- a/srcs/next/src/app/page.tsx +++ b/srcs/next/src/app/page.tsx @@ -98,3 +98,5 @@ const Home = () => { }; export default Home; + +export default Home; \ No newline at end of file diff --git a/srcs/next/src/app/redux/mySlice.tsx b/srcs/next/src/app/redux/mySlice.tsx new file mode 100644 index 000000000..ce8fa5912 --- /dev/null +++ b/srcs/next/src/app/redux/mySlice.tsx @@ -0,0 +1,34 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; + +interface MyState { + value: number; +} + +const initialState: MyState = { + value: 0, +}; + +const mySlice = createSlice({ + name: 'my', + initialState, + reducers: { + up: (state, action: PayloadAction) => { + state.value += action.payload; + }, + }, +}); + +export default mySlice; +export const { up } = mySlice.actions; + +/* + +1. PayloadAction을 가져와서 up reducer의 action 매개변수 타입을 PayloadAction로 명시합니다. 이는 createSlice에서 생성된 액션의 타입을 정확하게 지정하는 데 도움이 됩니다. + +2. initialState를 명시적으로 MyState 타입으로 지정합니다. 이렇게 하면 초기 상태의 형식이 제대로 유지됩니다. + +3. export default mySlice.reducer;는 슬라이스의 리듀서를 내보냅니다. + +4. export const { up } = mySlice.actions;는 up 액션을 내보냅니다. + +*/ \ No newline at end of file diff --git a/srcs/next/src/app/redux/tokenSlice.tsx b/srcs/next/src/app/redux/tokenSlice.tsx new file mode 100644 index 000000000..548fc3144 --- /dev/null +++ b/srcs/next/src/app/redux/tokenSlice.tsx @@ -0,0 +1,37 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; + +interface tokenState { + authenticated : boolean; + accessToken : string | null; + expireTime : number | null; +} + +const initialState: tokenState = { + authenticated : false, + accessToken : null, + expireTime : null, +}; + +const tokenSlice = createSlice({ + name: 'authToken', + initialState, + reducers: { + // save token + setToken: (state, action: PayloadAction) => { + state.authenticated = true; + state.accessToken = action.payload; + }, + // save expire time + setExpireTime: (state, action: PayloadAction) => { + state.expireTime = action.payload; + }, + }, +}); + +// check authenticated +export const isAuthenticated = (state: tokenState) => { + return state.expireTime !== null && state.expireTime > Date.now(); +}; + +export default tokenSlice; +export const {setToken, setExpireTime} = tokenSlice.actions; diff --git a/srcs/next/src/app/register/CheckDuplicatebutton.tsx b/srcs/next/src/app/register/CheckDuplicatebutton.tsx new file mode 100644 index 000000000..974f31f68 --- /dev/null +++ b/srcs/next/src/app/register/CheckDuplicatebutton.tsx @@ -0,0 +1,56 @@ +'use client' + +import { Provider } from "react-redux"; +import store from "../redux/store"; +import { setName, setImageUrl } from "../redux/userSlice" +import { useSelector, useDispatch } from "react-redux"; +import axios from "axios"; +import { userState } from "../redux/userSlice"; + +const CheckDuplicatebuttonContent = () => { + const dispatch = useDispatch(); + const userName = useSelector((state: userState) => state.name); + + // console.log("state.username: " + userName); + const checkDuplicate = async() => { + //const inputName = + try{ + //alert("inputname: " + userName); + const response = await axios.get("http://10.14.9.4:3000/users/username/testUser"); + alert('asdas'); + dispatch(setName("testUser")); + console.log("correct"); + } catch(error) { + console.log("dup"); + } + {userName ? ( + + 사용 가능한 닉네임입니다! + ) : ( + + 중복된 닉네임입니다! + ) + } + } + + const myOnChange = () => { + dispatch(setName(userName)); + } + + return ( + + + Check Duplicate + + ) +}; + +const CheckDuplicatebutton = () => { + return ( + + + + ) +}; + +export default CheckDuplicatebutton; \ No newline at end of file diff --git a/srcs/next/src/app/register/RegisterButton.tsx b/srcs/next/src/app/register/RegisterButton.tsx new file mode 100644 index 000000000..49c4d8e3d --- /dev/null +++ b/srcs/next/src/app/register/RegisterButton.tsx @@ -0,0 +1,62 @@ +'use client' + +import React from 'react'; +import axios, { AxiosResponse } from 'axios'; +import { CookiesProvider } from 'react-cookie'; +import { getCookie } from '../Cookie'; +import { Provider } from 'react-redux'; +import { useSelector } from 'react-redux'; +import { userState } from '../redux/userSlice'; +import store from '../redux/store'; + +const RegisterButtonContent = () => { + const name = useSelector((state: userState) => state.name); + const imageUrl = useSelector((state: userState) => state.imageUrl); + const register = async () => { + const enrollToken = getCookie("enroll_token"); + console.log(enrollToken); + if (enrollToken != undefined) + { + const postRes :AxiosResponse = await axios.post("http://10.19.9.4:3000/signup", { + headers: { + Authorization: `Bearer ${enrollToken}` + }, + }); + const nameRes :AxiosResponse = await axios.patch("http://10.19.9.4:3000/signup/username", { + headers: { + Authorization: `Bearer ${enrollToken}` + }, + data: { + username: name, + } + }); + const imageRes :AxiosResponse = await axios.patch("http://10.19.9.4:3000/signup/profileImage", { + headers: { + Authorization: `Bearer ${enrollToken}` + }, + data: { + profileImage: imageUrl, + } + }); + } + } + +// @Post('/signup') // DB ㅅ생ㅅ 크토크만 +// @Patch('/signup/username') // ㅌㅌㅌ큰 + username +// @Patch('/signup/profileImage') / 크토크 + return ( + Register + ) +}; + +const RegisterButton = () => { + return ( + + + + + + ) +}; + +export default RegisterButton; \ No newline at end of file diff --git a/srcs/next/src/app/register/page.tsx b/srcs/next/src/app/register/page.tsx new file mode 100644 index 000000000..5105c42c3 --- /dev/null +++ b/srcs/next/src/app/register/page.tsx @@ -0,0 +1,35 @@ +'use client' + +import React, { useState } from "react"; +import RegisterButton from "./RegisterButton"; +import CheckDuplicatebutton from "./CheckDuplicatebutton"; + +const RegisterHome: React.FC = () => { + const handleImageUpload = (event: React.ChangeEvent) => { + // 이미지 선택 여부를 체크 + }; + + return ( + + Register Page!! + + + 이미지 업로드 (500x500): + + + + + + + 닉네임: + + + + + + + + ); +}; + +export default RegisterHome; \ No newline at end of file