From cae09437132dbdaa0b8ae4da29be2e672db5c523 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EA=B7=BC=ED=98=95?= Date: Tue, 21 May 2024 14:59:31 +0900 Subject: [PATCH 01/13] =?UTF-8?q?feat:=20/kakao/token/info=20=EC=B9=B4?= =?UTF-8?q?=EC=B9=B4=EC=98=A4=20=ED=86=A0=ED=81=B0=20=EC=A0=95=EB=B3=B4=20?= =?UTF-8?q?=EB=B3=B4=EA=B8=B0=20=EC=97=94=EB=93=9C=ED=8F=AC=EC=9D=B8?= =?UTF-8?q?=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/auth/auth.controller.ts | 6 ++++++ src/auth/auth.service.ts | 13 +++++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts index 2ca9198..bc57723 100644 --- a/src/auth/auth.controller.ts +++ b/src/auth/auth.controller.ts @@ -29,6 +29,12 @@ export class AuthController { return this.authService.kakaoToken(code); } + @Get('kakao/token/info') + @ApiQuery({ name: 'kakaoToken', required: true }) + async kakaoTokenInfo(@Query('kakaoToken') kakaoToken: string) { + return this.authService.kakaoTokenInfo(kakaoToken); + } + @Post('kakao/login') @ApiQuery({ name: 'kakaoToken', required: true }) async kakaoLogin(@Query('kakaoToken') kakaoToken: string) { diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index a70becd..3bbacfa 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -40,6 +40,19 @@ export class AuthService { return data['access_token']; } + async kakaoTokenInfo(kakaoToken: string) { + const { data } = await firstValueFrom( + this.httpService.get('https://kapi.kakao.com/v1/user/access_token_info', { + headers: { + Authorization: `Bearer ${kakaoToken}`, + 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', + }, + }), + ); + + return data; + } + async kakaoLogin(kakaoToken: string) { const { data } = await firstValueFrom( this.httpService.get('https://kapi.kakao.com/v2/user/me', { From b0e077f9d4b2a675241abb821e62262e40e617fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EA=B7=BC=ED=98=95?= Date: Tue, 21 May 2024 16:09:10 +0900 Subject: [PATCH 02/13] =?UTF-8?q?feat:=20User=20=EB=AA=A8=EB=93=88=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/user/dto/create-user.dto.ts | 6 ++++- src/user/interfaces/user.interfaces.ts | 5 ++++ src/user/schemas/user.schema.ts | 7 ++++++ src/user/user.module.ts | 6 ++++- src/user/user.providers.ts | 12 +++++++++ src/user/user.repository.ts | 35 ++++++++++++++++++++++++++ src/user/user.service.ts | 15 ++++++----- 7 files changed, 78 insertions(+), 8 deletions(-) create mode 100644 src/user/interfaces/user.interfaces.ts create mode 100644 src/user/schemas/user.schema.ts create mode 100644 src/user/user.providers.ts create mode 100644 src/user/user.repository.ts diff --git a/src/user/dto/create-user.dto.ts b/src/user/dto/create-user.dto.ts index 0311be1..db50069 100644 --- a/src/user/dto/create-user.dto.ts +++ b/src/user/dto/create-user.dto.ts @@ -1 +1,5 @@ -export class CreateUserDto {} +export class CreateUserDto { + readonly kakaoId: number; + readonly name: string; + readonly email: string; +} diff --git a/src/user/interfaces/user.interfaces.ts b/src/user/interfaces/user.interfaces.ts new file mode 100644 index 0000000..6bb250f --- /dev/null +++ b/src/user/interfaces/user.interfaces.ts @@ -0,0 +1,5 @@ +export interface UserInterface extends Document { + kakaoId: number; + name: string; + email: string; +} diff --git a/src/user/schemas/user.schema.ts b/src/user/schemas/user.schema.ts new file mode 100644 index 0000000..c10ded3 --- /dev/null +++ b/src/user/schemas/user.schema.ts @@ -0,0 +1,7 @@ +import { Schema } from 'mongoose'; + +export const UserSchema = new Schema({ + kakaoId: Number, + name: String, + email: String, +}); diff --git a/src/user/user.module.ts b/src/user/user.module.ts index c59c53a..636de33 100644 --- a/src/user/user.module.ts +++ b/src/user/user.module.ts @@ -1,10 +1,14 @@ import { Module } from '@nestjs/common'; +import { DatabaseModule } from '../database/database.module'; import { UserController } from './user.controller'; +import { userProviders } from './user.providers'; +import { UserRepository } from './user.repository'; import { UserService } from './user.service'; @Module({ + imports: [DatabaseModule], controllers: [UserController], - providers: [UserService], + providers: [UserService, ...userProviders, UserRepository], }) export class UserModule {} diff --git a/src/user/user.providers.ts b/src/user/user.providers.ts new file mode 100644 index 0000000..a8b729d --- /dev/null +++ b/src/user/user.providers.ts @@ -0,0 +1,12 @@ +import { Connection } from 'mongoose'; + +import { UserSchema } from './schemas/user.schema'; + +export const userProviders = [ + { + provide: 'USER_MODEL', + useFactory: (connection: Connection) => + connection.model('User', UserSchema), + inject: ['MONGODB_CONNECTION'], + }, +]; diff --git a/src/user/user.repository.ts b/src/user/user.repository.ts new file mode 100644 index 0000000..ad38445 --- /dev/null +++ b/src/user/user.repository.ts @@ -0,0 +1,35 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { Model } from 'mongoose'; + +import { CreateUserDto } from './dto/create-user.dto'; +import { UserInterface } from './interfaces/user.interfaces'; + +@Injectable() +export class UserRepository { + constructor( + @Inject('USER_MODEL') + private readonly userModel: Model, + ) {} + + create(createUserDto: CreateUserDto) { + return this.userModel.create(createUserDto); + } + + findAll() { + return this.userModel.find().exec(); + } + + findOne(userId: string) { + return this.userModel.findById(userId).exec(); + } + + update(userId: string, updateUserDto: CreateUserDto) { + return this.userModel + .findByIdAndUpdate(userId, updateUserDto, { new: true }) + .exec(); + } + + delete(userId: string) { + this.userModel.findByIdAndDelete(userId).exec().then(); + } +} diff --git a/src/user/user.service.ts b/src/user/user.service.ts index af873a0..ccca61a 100644 --- a/src/user/user.service.ts +++ b/src/user/user.service.ts @@ -2,26 +2,29 @@ import { Injectable } from '@nestjs/common'; import { CreateUserDto } from './dto/create-user.dto'; import { UpdateUserDto } from './dto/update-user.dto'; +import { UserRepository } from './user.repository'; @Injectable() export class UserService { - create(createUserDto: CreateUserDto) { - return createUserDto; + constructor(private readonly userRepository: UserRepository) {} + + async create(createUserDto: CreateUserDto) { + return await this.userRepository.create(createUserDto); } - findAll() { + async findAll() { return `This action returns all user`; } - findOne(id: number) { + async findOne(id: number) { return `This action returns a #${id} user`; } - update(id: number, updateUserDto: UpdateUserDto) { + async update(id: number, updateUserDto: UpdateUserDto) { return { id, updateUserDto }; } - remove(id: number) { + async remove(id: number) { return `This action removes a #${id} user`; } } From 608067256443836ab8cd7dfcc30f564967875ba6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EA=B7=BC=ED=98=95?= Date: Tue, 21 May 2024 19:44:13 +0900 Subject: [PATCH 03/13] =?UTF-8?q?refactor:=20=EC=A4=84=20=EB=81=9D=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20`lf`=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitattributes | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..2125666 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto \ No newline at end of file From 237a0371e4169f7c08471cad7bef6b5ee4fbd573 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EA=B7=BC=ED=98=95?= Date: Tue, 21 May 2024 19:46:59 +0900 Subject: [PATCH 04/13] refactor: pre-commit --- scripts/git-hooks/pre-commit | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 scripts/git-hooks/pre-commit diff --git a/scripts/git-hooks/pre-commit b/scripts/git-hooks/pre-commit new file mode 100644 index 0000000..08058e5 --- /dev/null +++ b/scripts/git-hooks/pre-commit @@ -0,0 +1,2 @@ +#!/usr/bin/env sh +npm run lint From 2aa8dba93fc239d86a41296a4d9df3eba134e214 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EA=B7=BC=ED=98=95?= Date: Tue, 21 May 2024 19:53:08 +0900 Subject: [PATCH 05/13] feat: add User Entity --- src/user/entities/user.entity.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/user/entities/user.entity.ts b/src/user/entities/user.entity.ts index 4f82c14..c20f5c4 100644 --- a/src/user/entities/user.entity.ts +++ b/src/user/entities/user.entity.ts @@ -1 +1,6 @@ -export class User {} +export class User { + id: string; + kakaoId: number; + name: string; + email: string; +} From 4d960cf7b7bf879c15aef977efd7feafb81f0dc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EA=B7=BC=ED=98=95?= Date: Tue, 21 May 2024 19:54:47 +0900 Subject: [PATCH 06/13] refactor: fix git eol `lf` --- .gitattributes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitattributes b/.gitattributes index 2125666..937b84a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1 @@ -* text=auto \ No newline at end of file +*.ts text eol=lf \ No newline at end of file From d5971a749cb3d7ab4a2ec2bd2fdc796e6993e47d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EA=B7=BC=ED=98=95?= Date: Wed, 22 May 2024 17:38:50 +0900 Subject: [PATCH 07/13] feat: User CRUD --- src/user/dto/create-user.dto.ts | 16 ++++++++++++++++ src/user/user.controller.ts | 21 ++++++++++++--------- src/user/user.service.ts | 14 +++++++------- 3 files changed, 35 insertions(+), 16 deletions(-) diff --git a/src/user/dto/create-user.dto.ts b/src/user/dto/create-user.dto.ts index db50069..d3dac81 100644 --- a/src/user/dto/create-user.dto.ts +++ b/src/user/dto/create-user.dto.ts @@ -1,5 +1,21 @@ +import { ApiProperty } from '@nestjs/swagger'; + export class CreateUserDto { + @ApiProperty({ + example: 1234567890, + description: '사용자의 카카오 ID', + }) readonly kakaoId: number; + + @ApiProperty({ + example: '박근형', + description: '사용자의 이름', + }) readonly name: string; + + @ApiProperty({ + example: 'w8385@kakao.com', + description: '사용자의 이메일', + }) readonly email: string; } diff --git a/src/user/user.controller.ts b/src/user/user.controller.ts index f975628..28420ed 100644 --- a/src/user/user.controller.ts +++ b/src/user/user.controller.ts @@ -28,18 +28,21 @@ export class UserController { return this.userService.findAll(); } - @Get(':id') - findOne(@Param('id') id: string) { - return this.userService.findOne(+id); + @Get(':userId') + findOne(@Param('userId') userId: string) { + return this.userService.findOne(userId); } - @Patch(':id') - update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) { - return this.userService.update(+id, updateUserDto); + @Patch(':userId') + update( + @Param('userId') userId: string, + @Body() updateUserDto: UpdateUserDto, + ) { + return this.userService.update(userId, updateUserDto); } - @Delete(':id') - remove(@Param('id') id: string) { - return this.userService.remove(+id); + @Delete(':userId') + remove(@Param('userId') userId: string) { + return this.userService.remove(userId); } } diff --git a/src/user/user.service.ts b/src/user/user.service.ts index ccca61a..bb41f5f 100644 --- a/src/user/user.service.ts +++ b/src/user/user.service.ts @@ -13,18 +13,18 @@ export class UserService { } async findAll() { - return `This action returns all user`; + return await this.userRepository.findAll(); } - async findOne(id: number) { - return `This action returns a #${id} user`; + async findOne(userId: string) { + return await this.userRepository.findOne(userId); } - async update(id: number, updateUserDto: UpdateUserDto) { - return { id, updateUserDto }; + async update(userId: string, updateUserDto: UpdateUserDto) { + return { userId, updateUserDto }; } - async remove(id: number) { - return `This action removes a #${id} user`; + remove(userId: string) { + return this.userRepository.delete(userId); } } From 3414010335095ad36bfd83ee2d0a4fd1b70235ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EA=B7=BC=ED=98=95?= Date: Wed, 22 May 2024 18:05:32 +0900 Subject: [PATCH 08/13] =?UTF-8?q?fix:=20webhook=20request.body.toString()?= =?UTF-8?q?=20=EB=BB=97=EB=8A=94=20=EA=B2=BD=EC=9A=B0=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/filters/all-exception.filter.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/common/filters/all-exception.filter.ts b/src/common/filters/all-exception.filter.ts index ba101a9..a31195d 100644 --- a/src/common/filters/all-exception.filter.ts +++ b/src/common/filters/all-exception.filter.ts @@ -34,13 +34,9 @@ export class AllExceptionsFilter implements ExceptionFilter { const timestamp = new Date().toISOString(); webhook.error( - path, + `[${request.method}] ${path}`, timestamp, - 'request body: ' + - request.body.toString() + - '\n' + - 'error message: ' + - message.toString(), + 'error message: ' + message.toString(), ); httpAdapter.reply( From eb5450e0513aebc93664e582f0d813fa3a96d90d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EA=B7=BC=ED=98=95?= Date: Wed, 22 May 2024 18:08:21 +0900 Subject: [PATCH 09/13] feat: find User By KakaoId --- src/user/user.controller.ts | 5 +++++ src/user/user.repository.ts | 4 ++++ src/user/user.service.ts | 4 ++++ 3 files changed, 13 insertions(+) diff --git a/src/user/user.controller.ts b/src/user/user.controller.ts index 28420ed..44ed89f 100644 --- a/src/user/user.controller.ts +++ b/src/user/user.controller.ts @@ -33,6 +33,11 @@ export class UserController { return this.userService.findOne(userId); } + @Get('kakao/:kakaoId') + findOneByKakaoId(@Param('kakaoId') kakaoId: string) { + return this.userService.findOneByKakaoId(kakaoId); + } + @Patch(':userId') update( @Param('userId') userId: string, diff --git a/src/user/user.repository.ts b/src/user/user.repository.ts index ad38445..6175b3d 100644 --- a/src/user/user.repository.ts +++ b/src/user/user.repository.ts @@ -23,6 +23,10 @@ export class UserRepository { return this.userModel.findById(userId).exec(); } + findOneByKakaoId(kakaoId: string) { + return this.userModel.findOne({ kakaoId }).exec(); + } + update(userId: string, updateUserDto: CreateUserDto) { return this.userModel .findByIdAndUpdate(userId, updateUserDto, { new: true }) diff --git a/src/user/user.service.ts b/src/user/user.service.ts index bb41f5f..48a5c18 100644 --- a/src/user/user.service.ts +++ b/src/user/user.service.ts @@ -20,6 +20,10 @@ export class UserService { return await this.userRepository.findOne(userId); } + async findOneByKakaoId(kakaoId: string) { + return await this.userRepository.findOneByKakaoId(kakaoId); + } + async update(userId: string, updateUserDto: UpdateUserDto) { return { userId, updateUserDto }; } From 7bdfa49bf4e52bed2eee5e68980611b272d76d7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EA=B7=BC=ED=98=95?= Date: Thu, 23 May 2024 04:13:09 +0900 Subject: [PATCH 10/13] =?UTF-8?q?feat:=20JWT=20=EB=B0=9C=EA=B8=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 99 +++++++++++++++++++++++++++++++++++++ package.json | 1 + src/auth/auth.controller.ts | 6 +++ src/auth/auth.module.ts | 14 +++++- src/auth/auth.service.ts | 27 ++++++++++ src/auth/constants.ts | 7 +++ src/user/user.module.ts | 1 + src/user/user.repository.ts | 13 ++--- src/user/user.service.ts | 3 +- 9 files changed, 163 insertions(+), 8 deletions(-) create mode 100644 src/auth/constants.ts diff --git a/package-lock.json b/package-lock.json index 895f82a..930e5c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@nestjs/common": "^10.0.0", "@nestjs/config": "^3.2.1", "@nestjs/core": "^10.0.0", + "@nestjs/jwt": "^10.2.0", "@nestjs/platform-express": "^10.0.0", "@nestjs/swagger": "^7.3.1", "@types/xlsx-populate": "github:JanLoebel/types-xlsx-populate", @@ -1888,6 +1889,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/mapped-types": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.0.5.tgz", @@ -2416,6 +2429,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", @@ -3730,6 +3751,11 @@ "node": "*" } }, + "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", @@ -4549,6 +4575,14 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true }, + "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", @@ -7111,6 +7145,27 @@ "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/jszip": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", @@ -7122,6 +7177,25 @@ "setimmediate": "^1.0.5" } }, + "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/kareem": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.0.tgz", @@ -7264,6 +7338,11 @@ "resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz", "integrity": "sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==" }, + "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", @@ -7279,16 +7358,31 @@ "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==" }, + "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.isnil": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/lodash.isnil/-/lodash.isnil-4.0.0.tgz", "integrity": "sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng==" }, + "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.isundefined": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz", @@ -7306,6 +7400,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/lodash.reduce": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", diff --git a/package.json b/package.json index 5020e89..42b2ea6 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@nestjs/common": "^10.0.0", "@nestjs/config": "^3.2.1", "@nestjs/core": "^10.0.0", + "@nestjs/jwt": "^10.2.0", "@nestjs/platform-express": "^10.0.0", "@nestjs/swagger": "^7.3.1", "@types/xlsx-populate": "github:JanLoebel/types-xlsx-populate", diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts index bc57723..ffc29e8 100644 --- a/src/auth/auth.controller.ts +++ b/src/auth/auth.controller.ts @@ -52,4 +52,10 @@ export class AuthController { async kakaoUnlink(@Query('kakaoToken') kakaoToken: string) { return this.authService.kakaoUnlink(kakaoToken); } + + @Get('login') + @ApiQuery({ name: 'kakaoToken', required: true }) + async login(@Query('kakaoToken') kakaoToken: string) { + return this.authService.login(kakaoToken); + } } diff --git a/src/auth/auth.module.ts b/src/auth/auth.module.ts index 0848f97..79aeab6 100644 --- a/src/auth/auth.module.ts +++ b/src/auth/auth.module.ts @@ -1,12 +1,24 @@ import { HttpModule } from '@nestjs/axios'; import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; +import { JwtModule } from '@nestjs/jwt'; +import { UserModule } from '../user/user.module'; import { AuthController } from './auth.controller'; import { AuthService } from './auth.service'; +import { jwtConstants } from './constants'; @Module({ - imports: [ConfigModule, HttpModule], + imports: [ + ConfigModule, + HttpModule, + UserModule, + JwtModule.register({ + global: true, + secret: jwtConstants.secret, + signOptions: { expiresIn: '60s' }, + }), + ], controllers: [AuthController], providers: [AuthService], }) diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index 3bbacfa..e9e1fa7 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -1,13 +1,18 @@ import { HttpService } from '@nestjs/axios'; import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; +import { JwtService } from '@nestjs/jwt'; import { firstValueFrom } from 'rxjs'; +import { UserService } from '../user/user.service'; + @Injectable() export class AuthService { constructor( private readonly configService: ConfigService, private readonly httpService: HttpService, + private readonly userService: UserService, + private readonly jwtService: JwtService, ) {} kakaoCallback( @@ -99,4 +104,26 @@ export class AuthService { return data; } + + async login(kakaoToken: string) { + const kakaoInfo = await this.kakaoLogin(kakaoToken); + + const kakaoId = kakaoInfo.id; + const name = kakaoInfo.kakao_account.profile.nickname; + const email = kakaoInfo.kakao_account.email; + + let user = await this.userService.findOneByKakaoId(kakaoId); + if (!user) { + user = await this.userService.create({ + kakaoId, + name, + email, + }); + } + + const payload = { sub: user.id, name: user.name, iat: Date.now() }; + return { + access_token: await this.jwtService.signAsync(payload), + }; + } } diff --git a/src/auth/constants.ts b/src/auth/constants.ts new file mode 100644 index 0000000..37903da --- /dev/null +++ b/src/auth/constants.ts @@ -0,0 +1,7 @@ +import * as dotenv from 'dotenv'; +import * as process from 'node:process'; + +dotenv.config(); +export const jwtConstants = { + secret: process.env.JWT_SECRET, +}; diff --git a/src/user/user.module.ts b/src/user/user.module.ts index 636de33..3b7327e 100644 --- a/src/user/user.module.ts +++ b/src/user/user.module.ts @@ -10,5 +10,6 @@ import { UserService } from './user.service'; imports: [DatabaseModule], controllers: [UserController], providers: [UserService, ...userProviders, UserRepository], + exports: [UserService], }) export class UserModule {} diff --git a/src/user/user.repository.ts b/src/user/user.repository.ts index 6175b3d..305008e 100644 --- a/src/user/user.repository.ts +++ b/src/user/user.repository.ts @@ -2,6 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Model } from 'mongoose'; import { CreateUserDto } from './dto/create-user.dto'; +import { User } from './entities/user.entity'; import { UserInterface } from './interfaces/user.interfaces'; @Injectable() @@ -12,25 +13,25 @@ export class UserRepository { ) {} create(createUserDto: CreateUserDto) { - return this.userModel.create(createUserDto); + return this.userModel.create(createUserDto) as Promise; } findAll() { - return this.userModel.find().exec(); + return this.userModel.find().exec() as Promise; } - findOne(userId: string) { - return this.userModel.findById(userId).exec(); + findOne(userId: string): Promise { + return this.userModel.findById(userId).exec() as Promise; } findOneByKakaoId(kakaoId: string) { - return this.userModel.findOne({ kakaoId }).exec(); + return this.userModel.findOne({ kakaoId }).exec() as Promise; } update(userId: string, updateUserDto: CreateUserDto) { return this.userModel .findByIdAndUpdate(userId, updateUserDto, { new: true }) - .exec(); + .exec() as Promise; } delete(userId: string) { diff --git a/src/user/user.service.ts b/src/user/user.service.ts index 48a5c18..82e29cf 100644 --- a/src/user/user.service.ts +++ b/src/user/user.service.ts @@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common'; import { CreateUserDto } from './dto/create-user.dto'; import { UpdateUserDto } from './dto/update-user.dto'; +import { User } from './entities/user.entity'; import { UserRepository } from './user.repository'; @Injectable() @@ -16,7 +17,7 @@ export class UserService { return await this.userRepository.findAll(); } - async findOne(userId: string) { + async findOne(userId: string): Promise { return await this.userRepository.findOne(userId); } From a3bc2f779fa0456ca2a3fb1f1fff41931070b423 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EA=B7=BC=ED=98=95?= Date: Thu, 23 May 2024 04:34:20 +0900 Subject: [PATCH 11/13] =?UTF-8?q?feat:=20JWT=20=ED=99=95=EC=9D=B8=EC=9A=A9?= =?UTF-8?q?=20/user/me?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app.module.ts | 10 ++++---- src/auth/auth.guard.ts | 36 ++++++++++++++++++++++++++++ src/common/configs/swagger.config.ts | 7 ++++++ src/user/user.controller.ts | 12 +++++++++- 4 files changed, 58 insertions(+), 7 deletions(-) create mode 100644 src/auth/auth.guard.ts diff --git a/src/app.module.ts b/src/app.module.ts index 26b6253..a668e9d 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,9 +1,7 @@ import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; -import { APP_FILTER } from '@nestjs/core'; import { AuthModule } from './auth/auth.module'; -import { AllExceptionsFilter } from './common/filters/all-exception.filter'; import { DebugModule } from './debug/debug.module'; import { EventModule } from './event/event.module'; import { GroupModule } from './group/group.module'; @@ -23,10 +21,10 @@ import { UserModule } from './user/user.module'; UserModule, ], providers: [ - { - provide: APP_FILTER, - useClass: AllExceptionsFilter, - }, + // { + // provide: APP_FILTER, + // useClass: AllExceptionsFilter, + // }, ], }) export class AppModule {} diff --git a/src/auth/auth.guard.ts b/src/auth/auth.guard.ts new file mode 100644 index 0000000..75df5e3 --- /dev/null +++ b/src/auth/auth.guard.ts @@ -0,0 +1,36 @@ +import { + CanActivate, + ExecutionContext, + Injectable, + UnauthorizedException, +} from '@nestjs/common'; +import { JwtService } from '@nestjs/jwt'; +import { Request } from 'express'; + +import { jwtConstants } from './constants'; + +@Injectable() +export class AuthGuard implements CanActivate { + constructor(private jwtService: JwtService) {} + + async canActivate(context: ExecutionContext): Promise { + const request = context.switchToHttp().getRequest(); + const token = this.extractTokenFromHeader(request); + if (!token) { + throw new UnauthorizedException(); + } + try { + request['user'] = await this.jwtService.verifyAsync(token, { + secret: jwtConstants.secret, + }); + } catch { + throw new UnauthorizedException(); + } + return true; + } + + private extractTokenFromHeader(request: Request): string | undefined { + const [type, token] = request.headers.authorization?.split(' ') ?? []; + return type === 'Bearer' ? token : undefined; + } +} diff --git a/src/common/configs/swagger.config.ts b/src/common/configs/swagger.config.ts index c2df57c..b7d993c 100644 --- a/src/common/configs/swagger.config.ts +++ b/src/common/configs/swagger.config.ts @@ -60,6 +60,13 @@ const authSwaggerConfig = (app: INestApplication) => { }, }, }) + .addBearerAuth( + { + type: 'http', + scheme: 'bearer', + }, + 'Authorization', + ) .build(); const document = SwaggerModule.createDocument(app, config, { diff --git a/src/user/user.controller.ts b/src/user/user.controller.ts index 44ed89f..d270c33 100644 --- a/src/user/user.controller.ts +++ b/src/user/user.controller.ts @@ -6,9 +6,12 @@ import { Param, Patch, Post, + Request, + UseGuards, } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; +import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; +import { AuthGuard } from '../auth/auth.guard'; import { CreateUserDto } from './dto/create-user.dto'; import { UpdateUserDto } from './dto/update-user.dto'; import { UserService } from './user.service'; @@ -28,6 +31,13 @@ export class UserController { return this.userService.findAll(); } + @UseGuards(AuthGuard) + @ApiBearerAuth('Authorization') + @Get('me') + me(@Request() req: any) { + return req.user; + } + @Get(':userId') findOne(@Param('userId') userId: string) { return this.userService.findOne(userId); From 417a1acadcf9a4ea1b6928c7d6329286848cb7e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EA=B7=BC=ED=98=95?= Date: Fri, 24 May 2024 00:22:13 +0900 Subject: [PATCH 12/13] feat: signout --- src/auth/auth.controller.ts | 5 +++++ src/auth/auth.service.ts | 9 +++++++++ src/user/user.controller.ts | 5 +++++ src/user/user.repository.ts | 8 ++++++++ src/user/user.service.ts | 8 ++++++++ 5 files changed, 35 insertions(+) diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts index ffc29e8..56791b9 100644 --- a/src/auth/auth.controller.ts +++ b/src/auth/auth.controller.ts @@ -58,4 +58,9 @@ export class AuthController { async login(@Query('kakaoToken') kakaoToken: string) { return this.authService.login(kakaoToken); } + + @Get('signout') + async signout(@Query('kakaoToken') kakaoToken: string) { + return this.authService.signout(kakaoToken); + } } diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index e9e1fa7..b80e2cf 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -126,4 +126,13 @@ export class AuthService { access_token: await this.jwtService.signAsync(payload), }; } + + async signout(kakaoToken: string) { + const { kakaoId } = await this.kakaoTokenInfo(kakaoToken); + + this.userService.removeOneByKakaoId(kakaoId); + await this.kakaoUnlink(kakaoToken); + + return { message: 'Successfully signed out' }; + } } diff --git a/src/user/user.controller.ts b/src/user/user.controller.ts index d270c33..2e35fb6 100644 --- a/src/user/user.controller.ts +++ b/src/user/user.controller.ts @@ -56,6 +56,11 @@ export class UserController { return this.userService.update(userId, updateUserDto); } + @Delete() + removeAll() { + return this.userService.removeAll(); + } + @Delete(':userId') remove(@Param('userId') userId: string) { return this.userService.remove(userId); diff --git a/src/user/user.repository.ts b/src/user/user.repository.ts index 305008e..53a36d5 100644 --- a/src/user/user.repository.ts +++ b/src/user/user.repository.ts @@ -34,7 +34,15 @@ export class UserRepository { .exec() as Promise; } + deleteAll() { + this.userModel.deleteMany().exec().then(); + } + delete(userId: string) { this.userModel.findByIdAndDelete(userId).exec().then(); } + + deleteOneByKakaoId(kakaoId: string) { + this.userModel.findOneAndDelete({ kakaoId }).exec().then(); + } } diff --git a/src/user/user.service.ts b/src/user/user.service.ts index 82e29cf..281dde8 100644 --- a/src/user/user.service.ts +++ b/src/user/user.service.ts @@ -29,7 +29,15 @@ export class UserService { return { userId, updateUserDto }; } + removeAll() { + return this.userRepository.deleteAll(); + } + remove(userId: string) { return this.userRepository.delete(userId); } + + removeOneByKakaoId(kakaoId: string) { + return this.userRepository.deleteOneByKakaoId(kakaoId); + } } From b192f6cda09c52cf5aa2e72c818905f23920d405 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EA=B7=BC=ED=98=95?= Date: Fri, 24 May 2024 02:41:47 +0900 Subject: [PATCH 13/13] =?UTF-8?q?feat:=20/group/{groupId}/member=20memberI?= =?UTF-8?q?nfo=20=EC=86=8D=EC=84=B1=20=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/group/group.service.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/group/group.service.ts b/src/group/group.service.ts index dbcbcfd..22f743d 100644 --- a/src/group/group.service.ts +++ b/src/group/group.service.ts @@ -124,9 +124,16 @@ export class GroupService { } async getMembers(groupId: string) { - return this.groupRepository.findOne(groupId).then((group) => { - return this.getAllMembers(group.members); - }); + const group = await this.groupRepository.findOne(groupId); + const members = await this.getAllMembers(group.members); + const memberInfo = []; + if (group.members.length > 0) { + for (const info in members[0].memberInfo) { + memberInfo.push(info); + } + } + + return { memberInfo, members }; } async getEvents(groupId: string) {