diff --git a/backend-api/package-lock.json b/backend-api/package-lock.json index 80092a3..72bdf01 100644 --- a/backend-api/package-lock.json +++ b/backend-api/package-lock.json @@ -2932,15 +2932,27 @@ } }, "@nestjs/swagger": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-6.1.2.tgz", - "integrity": "sha512-RU1DeTDyuN/lRXKFWaf7I9LYF34/ale3IIGeY3romAcXL/N9W0+50Ek3ou+Ajd5FqpLqzt7saYhnaQegVuU4UQ==", + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-6.1.4.tgz", + "integrity": "sha512-kE8VjR+NaoKqxg8XqM/YYfALScPh4AcoR8Wywga8/OxHsTHY+MKxqvTpWp7IhCUWSA6xT8nQUpcC9Rt7C+r7Hw==", "requires": { - "@nestjs/mapped-types": "1.1.0", + "@nestjs/mapped-types": "1.2.0", "js-yaml": "4.1.0", "lodash": "4.17.21", "path-to-regexp": "3.2.0", - "swagger-ui-dist": "4.14.0" + "swagger-ui-dist": "4.15.5" + }, + "dependencies": { + "@nestjs/mapped-types": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-1.2.0.tgz", + "integrity": "sha512-NTFwPZkQWsArQH8QSyFWGZvJ08gR+R4TofglqZoihn/vU+ktHEJjMqsIsADwb7XD97DhiD+TVv5ac+jG33BHrg==" + }, + "swagger-ui-dist": { + "version": "4.15.5", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-4.15.5.tgz", + "integrity": "sha512-V3eIa28lwB6gg7/wfNvAbjwJYmDXy1Jo1POjyTzlB6wPcHiGlRxq39TSjYGVjQrUSAzpv+a7nzp7mDxgNy57xA==" + } } }, "@nestjs/testing": { @@ -7928,6 +7940,11 @@ } } }, + "encoding-negotiator": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/encoding-negotiator/-/encoding-negotiator-2.0.1.tgz", + "integrity": "sha512-GSK7qphNR4iPcejfAlZxKDoz3xMhnspwImK+Af5WhePS9jUpK/Oh7rUdyENWu+9rgDflOCTmAojBsgsvM8neAQ==" + }, "end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -8786,6 +8803,130 @@ "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", "dev": true }, + "fastify-plugin": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-3.0.1.tgz", + "integrity": "sha512-qKcDXmuZadJqdTm6vlCqioEbyewF60b/0LOFCcYN1B6BIZGlYJumWWOYs70SFYLDAH4YqdE1cxH/RKMG7rFxgA==" + }, + "fastify-static": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/fastify-static/-/fastify-static-4.7.0.tgz", + "integrity": "sha512-zZhCfJv/hkmud2qhWqpU3K9XVAuy3+IV8Tp9BC5J5U+GyA2XwoB6h8lh9GqpEIqdXOw01WyWQllV7dOWVyAlXg==", + "requires": { + "fastify-static-deprecated": "npm:fastify-static@4.6.1", + "process-warning": "^1.0.0" + } + }, + "fastify-static-deprecated": { + "version": "npm:fastify-static@4.6.1", + "resolved": "https://registry.npmjs.org/fastify-static/-/fastify-static-4.6.1.tgz", + "integrity": "sha512-vy7N28U4AMhuOim12ZZWHulEE6OQKtzZbHgiB8Zj4llUuUQXPka0WHAQI3njm1jTCx4W6fixUHfpITxweMtAIA==", + "requires": { + "content-disposition": "^0.5.3", + "encoding-negotiator": "^2.0.1", + "fastify-plugin": "^3.0.0", + "glob": "^7.1.4", + "p-limit": "^3.1.0", + "readable-stream": "^3.4.0", + "send": "^0.17.1" + }, + "dependencies": { + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg==" + }, + "http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "requires": { + "ee-first": "1.1.1" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "send": { + "version": "0.17.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", + "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "1.8.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + } + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==" + } + } + }, + "fastify-swagger": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/fastify-swagger/-/fastify-swagger-5.2.0.tgz", + "integrity": "sha512-yKct50Mev9YIrhd2FRO4AChcJM9JwTBCziIjA4C+AI+hV2ystaIklgHVEwHoyqlaeQ+B4gZ1Z5rgOE87i4llLg==", + "requires": { + "fastify-swagger-deprecated": "npm:fastify-swagger@5.1.1", + "process-warning": "^1.0.0" + }, + "dependencies": { + "fastify-swagger-deprecated": { + "version": "npm:fastify-swagger@5.1.1", + "resolved": "https://registry.npmjs.org/fastify-swagger/-/fastify-swagger-5.1.1.tgz", + "integrity": "sha512-7DA0zS8CCV5r+gbLgWdeeKEwLrVbbOxLMJVUfOl1H9+wSildSLD8hok2TLX7s3c28wOjF8+iZRxsz/hBDzfdIw==", + "requires": { + "fastify-plugin": "^3.0.0", + "fastify-static": "^4.0.0", + "js-yaml": "^4.0.0", + "json-schema-resolver": "^1.3.0", + "openapi-types": "^10.0.0", + "rfdc": "^1.3.0" + } + } + } + }, "fastq": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", @@ -11487,6 +11628,31 @@ } } }, + "json-schema-resolver": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/json-schema-resolver/-/json-schema-resolver-1.3.0.tgz", + "integrity": "sha512-EX7W1r8aZ/T3j8GbbBxPXi60bnsELfT90OiA1QrbGMvwzVSbyMNOAzvMFcFb8m7gKCXZLJpGe+cJOvWgoFl29A==", + "requires": { + "debug": "^4.1.1", + "rfdc": "^1.1.4", + "uri-js": "^4.2.2" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, "json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -13309,6 +13475,11 @@ "is-wsl": "^2.1.1" } }, + "openapi-types": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-10.0.0.tgz", + "integrity": "sha512-Y8xOCT2eiKGYDzMW9R4x5cmfc3vGaaI4EL2pwhDmodWw1HlK18YcZ4uJxc7Rdp7/gGzAygzH9SXr6GKYIXbRcQ==" + }, "optional": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/optional/-/optional-0.1.4.tgz", @@ -13417,7 +13588,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, "requires": { "yocto-queue": "^0.1.0" } @@ -14254,6 +14424,11 @@ "type": "^2.1.0" } }, + "process-warning": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-1.0.0.tgz", + "integrity": "sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q==" + }, "promise-inflight": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", @@ -14931,6 +15106,11 @@ "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", "dev": true }, + "rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==" + }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -17796,7 +17976,6 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, "requires": { "punycode": "^2.1.0" } @@ -18375,8 +18554,7 @@ "yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" }, "zip-stream": { "version": "4.1.0", diff --git a/backend-api/package.json b/backend-api/package.json index f49f25a..e1c3013 100644 --- a/backend-api/package.json +++ b/backend-api/package.json @@ -33,7 +33,7 @@ "@nestjs/mapped-types": "*", "@nestjs/passport": "^9.0.0", "@nestjs/platform-express": "^9.0.0", - "@nestjs/swagger": "^6.1.2", + "@nestjs/swagger": "^6.1.4", "@nestjs/typeorm": "^9.0.1", "@nestjsplus/knex": "^1.0.0", "@types/cookie-parser": "^1.4.3", @@ -48,6 +48,7 @@ "cookie-parser": "^1.4.6", "cross-env": "^7.0.3", "dylib-node": "^1.0.10", + "fastify-swagger": "^5.2.0", "js-joda": "^1.11.0", "knex": "^2.3.0", "knex-bigquery": "^2.0.3", diff --git a/backend-api/src/app.module.ts b/backend-api/src/app.module.ts index e29d7e3..1c9051b 100644 --- a/backend-api/src/app.module.ts +++ b/backend-api/src/app.module.ts @@ -28,10 +28,9 @@ import { ShareUrlModule } from './share-url/share-url.module'; port: parseInt(process.env.DB_PORT) || 3306, username: process.env.DB_USERNAME, password: process.env.DB_PASSWORD, - database: 'vanillameta_auth', + database: process.env.DB_NAME, autoLoadEntities: true, entities: [__dirname + '/**/*.entity{.ts,.js}'], - // entities: ["dist/common/entities/*{.ts,.js}"], synchronize: false, logging: process.env.NODE_ENV == 'prod', retryAttempts: 1, diff --git a/backend-api/src/auth/auth.service.ts b/backend-api/src/auth/auth.service.ts index d5ef7f9..c741432 100644 --- a/backend-api/src/auth/auth.service.ts +++ b/backend-api/src/auth/auth.service.ts @@ -43,7 +43,7 @@ export class AuthService { // accesstoken이 없을때 } - async setRefreshKey(refreshToken: string, jwt_id: string){ + async setRefreshKey(refreshToken: string, jwt_id: number){ const findToken = await this.refreshTokenRepository.findOne({ where: { id: jwt_id } }); const token = refreshToken.replace('Bearer ', ''); if(!findToken){ @@ -68,12 +68,13 @@ export class AuthService { // 회원이 존재하는지 확인 } - async deleteRefreshToken(Token: string) { - const token = Token.replace('Bearer ', '').split('=')[1]; + async deleteRefreshToken(userId: number) { + console.log(userId) const refreshTokenInfo = await this.refreshTokenRepository.findOne({ - where: { refreshToken: token }, + where: { id: userId }, }); - refreshTokenInfo.refreshToken = null; + console.log(refreshTokenInfo) + refreshTokenInfo.refreshToken = ''; await this.refreshTokenRepository.save(refreshTokenInfo); } @@ -99,8 +100,7 @@ export class AuthService { } } // Refresh 토큰이 유효한지 확인 - async checkAccess(token: string, password: string) { - const { accessKeyData } = await this.verifyAccessToken(token); - return await this.validateUser(accessKeyData.userId, password) + async checkAccess(userId: string, password: string) { + return await this.validateUser(userId, password) } } diff --git a/backend-api/src/auth/entites/refresh_token.entity.ts b/backend-api/src/auth/entites/refresh_token.entity.ts index 80691ea..73c0372 100644 --- a/backend-api/src/auth/entites/refresh_token.entity.ts +++ b/backend-api/src/auth/entites/refresh_token.entity.ts @@ -3,7 +3,7 @@ import { Column, Entity, PrimaryColumn } from 'typeorm'; @Entity() export class RefreshToken { @PrimaryColumn() - id: string; + id: number; @Column() refreshToken: string; diff --git a/backend-api/src/auth/guards/jwt-auth.guard.ts b/backend-api/src/auth/guards/jwt-auth.guard.ts index 560d54d..b9ec67b 100644 --- a/backend-api/src/auth/guards/jwt-auth.guard.ts +++ b/backend-api/src/auth/guards/jwt-auth.guard.ts @@ -15,6 +15,7 @@ export class JwtAuthGuard extends AuthGuard('jwt') { } const token = authorization.replace('Bearer ', ''); const userInfo = await this.validate(token); + if (userInfo) request.user = userInfo; return !!userInfo; } diff --git a/backend-api/src/dashboard/dashboard.controller.ts b/backend-api/src/dashboard/dashboard.controller.ts index 974e8fa..dafd036 100644 --- a/backend-api/src/dashboard/dashboard.controller.ts +++ b/backend-api/src/dashboard/dashboard.controller.ts @@ -11,15 +11,15 @@ export class DashboardController { @UseGuards(JwtAuthGuard) @Post() create(@Body() createDashboardDto: CreateDashboardDto, @Req() req) { - const { authorization } = req.headers; - return this.dashboardService.create(createDashboardDto, authorization); + const { accessKeyData } = req.user + return this.dashboardService.create(createDashboardDto, accessKeyData.id); } @UseGuards(JwtAuthGuard) @Get() findAll(@Req() req) { - const { authorization } = req.headers; - return this.dashboardService.findAll(authorization); + const { accessKeyData } = req.user; + return this.dashboardService.findAll(accessKeyData.id); } @UseGuards(JwtAuthGuard) diff --git a/backend-api/src/dashboard/dashboard.service.ts b/backend-api/src/dashboard/dashboard.service.ts index 635c63a..ff353d9 100644 --- a/backend-api/src/dashboard/dashboard.service.ts +++ b/backend-api/src/dashboard/dashboard.service.ts @@ -30,11 +30,9 @@ export class DashboardService { private readonly authService: AuthService, ) {} - async create(createDashboardDto: CreateDashboardDto, accessToken: string) { - const findUser = await this.authService.verifyAccessToken(accessToken); - const { accessKeyData } = findUser; + async create(createDashboardDto: CreateDashboardDto, accessToken: number) { const userData = await this.userRepository.findOne({ - where: { id: accessKeyData.id }, + where: { id: accessToken }, }); if (!userData) { return 'Bad Request'; @@ -53,7 +51,7 @@ export class DashboardService { if (findTitle.title !== createDashboardDto.title) { const share_id = await this.dashboardShareRepository.save({ - uuid: uuidv4(), + uuid: uuidv4(), // uuid의 버전 uuidv1의 결우 mac의 정보등을 담고있음. createdAt: new Date(), updatedAt: new Date() }); @@ -81,6 +79,7 @@ export class DashboardService { // .orUpdate(['title', 'layout'], ['id']) // .execute(); // console.log('test', test.generatedMaps[0].id); + // 기존 코드 await this.userService.saveDashboard(newDashboard.id, id); await this.dashboardWidgetService.create(saveObjDW); @@ -91,8 +90,8 @@ export class DashboardService { return 'already exist name title'; } - async findAll(accessToken: string) { - const findUser = await this.userService.findDashboardId(accessToken); + async findAll(userId: number) { + const findUser = await this.userService.findDashboardId(userId); const findId = findUser.map(el => el['dashboardId']); const find_all = []; @@ -149,6 +148,7 @@ export class DashboardService { dashboardId: id, widgetIds: widgetIds, }; + // 업데이트할 데이터 await this.dashboardWidgetService.update(id, saveObjDW); const updatedDashboard = await this.dashboardRepository.save(find_dashboard); diff --git a/backend-api/src/login/login.controller.ts b/backend-api/src/login/login.controller.ts index 273179c..2d29cf6 100644 --- a/backend-api/src/login/login.controller.ts +++ b/backend-api/src/login/login.controller.ts @@ -6,7 +6,10 @@ import { LoginUserDto } from '../login/dto/login-user.dto'; import { AuthService } from 'src/auth/auth.service'; import { JwtAuthGuard } from 'src/auth/guards/jwt-auth.guard'; import { LocalAuthGuard } from 'src/auth/guards/local-auth.guard'; +import {ApiOperation, ApiTags } from '@nestjs/swagger'; + +@ApiTags('로그인 관련 API') @Controller('login') export class LoginController { constructor( @@ -16,6 +19,7 @@ export class LoginController { @UsePipes(ValidationPipe) @Post('signin') + @ApiOperation({ summary: ' 로그인 ' }) async logIn(@Res() res,@Req() req, @Body() loginDto: LoginUserDto){ const findUser = await this.loginService.signin(loginDto) // 유저존재여부 확인 @@ -27,25 +31,27 @@ export class LoginController { // RefreshToken 저장 res.cookie('jwt_re', refreshToken, { - httpOnly: true, - sameSite: 'none', - secure: true + httpOnly: true, // 브라우저에서 cookie에 에 접근권한 x + sameSite: 'Lax', // 다른 도메인의 cookie를 허용한 주소만 가져올 수 있음 + secure: true // 보안처리된 https만 허 }) return res.status(201).json({ accessToken: accessToken, message: 'success' }) } + // validationPipe = 들어오는 모든 클라이언트 페이로드에 대한 유효성 검사 규칙을 적용 @UsePipes(ValidationPipe) @Post('signup') + @ApiOperation({ summary: '회원가입' }) create(@Body() createUserDto: CreateLoginDto) { return this.loginService.signup(createUserDto); } - @UseGuards(LocalAuthGuard) + @UseGuards(LocalAuthGuard) //refrshtoken 검사 @Post('signout') + @ApiOperation({ summary: '로그아웃' }) async signOut(@Res() res, @Req() req) { - const refreshToken = req.headers.cookie; - delete req.headers.authorization; - await this.authService.deleteRefreshToken(refreshToken) + const { jwtId } = req.user.refreshKeyData; + await this.authService.deleteRefreshToken(jwtId) return res.status(201).clearCookie('jwt_re').json({ message: 'success'}) } } diff --git a/backend-api/src/login/login.service.ts b/backend-api/src/login/login.service.ts index 35b1390..36bb289 100644 --- a/backend-api/src/login/login.service.ts +++ b/backend-api/src/login/login.service.ts @@ -19,7 +19,7 @@ export class LoginService { async signin(loginDto: LoginUserDto) { const { userId, password } = loginDto; - const findUser = await this.authService.validateUser(userId, password); + const findUser = await this.authService.validateUser(userId, password); // 요저의 존재여부 확인 if (!findUser) { throw new UnauthorizedException(`Unauthorized`); } diff --git a/backend-api/src/main.ts b/backend-api/src/main.ts index 03cd5f6..3516822 100644 --- a/backend-api/src/main.ts +++ b/backend-api/src/main.ts @@ -5,13 +5,13 @@ import { ExpressAdapter } from '@nestjs/platform-express'; import { HttpExceptionFilter } from './nest-utils/http-exception.filter'; import { ValidationPipe } from '@nestjs/common'; import cookieParser from 'cookie-parser'; +import { setupSwagger } from './utils/swagger.js'; async function bootstrap() { const expressApp = express(); - const whiteList = ['https://vanillameta-dev.vanillabrain.com', 'http://localhost:3000'] + const whiteList = ['https://vanillameta-dev.vanillabrain.com', 'http://localhost:3000', 'http://localhost:4000'] const app = await NestFactory.create(AppModule, new ExpressAdapter(expressApp), { logger: console, - cors: { origin: function (origin, callback){ if(whiteList.indexOf(origin) !== -1){ @@ -29,8 +29,7 @@ async function bootstrap() { }); app.useGlobalFilters(new HttpExceptionFilter()); app.use(cookieParser()); - // app.useGlobalPipes(new ValidationPipe({ transform: true })); - // const app = await NestFactory.create(AppModule); + setupSwagger(app) await app.listen(4000); } diff --git a/backend-api/src/share-url/dto/create-share-url.dto.ts b/backend-api/src/share-url/dto/create-share-url.dto.ts index 4bf3def..8627d29 100644 --- a/backend-api/src/share-url/dto/create-share-url.dto.ts +++ b/backend-api/src/share-url/dto/create-share-url.dto.ts @@ -1,11 +1,14 @@ +import { ApiProperty } from '@nestjs/swagger'; import { IsNotEmpty, IsNumber, IsString } from 'class-validator'; export class ShareUrlOnDto { @IsString() @IsNotEmpty() + @ApiProperty({ description: '유저Id'}) userId: string; @IsString() @IsNotEmpty() + @ApiProperty({ description: '공유 url 공유기간'}) endDate: string; } diff --git a/backend-api/src/share-url/share-url.controller.ts b/backend-api/src/share-url/share-url.controller.ts index 52fa383..ac35201 100644 --- a/backend-api/src/share-url/share-url.controller.ts +++ b/backend-api/src/share-url/share-url.controller.ts @@ -3,28 +3,33 @@ import { ShareUrlService } from './share-url.service'; import { ShareUrlOnDto } from './dto/create-share-url.dto'; import { UpdateShareUrlDto } from './dto/update-share-url.dto'; import { JwtAuthGuard } from 'src/auth/guards/jwt-auth.guard'; +import {ApiOperation, ApiParam, ApiTags } from '@nestjs/swagger'; @Controller('share-url') +@ApiTags('url공유화 API') export class ShareUrlController { constructor(private readonly shareUrlService: ShareUrlService) {} @UseGuards(JwtAuthGuard) @Post('share-on/:dashboardId') + @ApiOperation({ summary: '공유 url On' }) checkShareUrlOn(@Req() req, @Param() params, @Body() shareUrlOnDto: ShareUrlOnDto) { - const { authorization } = req.headers; + const { userId } = req.user.accessKeyData; const { dashboardId } = params; - return this.shareUrlService.checkShareUrlOn( authorization, dashboardId, shareUrlOnDto ) + return this.shareUrlService.checkShareUrlOn( userId, dashboardId, shareUrlOnDto ) } @UseGuards(JwtAuthGuard) @Post('share-off/:dashboardId') + @ApiOperation({ summary: '공유 url Off' }) checkShareUrlOff(@Req() req, @Param() params, @Body() shareUrlOnDto: ShareUrlOnDto) { - const { authorization } = req.headers; + const { userId } = req.user.accessKeyData; const { dashboardId } = params; - return this.shareUrlService.checkShareUrlOff( authorization, dashboardId, shareUrlOnDto) + return this.shareUrlService.checkShareUrlOff( userId, dashboardId, shareUrlOnDto) } @Get('share-dashboard/:uuid') + @ApiOperation({ summary: '공유 url 접속' }) async shareDashboardInfo(@Param() param) { const { shareUrl } = param; return await this.shareUrlService.shareDashboardInfo(shareUrl) diff --git a/backend-api/src/share-url/share-url.service.ts b/backend-api/src/share-url/share-url.service.ts index b513a6b..3a0ae75 100644 --- a/backend-api/src/share-url/share-url.service.ts +++ b/backend-api/src/share-url/share-url.service.ts @@ -22,9 +22,8 @@ export class ShareUrlService { } - async checkShareUrlOn( accessToken:string, dashboardId: number, shareUrlOnDto: ShareUrlOnDto ) { - const findpass = await this.userRepository.findOne({ where: { userId: shareUrlOnDto.userId }}) - const findUser = await this.authService.checkAccess(accessToken, findpass.password); + async checkShareUrlOn( userId:string, dashboardId: number, shareUrlOnDto: ShareUrlOnDto ) { + const findUser = await this.userRepository.findOne({ where: { userId: userId }}) if(!findUser){ return 'not exist user' } else { @@ -40,22 +39,23 @@ export class ShareUrlService { return { uuid: findDashboardShare.uuid, message: "success" } } } + // 공유기능 on시 공유토큰과 endDate를 저장 - async checkShareUrlOff( accessToken:string, dashboardId: number, shareUrlOnDto: ShareUrlOnDto){ - const findpass = await this.userRepository.findOne({ where: { userId: shareUrlOnDto.userId }}) - const findUser = await this.authService.checkAccess(accessToken, findpass.password); + async checkShareUrlOff( userId:string, dashboardId: number, shareUrlOnDto: ShareUrlOnDto){ + const findUser = await this.userRepository.findOne({ where: { userId: userId }}) if(!findUser){ return 'not exist user' } else { const findDashboard = await this.dashboardRepository.findOne( { where: { id: dashboardId } }) const findDashboardShare = await this.dashboardShareRepository.findOne({ where: { id: findDashboard.shareId }}) - findDashboardShare.shareToken = null; + findDashboardShare.shareToken = ''; findDashboardShare.shareYn = YesNo.NO; findDashboardShare.endDate = null; await this.dashboardShareRepository.save(findDashboardShare) return { message: "success" } } } + // 공유기능 off시 쉐어토큰, endDate을 없애고 사용가능여부를 N으로 저장 async shareDashboardInfo(uuid: string){ const findDashboardShareUrl = await this.dashboardShareRepository.findOne({ where: { uuid: uuid }}) @@ -65,4 +65,5 @@ export class ShareUrlService { } return this.dashboardService.findOne(+findDashboard.id) } + // 공유url로 접속시 대시보드의 정보를 받아오는 코드 } diff --git a/backend-api/src/user/dto/create-user.dto.ts b/backend-api/src/user/dto/create-user.dto.ts index bd988eb..fb33800 100644 --- a/backend-api/src/user/dto/create-user.dto.ts +++ b/backend-api/src/user/dto/create-user.dto.ts @@ -1,16 +1,21 @@ +import { ApiProperty, ApiTags } from '@nestjs/swagger'; import { IsNotEmpty, IsNumber, IsString } from 'class-validator'; +@ApiTags('유저생성 Dto') export class CreateUserDto { @IsString() @IsNotEmpty() + @ApiProperty({ description: '유저Id'}) userId: string; @IsString() @IsNotEmpty() + @ApiProperty({ description: '유저password'}) password: string; @IsString() @IsNotEmpty() + @ApiProperty({ description: '유저email'}) email: string; } diff --git a/backend-api/src/user/entities/user-mapping.entity.ts b/backend-api/src/user/entities/user-mapping.entity.ts index 849be06..ed1efbc 100644 --- a/backend-api/src/user/entities/user-mapping.entity.ts +++ b/backend-api/src/user/entities/user-mapping.entity.ts @@ -1,3 +1,4 @@ +import { ApiProperty } from '@nestjs/swagger'; import { IsNotEmpty, IsOptional } from 'class-validator'; import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn} from 'typeorm'; import { BaseEntity } from '../../common/entities/base.entity'; @@ -5,13 +6,16 @@ import { BaseEntity } from '../../common/entities/base.entity'; export class UserMapping extends BaseEntity { @PrimaryGeneratedColumn() + @ApiProperty({ description: 'id'}) id: number; @IsOptional() @Column() + @ApiProperty({ description: '대시보드id'}) dashboardId: number; @Column() + @ApiProperty({ description: '유저id'}) userInfoId: number; // @IsOptional() diff --git a/backend-api/src/user/entities/user.entity.ts b/backend-api/src/user/entities/user.entity.ts index fe4a563..69b37a4 100644 --- a/backend-api/src/user/entities/user.entity.ts +++ b/backend-api/src/user/entities/user.entity.ts @@ -1,3 +1,4 @@ +import { ApiProperty } from '@nestjs/swagger'; import { IsNotEmpty, IsOptional } from 'class-validator'; import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, BaseEntity} from 'typeorm'; @@ -6,21 +7,26 @@ import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateCol export class User { @PrimaryGeneratedColumn() + @ApiProperty({ description: 'id'}) id: number; @IsNotEmpty() @Column() + @ApiProperty({ description: '유저Id'}) userId: string; @Column() - jwtId: string; + @ApiProperty({ description: 'refreshTokenId'}) + jwtId: number; @IsNotEmpty() @Column() + @ApiProperty({ description: '유저email'}) email: string; @IsNotEmpty() @Column() + @ApiProperty({ description: '유저password'}) password: string; @CreateDateColumn({ default: () => 'CURRENT_TIMESTAMP', type: "timestamp" }) diff --git a/backend-api/src/user/user.controller.ts b/backend-api/src/user/user.controller.ts index c12e017..ca60dae 100644 --- a/backend-api/src/user/user.controller.ts +++ b/backend-api/src/user/user.controller.ts @@ -4,8 +4,11 @@ import { CreateUserDto } from './dto/create-user.dto'; import { UpdateUserDto } from './dto/update-user.dto'; import { JwtAuthGuard } from 'src/auth/guards/jwt-auth.guard'; import { AuthService } from 'src/auth/auth.service'; +import { LocalAuthGuard } from 'src/auth/guards/local-auth.guard'; +import {ApiOperation, ApiTags } from '@nestjs/swagger'; @Controller('user') +@ApiTags('유저 API') export class UserController { constructor( private readonly userService: UserService, @@ -14,38 +17,43 @@ export class UserController { @UseGuards(JwtAuthGuard) @Get('userinfo') + @ApiOperation({ summary: '해당유저정보 가져오기' }) findOne(@Req() req) { - const { authorization } = req.headers; - return this.userService.findOne(authorization); + const { id } = req.user.accessKeyData; + return this.userService.findOne(id); } @UseGuards(JwtAuthGuard) @Patch('change-info') + @ApiOperation({ summary: '유저정보 수정' }) updateUsername(@Req() req, @Body() updateUserDto: UpdateUserDto) { - const { authorization } = req.headers; - return this.userService.updateUserInfo(authorization, updateUserDto); + const { userId } = req.user.accessKeyData; + return this.userService.updateUserInfo(userId, updateUserDto); } @UseGuards(JwtAuthGuard) @Delete('delete-account') + @ApiOperation({ summary: ' 해당유저 삭제 ' }) deleteUser(@Req() req, @Body() createUserDto: CreateUserDto) { - const { authorization } = req.headers; + const { userId } = req.user.accessKeyData; const { password } = createUserDto; - return this.userService.deleteUser(authorization, password); + return this.userService.deleteUser(userId, password); } - // @UseGuards(JwtAuthGuard) + @UseGuards(LocalAuthGuard) @Post('get-access-token') + @ApiOperation({ summary: 'AccessToken 재 발급' }) async reissuanceAccessToken(@Req() req, @Res() res) { - const { cookie } = req.headers; - const accessToken = await this.userService.reissuanceAccessToken(cookie); + const { userId } = req.user.refreshKeyData; + const accessToken = await this.userService.reissuanceAccessToken(userId); return res.status(201).json({ accessToken: accessToken, message: 'success' }); } @UseGuards(JwtAuthGuard) @Get('get-dashboard') + @ApiOperation({ summary: '해당유정의 대시보드 목록 가져오기' }) async findDashboardId(@Req() req) { - const { authorization } = req.headers; - await this.userService.findDashboardId(authorization); + const { id } = req.headers.accessKeyData; + await this.userService.findDashboardId(id); } } diff --git a/backend-api/src/user/user.service.ts b/backend-api/src/user/user.service.ts index 25c40dd..6cdd085 100644 --- a/backend-api/src/user/user.service.ts +++ b/backend-api/src/user/user.service.ts @@ -18,11 +18,9 @@ export class UserService { private authService: AuthService, ) {} - async findOne(accessToken: string) { - const findUser = await this.authService.verifyAccessToken(accessToken) - const { accessKeyData } = findUser; + async findOne(userId: number) { const userData = await this.userRepository.findOne({ - where: { userId: accessKeyData.userId } + where: { id: userId } }); if(!userData){ return 'Bad Request' @@ -32,8 +30,8 @@ export class UserService { } } - async updateUserInfo(accessToken: string, updateUserDto: UpdateUserDto) { - const findUser = await this.authService.checkAccess(accessToken, updateUserDto.password); + async updateUserInfo(userId: string, updateUserDto: UpdateUserDto) { + const findUser = await this.authService.checkAccess(userId, updateUserDto.password); if(!findUser){ throw new HttpException('not exist user', HttpStatus.CONFLICT); } else { @@ -44,8 +42,8 @@ export class UserService { } } - async deleteUser( accessToken:string, password: string ) { - const findUser = await this.authService.checkAccess(accessToken, password); + async deleteUser( userId:string, password: string ) { + const findUser = await this.authService.checkAccess(userId, password); if(!findUser){ return 'Unauthorized' } else { @@ -54,30 +52,27 @@ export class UserService { } } - async reissuanceAccessToken(cookie: string){ - const tokenInfo = await this.authService.verifyRefreshToken(cookie) - - const { userId } = tokenInfo.refreshKeyData + async reissuanceAccessToken(userId: string){ const payload = await this.userRepository.findOne({ where: { userId: userId }}) return await this.authService.generateAccessToken(payload) } + // AccessToken 만료시 재발급 코드 async saveDashboard(dashboardId: number, userInfoId: number) { - console.log(userInfoId) const saveObj = { dashboardId: dashboardId, userInfoId: userInfoId } await this.userMappingRepository.save(saveObj) } + // mapping table 대시보드id, 유저id 저장 - async findDashboardId(accesstoken: string){ - const findUser = await this.authService.verifyAccessToken(accesstoken) - const { accessKeyData } = findUser; + async findDashboardId(id: number){ const findDashboard = await this.userMappingRepository .createQueryBuilder('user_mapping') .select("dashboardId") - .where('user_mapping.userInfoId = :userInfoId', { userInfoId: accessKeyData.id }).getRawMany() + .where('user_mapping.userInfoId = :userInfoId', { userInfoId: id }).getRawMany() return findDashboard } + // 대시보드id찾는 코드 } diff --git a/backend-api/src/utils/swagger-APIhelper.ts b/backend-api/src/utils/swagger-APIhelper.ts new file mode 100644 index 0000000..efbd762 --- /dev/null +++ b/backend-api/src/utils/swagger-APIhelper.ts @@ -0,0 +1,30 @@ +import { OpenAPIObject } from '@nestjs/swagger'; +import { PathsObject } from '@nestjs/swagger/dist/interfaces/open-api-spec.interface'; + +const arrayIntersect = (array1: string[], array2: string[]): string[] => array1.filter((item) => array2.includes(item)); + +export const filterDocumentsPathsByTags = (publicDocument: OpenAPIObject): PathsObject => { + const result: PathsObject = {}; + + + const tags = publicDocument.tags.map(({ name }) => name); // 모든 tage의 이름 + for (const path of Object.keys(publicDocument.paths)) { + const pathMethods = {}; + + for (const method of Object.keys(publicDocument.paths[path])) { + const endpointTags = publicDocument.paths[path][method].tags; //addTag로 경로 설정한 테그이름 + console.log(publicDocument.components) + if (!Array.isArray(endpointTags)) { + continue; + } + if (arrayIntersect(tags, endpointTags).length > 0) { + pathMethods[method] = publicDocument.paths[path][method]; // 모든 tag의 이름과 경로 설정한 테그이름이 같은것. + + } + } + + result[path] = pathMethods; + } + + return result; +}; \ No newline at end of file diff --git a/backend-api/src/utils/swagger.ts b/backend-api/src/utils/swagger.ts new file mode 100644 index 0000000..3253721 --- /dev/null +++ b/backend-api/src/utils/swagger.ts @@ -0,0 +1,34 @@ +import { INestApplication } from '@nestjs/common'; +import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; +import { filterDocumentsPathsByTags } from './swagger-APIhelper'; +import { filterDocumentsDtoPathsByTags } from './swagger/Dtohelper' + +/** + * Swagger 세팅 + * + * @param {INestApplication} app + */ +export function setupSwagger(app: INestApplication): void { + const options = new DocumentBuilder() + .setTitle('NestJS Study API Docs') + .setDescription('NestJS Study API description') + .setVersion('3.0.3') + .addBearerAuth({ + type: 'http', + scheme: 'bearer', + bearerFormat: 'JWT', + name: 'jwt_ac', + description: 'Enter jwt token', + in: 'header' + },'AccessKey') + .addTag('유저 API') + .addTag('url공유화 API') + .addTag('로그인 관련 API') + .build(); + + + const document = SwaggerModule.createDocument(app, options); + document.paths = filterDocumentsPathsByTags(document); + // document.paths = filterDocumentsDtoPathsByTags(document); + SwaggerModule.setup('api-docs', app, document); +} \ No newline at end of file diff --git a/backend-api/src/utils/swagger/Dtohelper.ts b/backend-api/src/utils/swagger/Dtohelper.ts new file mode 100644 index 0000000..4481660 --- /dev/null +++ b/backend-api/src/utils/swagger/Dtohelper.ts @@ -0,0 +1,30 @@ +import { OpenAPIObject } from '@nestjs/swagger'; +import { ComponentsObject } from '@nestjs/swagger/dist/interfaces/open-api-spec.interface'; + +const arrayIntersect = (array1: string[], array2: string[]): string[] => array1.filter((item) => array2.includes(item)); + +export const filterDocumentsDtoPathsByTags = (publicDocument: OpenAPIObject): ComponentsObject => { + const result: ComponentsObject = {}; + + const tags = publicDocument.tags.map(({ name }) => name); // 모든 tage의 이름 + console.log(tags) + for (const path of Object.keys(publicDocument.paths)) { + const pathMethods = {}; + + for (const method of Object.keys(publicDocument.paths[path])) { + const endpointTags = publicDocument.paths[path][method].tags; //addTag로 경로 설정한 테그이름 + + if (!Array.isArray(endpointTags)) { + continue; + } + + if (arrayIntersect(tags, endpointTags).length > 0) { + pathMethods[method] = publicDocument.paths[path][method]; // 모든 tag의 이름과 경로 설정한 테그이름이 같은것. + } + } + console.log(result) + result[path] = pathMethods; + } + + return result; +}; \ No newline at end of file diff --git a/landing-page/Jenkinsfile b/landing-page/Jenkinsfile new file mode 100644 index 0000000..d032365 --- /dev/null +++ b/landing-page/Jenkinsfile @@ -0,0 +1,16 @@ +pipeline { + agent any + tools { + git "git" + } + stages { + stage('deploy') { + steps { + sh "aws configure set region $AWS_DEFAULT_REGION" + sh "aws configure set aws_access_key_id $AWS_ACCESS_KEY_ID" + sh "aws configure set aws_secret_access_key $AWS_SECRET_ACCESS_KEY" + sh "aws s3 cp landing-page s3://vanillameta.net --recursive --acl public-read" + } + } + } +}