From 61284f349af58315afb3ff75ea98399d025a3293 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=B1=EC=A7=80=EC=9B=90?= Date: Thu, 17 Jul 2025 20:27:09 +0900 Subject: [PATCH 1/3] =?UTF-8?q?SCRUM-220=20:=20=EA=B2=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85(=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/user/dto/create-guest.dto.ts | 9 +++++++++ src/user/entity/user.entity.ts | 4 ++-- src/user/user.controller.ts | 8 ++++++++ src/user/user.service.ts | 31 +++++++++++++++++++++++++++++++ 4 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 src/user/dto/create-guest.dto.ts diff --git a/src/user/dto/create-guest.dto.ts b/src/user/dto/create-guest.dto.ts new file mode 100644 index 0000000..a009d67 --- /dev/null +++ b/src/user/dto/create-guest.dto.ts @@ -0,0 +1,9 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class CreateGuestDto { + @ApiProperty({ + example: 'guest123', + description: '게스트 닉네임', + }) + userName: string; +} \ No newline at end of file diff --git a/src/user/entity/user.entity.ts b/src/user/entity/user.entity.ts index 7f38268..6fe1910 100644 --- a/src/user/entity/user.entity.ts +++ b/src/user/entity/user.entity.ts @@ -14,8 +14,8 @@ export class User { @Column({ name: 'email', unique: true, nullable: false }) email: string; - @Column({ name: 'password', nullable: true }) - password: string; + @Column({ name: 'password', nullable: true, type: 'varchar', length: 100 }) + password: string | null; @Column({ name: 'created_at', type: 'timestamp' }) createdAt: Date; diff --git a/src/user/user.controller.ts b/src/user/user.controller.ts index 44eb9dd..e968706 100644 --- a/src/user/user.controller.ts +++ b/src/user/user.controller.ts @@ -25,6 +25,7 @@ import { OAuthCallbackDto } from './dto/oauth_callback_dto.dto'; import { JwtAuthGuard } from 'src/auth/jwt.guard'; import { AuthRequest } from 'src/interface/AuthRequest.interface'; import { SignedCookies } from 'src/interface/SignedCookies.interface'; +import { CreateGuestDto } from './dto/create-guest.dto'; @ApiTags('api/user') @Controller('api/user') @@ -74,6 +75,13 @@ export class UserController { } } + @Post('signup') + @ApiOperation({ summary: '게스트 회원가입(로그인) API' }) + async guestSignUp(@Body() createGuestDto: CreateGuestDto) { + const result = await this.userService.guestSignUp(createGuestDto.userName); + return result; + } + @Get('info') @ApiOperation({ summary: '마이페이지 API' }) @UseGuards(JwtAuthGuard) diff --git a/src/user/user.service.ts b/src/user/user.service.ts index 41dbf52..c41ac55 100644 --- a/src/user/user.service.ts +++ b/src/user/user.service.ts @@ -11,6 +11,7 @@ import { AuthService } from 'src/auth/auth.service'; import { JwtService } from '@nestjs/jwt'; import Redis from 'ioredis'; import { JwtPayload } from '../interface/JwtPaylod.interface'; +import { v4 as uuidv4 } from 'uuid'; interface CommonTokenResponse { access_token: string; @@ -250,4 +251,34 @@ export class UserService { throw new NotFoundException('로그아웃 실패: 레디스에서 토큰 삭제 실패'); } } + + async guestSignUp(userName: string) { + // uuid로 이메일 생성 + const uuid = uuidv4(); + const email = `${uuid}@guest.local`; + // 이메일 중복 체크 + const exist = await this.userRepository.findOne({ where: { email } }); + if (exist) { + throw new Error('이미 존재하는 이메일입니다.'); + } + const now = new Date(); + const newUser = this.userRepository.create({ + email: email, + password: null, + createdAt: now, + updatedAt: now, + userName: userName, + }); + const saved: User = await this.userRepository.save(newUser); + const token = await this.authService.generateJWT(saved.id, saved.userName); + return { + isSuccess: true, + code: '200', + message: '요청에 성공하였습니다.', + createdAt: now.toISOString(), + data: 'Join Success', + access_token: token.access_token, + refresh_token: token.refresh_token, + }; + } } From b46e3a4c2596a05da3c1a936f9850501fe045455 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=B1=EC=A7=80=EC=9B=90?= Date: Thu, 17 Jul 2025 20:52:29 +0900 Subject: [PATCH 2/3] =?UTF-8?q?SCRUM-222=20:=20=EA=B2=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85(=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8)=20=EC=8B=9C=20=ED=86=A0=ED=81=B0=20=EC=A0=84?= =?UTF-8?q?=EB=8B=AC=20=EB=B0=A9=EC=8B=9D=20OAuth=EC=99=80=20=EB=8F=99?= =?UTF-8?q?=EC=9D=BC=ED=95=98=EA=B2=8C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/user/user.controller.ts | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/user/user.controller.ts b/src/user/user.controller.ts index e968706..6be7ffd 100644 --- a/src/user/user.controller.ts +++ b/src/user/user.controller.ts @@ -77,9 +77,23 @@ export class UserController { @Post('signup') @ApiOperation({ summary: '게스트 회원가입(로그인) API' }) - async guestSignUp(@Body() createGuestDto: CreateGuestDto) { + async guestSignUp( + @Body() createGuestDto: CreateGuestDto, + @Res({ passthrough: true }) res: Response + ) { const result = await this.userService.guestSignUp(createGuestDto.userName); - return result; + // access_token을 헤더로, refresh_token을 쿠키로 내려줌 (OAuth와 동일) + res.setHeader('Authorization', `Bearer ${result.access_token}`); + res.cookie('refresh_token', result.refresh_token, { + httpOnly: true, + secure: true, + sameSite: 'none', + maxAge: 1 * 24 * 60 * 60 * 1000, // 1일 + signed: true, + }); + // 응답 바디에는 토큰 정보 포함하지 않고, 나머지 정보만 반환 + const { access_token, refresh_token, ...rest } = result; + return rest; } @Get('info') From e45f2cb96d4a1cdc13ec625a6046f5deb321e9d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=B1=EC=A7=80=EC=9B=90?= Date: Thu, 17 Jul 2025 22:09:19 +0900 Subject: [PATCH 3/3] =?UTF-8?q?SCRUM-225=20:=20=EA=B8=B0=EB=B3=B8=20=20?= =?UTF-8?q?=EC=BF=A8=EB=8B=A4=EC=9A=B4=20=EC=8B=9C=EA=B0=84=203=EC=B4=88?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/canvas/canvas.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/canvas/canvas.service.ts b/src/canvas/canvas.service.ts index 07e379a..a383d87 100644 --- a/src/canvas/canvas.service.ts +++ b/src/canvas/canvas.service.ts @@ -420,8 +420,8 @@ export class CanvasService { }): Promise { // 캔버스 타입 조회 const canvasType = await this.getCanvasType(canvas_id); - // 게임 모드면 쿨다운 1초, 그 외는 10초 - const cooldownSeconds = canvasType === 'game_calculation' ? 1 : 10; + // 게임 모드면 쿨다운 1초, 그 외는 3초 + const cooldownSeconds = canvasType === 'game_calculation' ? 1 : 3; console.log( `[CanvasService] 쿨다운 설정: canvasType=${canvasType}, cooldownSeconds=${cooldownSeconds}` );