Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion nest-cli.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"deleteOutDir": true
"deleteOutDir": true,
"plugins": ["@nestjs/swagger"]
}
}
2 changes: 1 addition & 1 deletion src/app.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getHello(): string {
return 'Hello ㅁㄴㅇㄹㅁㄴㅇㅁㄴㅇㄹ!';
return 'Hello Davinci Code Game World!';
}
}
91 changes: 84 additions & 7 deletions src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// auth.controller.ts

import {
Controller,
Post,
Expand All @@ -9,18 +11,22 @@ import {
Res,
Req,
} from '@nestjs/common';
import { Response } from 'express';
import { Request } from 'express';
import { Response, Request } from 'express';
import { JwtService } from '@nestjs/jwt';
import { UserService } from '../user/user.service';
import LoginUserDto from './dto/auth.dto';
import { RedisAuthGuard } from './auth.guard';
import { RedisService } from 'src/redis/redis.service';

import {
ApiTags,
ApiOperation,
ApiResponse,
ApiBody,
ApiBearerAuth,
} from '@nestjs/swagger';

import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
@ApiTags('auth')

@Controller('auth')
export class AuthController {
constructor(
Expand All @@ -30,6 +36,30 @@ export class AuthController {
) {}

@Post('login')
@ApiOperation({
summary: '로그인',
description: '이메일, 패스워드를 이용한 로그인',
})
@ApiResponse({
status: 200,
description:
'로그인 성공 시 Access Token(헤더), Refresh Token(쿠키)을 발급합니다.',
schema: {
example: {
accessToken: 'Bearer <JWT_TOKEN_HERE>',
},
},
})
@ApiResponse({
status: 401,
description: '이메일 또는 비밀번호가 잘못된 경우',
schema: {
example: {
statusCode: 401,
message: 'Invalid credentials',
},
},
})
async login(
@Body() loginDto: LoginUserDto,
@Res({ passthrough: true }) res: Response,
Expand All @@ -45,6 +75,7 @@ export class AuthController {
const payload = { userEmail: user.userEmail, userId: user.id };
const accessToken = this.jwtService.sign(payload, { expiresIn: '2h' });
const refreshToken = this.jwtService.sign(payload, { expiresIn: '7d' });

await this.redisService.set(`access:${user.userEmail}`, accessToken, 3600);
await this.redisService.set(
`refresh:${user.userEmail}`,
Expand All @@ -54,8 +85,8 @@ export class AuthController {

res.cookie('refreshToken', refreshToken, {
httpOnly: true, // JavaScript로 접근 불가
secure: true, // HTTPS에서만 동작 (로컬 개발 시 false로 설정)
sameSite: 'none', // 일반 로그인
secure: true, // HTTPS에서만 동작 (개발시엔 false로 설정)
sameSite: 'none',
maxAge: 7 * 24 * 60 * 60 * 1000, // 7일
});

Expand All @@ -66,17 +97,63 @@ export class AuthController {

@UseGuards(RedisAuthGuard)
@Get('profile')
@ApiOperation({
summary: '프로필 조회(Protected)',
description: '로그인이 필요한 프로필 조회 API',
})
@ApiBearerAuth() // Swagger에서 Bearer Token 헤더를 입력할 수 있도록 표시
@ApiResponse({
status: 200,
description: '정상적으로 접근한 경우',
schema: {
example: {
message: 'This is a protected route',
},
},
})
@ApiResponse({
status: 401,
description: '권한이 없거나 토큰이 만료된 경우',
schema: {
example: {
statusCode: 401,
message: 'Unauthorized',
},
},
})
getProfile() {
return { message: 'This is a protected route' };
}

@Post('refresh')
@ApiOperation({
summary: 'Access Token 갱신',
description: 'Refresh Token을 통해 새로운 Access Token을 발급받습니다.',
})
@ApiResponse({
status: 200,
description: '새로운 Access Token을 발급합니다.',
schema: {
example: {
accessToken: 'Bearer <NEW_ACCESS_TOKEN>',
},
},
})
@ApiResponse({
status: 401,
description: 'Refresh Token이 없거나 올바르지 않은 경우',
schema: {
example: {
statusCode: 401,
message: 'Invalid refresh token',
},
},
})
async refresh(
@Req() req: Request,
@Res({ passthrough: true }) res: Response,
) {
const refreshToken = req.cookies['refreshToken']; // 쿠키에서 RefreshToken 읽기

if (!refreshToken) {
throw new HttpException(
'Refresh token not found',
Expand Down
11 changes: 11 additions & 0 deletions src/auth/dto/auth.dto.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsEmail, Matches, IsNotEmpty } from 'class-validator';

export default class LoginUserDto {
@ApiProperty({
required: true,
example: 'test@email.com',
description: '이메일',
})
@IsEmail({}, { message: '이메일 형식이 틀렸습니다.' })
@IsNotEmpty()
userEmail: string;

@ApiProperty({
required: true,
example: 'teST11!!',
description: '비밀번호',
})
@IsString()
@IsNotEmpty()
@Matches(/^(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/, {
Expand Down
13 changes: 12 additions & 1 deletion src/gameRoom/dto/createGameRoom.dto.ts
Original file line number Diff line number Diff line change
@@ -1 +1,12 @@
export class CreateGameRoomDto {}
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsNotEmpty } from 'class-validator';

export class CreateGameRoomDto {
@ApiProperty({
example: 'My Awesome Room',
description: '방 이름',
})
@IsString()
@IsNotEmpty()
roomName: string;
}
11 changes: 11 additions & 0 deletions src/gameRoom/dto/createRoomResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { ApiProperty } from '@nestjs/swagger';
import { GameRoomDto } from './gameRoom.dto';
import { GameRoomUserDto } from './gameRoomUser.dto';

export class CreateRoomResponseDto {
@ApiProperty({ type: GameRoomDto })
room: GameRoomDto;

@ApiProperty({ type: GameRoomUserDto })
user: GameRoomUserDto;
}
25 changes: 21 additions & 4 deletions src/gameRoom/dto/gameRoom.dto.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,25 @@
// dto/gameRoom.dto.ts
import { ApiProperty } from '@nestjs/swagger';
import { IsNumber, IsString, IsDate } from 'class-validator';

import { IsString, IsNotEmpty } from 'class-validator';
export class GameRoomDto {
@ApiProperty({ example: 1, description: '게임 방 ID' })
@IsNumber()
id: number;

export class GameroomDto {
@ApiProperty({ example: '테스트 방입니다.', description: '방 이름' })
@IsString()
@IsNotEmpty()
name: string;
roomName: string;

@ApiProperty({ example: 2, description: '최대 인원 수' })
@IsNumber()
maxPlayers: number;

@ApiProperty({ example: 1, description: '현재 인원 수' })
@IsNumber()
currentCount: number;

@ApiProperty({ description: '방 생성 일시' })
@IsDate()
createdAt: Date;
}
21 changes: 21 additions & 0 deletions src/gameRoom/dto/gameRoomUser.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// dto/gameRoomUser.dto.ts
import { ApiProperty } from '@nestjs/swagger';
import { IsNumber, IsDate } from 'class-validator';

export class GameRoomUserDto {
@ApiProperty({ example: 1, description: 'GameRoomUser 테이블 PK' })
@IsNumber()
id: number;

@ApiProperty({ example: 10, description: '방 ID' })
@IsNumber()
roomId: number;

@ApiProperty({ example: 100, description: '유저 ID' })
@IsNumber()
userId: number;

@ApiProperty({ description: '방 참여 일시' })
@IsDate()
joinedAt: Date;
}
12 changes: 12 additions & 0 deletions src/gameRoom/dto/getRoomStatusRespose.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// dto/getRoomStatusResponse.dto.ts
import { ApiProperty } from '@nestjs/swagger';
import { GameRoomDto } from './gameRoom.dto';
import { GameRoomUserDto } from './gameRoomUser.dto';

export class GetRoomStatusResponseDto {
@ApiProperty({ type: GameRoomDto })
roomName: GameRoomDto;

@ApiProperty({ type: [GameRoomUserDto] })
users: GameRoomUserDto[];
}
4 changes: 0 additions & 4 deletions src/gameRoom/dto/updateGameRoom.dto.ts

This file was deleted.

Loading
Loading