diff --git a/src/auth/jwt/jwt.strategy.ts b/src/auth/jwt/jwt.strategy.ts deleted file mode 100644 index 2d08d56..0000000 --- a/src/auth/jwt/jwt.strategy.ts +++ /dev/null @@ -1,26 +0,0 @@ -// jwt.strategy.ts -import { Injectable } from "@nestjs/common"; -import { PassportStrategy } from "@nestjs/passport"; -import { ExtractJwt, Strategy } from "passport-jwt"; -import { UserService } from "../../modules/user/user.service"; -import { UnauthorizedException } from "@nestjs/common"; - -@Injectable() -export class JwtStrategy extends PassportStrategy(Strategy) { - constructor(private readonly userService: UserService) { - super({ - jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), - ignoreExpiration: false, - secretOrKey: process.env.JWT_SECRET, - }); - } - - async validate(payload: any) { - const Id = payload.Id; - const user = await this.userService.getUserById(Id); - if (!user) { - throw new UnauthorizedException(); - } - return user; - } -} diff --git a/src/auth/strategy/jwt.strategy.ts b/src/auth/strategy/jwt.strategy.ts new file mode 100644 index 0000000..5e0a554 --- /dev/null +++ b/src/auth/strategy/jwt.strategy.ts @@ -0,0 +1,29 @@ +// jwt.strategy.ts +import { Injectable } from '@nestjs/common'; +import { PassportStrategy } from '@nestjs/passport'; +import { ExtractJwt, Strategy } from 'passport-jwt'; +import { UserService } from '../../modules/user/user.service'; +import { UnauthorizedException } from '@nestjs/common'; + +@Injectable() +export class JwtStrategy extends PassportStrategy(Strategy) { + constructor(private readonly userService: UserService) { + super({ + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + ignoreExpiration: false, + secretOrKey: process.env.JWT_SECRET, + }); + } + + async validate(payload: any) { + const id = payload.id; + if (!id) { + throw new UnauthorizedException(); + } + const user = await this.userService.getUserById(id); + if (!user) { + throw new UnauthorizedException(); + } + return user; + } +} diff --git a/src/modules/auth/auth.controller.ts b/src/modules/auth/auth.controller.ts index ebaa14f..8cf1556 100644 --- a/src/modules/auth/auth.controller.ts +++ b/src/modules/auth/auth.controller.ts @@ -10,32 +10,32 @@ import { Res, Req, UseGuards, -} from "@nestjs/common"; -import { ApiTags, ApiResponse, ApiOperation, ApiHeader } from "@nestjs/swagger"; -import { CreateTokenRequestDto } from "./dto/create-token-request.dto"; -import { CreateTokenResponseDto } from "./dto/create-token-response.dto"; -import { RefreshTokenRequestDto } from "./dto/refresh-token-request.dto"; -import { RefreshTokenResponseDto } from "./dto/refresh-token-response.dto"; -import { AuthService } from "./auth.service"; -import { Response } from "express"; -import { KakaoAuthGuard } from "src/auth/guard/kakao.auth.guard"; -import { SocialLoginRequestDto } from "./dto/social-login-request.dto"; -import { GoogleAuthGuard } from "src/auth/guard/google.auth.guard"; -import { CreateUserInfoResponseDto } from "../user/dto/create-user-info-response.dto"; +} from '@nestjs/common'; +import { ApiTags, ApiResponse, ApiOperation, ApiHeader } from '@nestjs/swagger'; +import { CreateTokenRequestDto } from './dto/create-token-request.dto'; +import { CreateTokenResponseDto } from './dto/create-token-response.dto'; +import { RefreshTokenRequestDto } from './dto/refresh-token-request.dto'; +import { RefreshTokenResponseDto } from './dto/refresh-token-response.dto'; +import { AuthService } from './auth.service'; +import { Response } from 'express'; +import { KakaoAuthGuard } from 'src/auth/guard/kakao.auth.guard'; +import { SocialLoginRequestDto } from './dto/social-login-request.dto'; +import { GoogleAuthGuard } from 'src/auth/guard/google.auth.guard'; +import { CreateUserInfoResponseDto } from '../user/dto/create-user-info-response.dto'; -@ApiTags("auth") -@Controller({ path: "auth" }) +@ApiTags('auth') +@Controller({ path: 'auth' }) export class AuthController { constructor(private readonly authService: AuthService) {} - @Post("token") - @ApiOperation({ summary: "로그인", description: "토큰 발급" }) + @Post('token') + @ApiOperation({ summary: '로그인', description: '토큰 발급' }) @ApiResponse({ status: 201, - description: "Created", + description: 'Created', type: CreateTokenResponseDto, }) - @ApiResponse({ status: 400, description: "Bad request" }) + @ApiResponse({ status: 400, description: 'Bad request' }) @HttpCode(HttpStatus.CREATED) async createToken( @Body() createTokenInfoDto: CreateTokenRequestDto, @@ -43,25 +43,29 @@ export class AuthController { ) { const tokens = await this.authService.create(createTokenInfoDto); - res.cookie("access_token", tokens.access_token, { + res.cookie('access_token', tokens.access_token, { httpOnly: true, expires: new Date(Date.now() + 15 * 60 * 1000), // 1h }); - res.cookie("refresh_token", tokens.refresh_token, { + res.cookie('refresh_token', tokens.refresh_token, { httpOnly: true, expires: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), // 30d }); - res.setHeader("access_token", tokens.access_token); - res.setHeader("refresh_token", tokens.refresh_token); - return res.status(HttpStatus.CREATED).send(); + res.setHeader('access_token', tokens.access_token); + res.setHeader('refresh_token', tokens.refresh_token); + + res.status(HttpStatus.CREATED).send(); } - @Post("token/refresh") - @ApiOperation({ summary: "토큰 재발급", description: "토큰 재발급" }) + @Post('token/refresh') + @ApiOperation({ + summary: '토큰 재발급', + description: '토큰 재발급', + }) @ApiResponse({ status: 201, - description: "Created", + description: 'Created', type: RefreshTokenResponseDto, }) @@ -73,54 +77,55 @@ export class AuthController { ) { const tokens = await this.authService.refreshToken(refreshTokenInfoDto); - res.cookie("access_token", tokens.access_token, { + res.cookie('access_token', tokens.access_token, { httpOnly: true, expires: new Date(Date.now() + 15 * 60 * 1000), // 1h }); - res.cookie("refresh_token", tokens.refresh_token, { + res.cookie('refresh_token', tokens.refresh_token, { httpOnly: true, expires: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), // 30d }); - res.setHeader("access_token", tokens.access_token); - res.setHeader("refresh_token", tokens.refresh_token); - return res.status(HttpStatus.CREATED).send(); + res.setHeader('access_token', tokens.access_token); + res.setHeader('refresh_token', tokens.refresh_token); + + res.status(HttpStatus.CREATED).send(); } @ApiOperation({ - summary: "카카오 소셜로그인", - description: "기가입 유저는 로그인, 신규 유저는 회원가입 진행", + summary: '카카오 소셜로그인', + description: '기가입 유저는 로그인, 신규 유저는 회원가입 진행', }) @ApiResponse({ status: 201, - description: "Created", + description: 'Created', }) @UseGuards(KakaoAuthGuard) - @Get("login/kakao") + @Get('login/kakao') async loginWithKakao( @Req() req: Request, @Res({ passthrough: true }) res: Response, ): Promise { - const user = req["user"] as SocialLoginRequestDto; + const user = req['user'] as SocialLoginRequestDto; return await this.authService.OAuthLogin(user); } @ApiOperation({ - summary: "구글 소셜로그인", - description: "기가입 유저는 로그인, 신규 유저는 회원가입 진행", + summary: '구글 소셜로그인', + description: '기가입 유저는 로그인, 신규 유저는 회원가입 진행', }) @ApiResponse({ status: 201, - description: "Created", + description: 'Created', }) @UseGuards(GoogleAuthGuard) - @Get("login/google") + @Get('login/google') async loginWithGoogle( @Req() req: Request, @Res({ passthrough: true }) res: Response, ): Promise { - const user = req["user"] as SocialLoginRequestDto; + const user = req['user'] as SocialLoginRequestDto; return await this.authService.OAuthLogin(user); } diff --git a/src/modules/auth/auth.module.ts b/src/modules/auth/auth.module.ts index fbdd542..2d566b5 100644 --- a/src/modules/auth/auth.module.ts +++ b/src/modules/auth/auth.module.ts @@ -1,16 +1,16 @@ -import { Module } from "@nestjs/common"; -import { JwtModule } from "@nestjs/jwt"; -import { AuthService } from "./auth.service"; -import { AuthRepository } from "./auth.repository"; -import { AuthController } from "./auth.controller"; -import { DynamoDBModule } from "../../database/dynamodb/dynamodb.module"; -import { JwtStrategy } from "../../auth/jwt/jwt.strategy"; -import { PassportModule } from "@nestjs/passport"; -import { UserModule } from "../user/user.module"; -import { KakaoStrategy } from "src/auth/strategy/kakao.strategy"; -import { UserService } from "../user/user.service"; -import { UserRepository } from "../user/user.repository"; -import { GoogleStrategy } from "src/auth/strategy/google.strategy"; +import { Module } from '@nestjs/common'; +import { JwtModule } from '@nestjs/jwt'; +import { AuthService } from './auth.service'; +import { AuthRepository } from './auth.repository'; +import { AuthController } from './auth.controller'; +import { DynamoDBModule } from '../../database/dynamodb/dynamodb.module'; +import { JwtStrategy } from '../../auth/strategy/jwt.strategy'; +import { PassportModule } from '@nestjs/passport'; +import { UserModule } from '../user/user.module'; +import { KakaoStrategy } from 'src/auth/strategy/kakao.strategy'; +import { UserService } from '../user/user.service'; +import { UserRepository } from '../user/user.repository'; +import { GoogleStrategy } from 'src/auth/strategy/google.strategy'; @Module({ imports: [ @@ -18,7 +18,7 @@ import { GoogleStrategy } from "src/auth/strategy/google.strategy"; PassportModule, JwtModule.register({ secret: process.env.JWT_SECRET, - signOptions: { expiresIn: "1h" }, + signOptions: { expiresIn: '1h' }, }), UserModule, ], diff --git a/src/modules/auth/auth.repository.ts b/src/modules/auth/auth.repository.ts index 00cca1b..1546fda 100644 --- a/src/modules/auth/auth.repository.ts +++ b/src/modules/auth/auth.repository.ts @@ -1,21 +1,20 @@ -import { Injectable, Inject } from "@nestjs/common"; -import { DynamoDBDocument } from "@aws-sdk/lib-dynamodb"; +import { Injectable, Inject } from '@nestjs/common'; +import { DynamoDBDocument } from '@aws-sdk/lib-dynamodb'; @Injectable() export class AuthRepository { private tableName: string; - constructor(@Inject("DYNAMODB") private dynamoDb: DynamoDBDocument) { - const env = process.env.NODE_ENV; - this.tableName = (env === "dev" ? "Dev_" : "") + "UserInfoTest"; + constructor(@Inject('DYNAMODB') private dynamoDb: DynamoDBDocument) { + this.tableName = process.env.AWS_DYNAMODB_TABLE_NAME; } async findOneByEmail(email: string): Promise { const result = await this.dynamoDb.query({ TableName: this.tableName, - IndexName: "email-index", - KeyConditionExpression: "email = :email", + IndexName: 'email-index', + KeyConditionExpression: 'email = :email', ExpressionAttributeValues: { - ":email": email, + ':email': email, }, }); diff --git a/src/modules/auth/auth.service.ts b/src/modules/auth/auth.service.ts index 20f258f..1a7c179 100644 --- a/src/modules/auth/auth.service.ts +++ b/src/modules/auth/auth.service.ts @@ -1,15 +1,15 @@ -import { Injectable } from "@nestjs/common"; -import { UnauthorizedException } from "@nestjs/common"; -import { CreateTokenRequestDto } from "./dto/create-token-request.dto"; -import { RefreshTokenRequestDto } from "./dto/refresh-token-request.dto"; -import { AuthRepository } from "./auth.repository"; -import { JwtService } from "@nestjs/jwt"; -import * as bcrypt from "bcrypt"; -import { CreateTokenResponseDto } from "./dto/create-token-response.dto"; -import { RefreshTokenResponseDto } from "./dto/refresh-token-response.dto"; -import { SocialLoginRequestDto } from "./dto/social-login-request.dto"; -import { UserService } from "../user/user.service"; -import { CreateUserInfoResponseDto } from "../user/dto/create-user-info-response.dto"; +import { Injectable } from '@nestjs/common'; +import { UnauthorizedException } from '@nestjs/common'; +import { CreateTokenRequestDto } from './dto/create-token-request.dto'; +import { RefreshTokenRequestDto } from './dto/refresh-token-request.dto'; +import { AuthRepository } from './auth.repository'; +import { JwtService } from '@nestjs/jwt'; +import * as bcrypt from 'bcrypt'; +import { CreateTokenResponseDto } from './dto/create-token-response.dto'; +import { RefreshTokenResponseDto } from './dto/refresh-token-response.dto'; +import { SocialLoginRequestDto } from './dto/social-login-request.dto'; +import { UserService } from '../user/user.service'; +import { CreateUserInfoResponseDto } from '../user/dto/create-user-info-response.dto'; @Injectable() export class AuthService { @@ -32,19 +32,20 @@ export class AuthService { userInfo: CreateTokenRequestDto, ): Promise { const user = await this.validateUser(userInfo.email, userInfo.password); + if (!user) { throw new UnauthorizedException( - "User not found or password does not match", + 'User not found or password does not match', ); } // JWT 토큰 생성 - const payload = { Id: user.Id, SortKey: user.SortKey }; + const payload = { id: user.PK }; return { access_token: this.jwtService.sign({ ...payload, isRefreshToken: false }), refresh_token: this.jwtService.sign( { ...payload, isRefreshToken: true }, - { expiresIn: "30d" }, + { expiresIn: '30d' }, ), }; } @@ -59,19 +60,19 @@ export class AuthService { payload = this.jwtService.verify(refreshToken); if (!payload.isRefreshToken) { - throw new UnauthorizedException("Invalid token"); + throw new UnauthorizedException('Invalid token'); } } catch (error) { - throw new UnauthorizedException("Invalid token"); + throw new UnauthorizedException('Invalid token'); } // JWT 토큰 생성 - payload = { Id: payload.Id, SortKey: payload.SortKey }; + payload = { id: payload.id }; return { access_token: this.jwtService.sign({ ...payload, isRefreshToken: false }), refresh_token: this.jwtService.sign( { ...payload, isRefreshToken: true }, - { expiresIn: "30d" }, + { expiresIn: '30d' }, ), }; } diff --git a/src/modules/test/dto/create-test-request.dto.ts b/src/modules/test/dto/create-test-request.dto.ts index 2f5c9a5..531bf51 100644 --- a/src/modules/test/dto/create-test-request.dto.ts +++ b/src/modules/test/dto/create-test-request.dto.ts @@ -6,51 +6,51 @@ import { IsIn, IsArray, IsOptional, -} from "class-validator"; -import { Type } from "class-transformer"; -import { ApiProperty } from "@nestjs/swagger"; +} from 'class-validator'; +import { Type } from 'class-transformer'; +import { ApiProperty } from '@nestjs/swagger'; export class CreateTestRequestDto { @IsOptional() @IsString() @ApiProperty({ - example: "422838ab-3a92-4e5f-914c-5eae24249a92", - description: "Id", + example: '422838ab-3a92-4e5f-914c-5eae24249a92', + description: 'id', }) - Id?: string; + id?: string; @IsString() - @IsIn(["beginner", "intermediate", "advanced"]) - @ApiProperty({ example: "beginner", description: "level" }) + @IsIn(['beginner', 'intermediate', 'advanced']) + @ApiProperty({ example: 'beginner', description: 'level' }) level: string; @IsInt() @Type(() => Number) @Min(1) @Max(30) - @ApiProperty({ example: 10, description: "total_count" }) + @ApiProperty({ example: 10, description: 'total_count' }) total_count?: number; @IsInt() @Type(() => Number) @Min(0) @Max(30) - @ApiProperty({ example: 5, description: "expected_count" }) + @ApiProperty({ example: 5, description: 'expected_count' }) expected_count?: number; @IsArray() @IsString({ each: true }) @ApiProperty({ - example: ["word1", "word2", "word3", "word4", "word5"], - description: "An array of total words for the test.", + example: ['word1', 'word2', 'word3', 'word4', 'word5'], + description: 'An array of total words for the test.', }) total_words: string[]; @IsArray() @IsString({ each: true }) @ApiProperty({ - example: ["word1", "word2"], - description: "An array of input words for the test.", + example: ['word1', 'word2'], + description: 'An array of input words for the test.', }) input_words: string[]; } diff --git a/src/modules/test/dto/create-test-response.dto.ts b/src/modules/test/dto/create-test-response.dto.ts index cd598bf..7100921 100644 --- a/src/modules/test/dto/create-test-response.dto.ts +++ b/src/modules/test/dto/create-test-response.dto.ts @@ -1,42 +1,42 @@ -import { ApiProperty } from "@nestjs/swagger"; +import { ApiProperty } from '@nestjs/swagger'; export class CreateTestResponseDto { @ApiProperty({ - example: "422838ab-3a92-4e5f-914c-5eae24249a92", - description: "The id of the user", + example: '422838ab-3a92-4e5f-914c-5eae24249a92', + description: 'The id of the user', }) - Id: string; + id: string; - @ApiProperty({ example: "beginner", description: "level" }) + @ApiProperty({ example: 'beginner', description: 'level' }) level: string; - @ApiProperty({ example: 0.5, description: "score" }) + @ApiProperty({ example: 0.5, description: 'score' }) score: number; - @ApiProperty({ example: 10, description: "total_count" }) + @ApiProperty({ example: 10, description: 'total_count' }) total_count?: number; - @ApiProperty({ example: 5, description: "expected_count" }) + @ApiProperty({ example: 5, description: 'expected_count' }) expected_count?: number; - @ApiProperty({ example: 5, description: "expected_count" }) + @ApiProperty({ example: 5, description: 'expected_count' }) correct_count?: number; @ApiProperty({ - example: ["word1", "word2", "word3", "word4", "word5"], - description: "An array of total words for the test.", + example: ['word1', 'word2', 'word3', 'word4', 'word5'], + description: 'An array of total words for the test.', }) total_words: string[]; @ApiProperty({ - example: ["word1", "word2"], - description: "An array of input words for the test.", + example: ['word1', 'word2'], + description: 'An array of input words for the test.', }) input_words: string[]; @ApiProperty({ - example: ["word1"], - description: "An array of correct words for the test.", + example: ['word1'], + description: 'An array of correct words for the test.', }) correct_words: string[]; } diff --git a/src/modules/test/test.repository.ts b/src/modules/test/test.repository.ts index 008614f..1614de2 100644 --- a/src/modules/test/test.repository.ts +++ b/src/modules/test/test.repository.ts @@ -7,12 +7,11 @@ import { calculateScoreAndCorrectWords } from 'src/core/functions/calculate-scor export class TestRepository { private tableName: string; constructor(@Inject('DYNAMODB') private dynamoDb: DynamoDBDocument) { - const env = process.env.NODE_ENV; - this.tableName = (env === 'dev' ? 'Dev_' : '') + 'UserInfoTest'; + this.tableName = process.env.AWS_DYNAMODB_TABLE_NAME; } async createTest(data: any): Promise { - let id = data.Id; + let id = data.id; if (!id) { id = uuidv4(); @@ -22,8 +21,8 @@ export class TestRepository { const createdAt = new Date().toISOString(); const category = 'test'; const item = { - Id: id, - SortKey: `Test#${createdAt}`, + PK: id, + SK: `Test#${createdAt}`, ...data, ...testResult, category, diff --git a/src/modules/test/test.service.ts b/src/modules/test/test.service.ts index f5f4689..fd7e107 100644 --- a/src/modules/test/test.service.ts +++ b/src/modules/test/test.service.ts @@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common'; import { NotFoundException, BadRequestException } from '@nestjs/common'; import { TestRepository } from './test.repository'; import { CreateTestRequestDto } from './dto/create-test-request.dto'; +import { CreateTestResponseDto } from './dto/create-test-response.dto'; @Injectable() export class TestService { @@ -9,7 +10,13 @@ export class TestService { async createTest(data: CreateTestRequestDto): Promise { const item = await this.testRepository.createTest(data); - const { SortKey, ...result } = item; - return result; + + const responseItem = { + id: item.PK, + ...item, + }; + delete responseItem.PK; + delete responseItem.SK; + return responseItem; } } diff --git a/src/modules/user/dto/create-user-info-request.dto.ts b/src/modules/user/dto/create-user-info-request.dto.ts index 58ec753..2d76b88 100644 --- a/src/modules/user/dto/create-user-info-request.dto.ts +++ b/src/modules/user/dto/create-user-info-request.dto.ts @@ -5,25 +5,25 @@ import { IsString, IsNumber, IsOptional, -} from "class-validator"; -import { ApiProperty } from "@nestjs/swagger"; +} from 'class-validator'; +import { ApiProperty } from '@nestjs/swagger'; export class CreateUserInfoRequestDto { @IsOptional() @IsString() @ApiProperty({ required: false, - example: "422838ab-3a92-4e5f-914c-5eae24249a92", - description: "Id", + example: '422838ab-3a92-4e5f-914c-5eae24249a92', + description: 'id', }) - Id?: string; + id?: string; @IsEmail() @IsNotEmpty() @ApiProperty({ required: true, - example: "john.doe@example.com", - description: "The email of the user", + example: 'john.doe@example.com', + description: 'The email of the user', }) email: string; @@ -31,8 +31,8 @@ export class CreateUserInfoRequestDto { @IsString() @ApiProperty({ required: true, - example: "Password@1234", - description: "The password of the user", + example: 'Password@1234', + description: 'The password of the user', }) password: string; @@ -40,8 +40,8 @@ export class CreateUserInfoRequestDto { @IsString() @ApiProperty({ required: false, - example: "JohnDoe", - description: "The nickname of the user", + example: 'JohnDoe', + description: 'The nickname of the user', }) nickname?: string; @@ -49,8 +49,8 @@ export class CreateUserInfoRequestDto { @IsString() @ApiProperty({ required: false, - example: "m", - description: "The gender of the user", + example: 'm', + description: 'The gender of the user', }) gender?: string; @@ -59,7 +59,7 @@ export class CreateUserInfoRequestDto { @ApiProperty({ required: false, example: 20, - description: "The age of the user", + description: 'The age of the user', }) age?: number; } diff --git a/src/modules/user/dto/create-user-info-response.dto.ts b/src/modules/user/dto/create-user-info-response.dto.ts index bb8478e..2836125 100644 --- a/src/modules/user/dto/create-user-info-response.dto.ts +++ b/src/modules/user/dto/create-user-info-response.dto.ts @@ -1,37 +1,37 @@ -import { ApiProperty } from "@nestjs/swagger"; +import { ApiProperty } from '@nestjs/swagger'; // password 필드는 포함하지 않습니다. export class CreateUserInfoResponseDto { @ApiProperty({ - example: "422838ab-3a92-4e5f-914c-5eae24249a92", - description: "The id of the user", + example: '422838ab-3a92-4e5f-914c-5eae24249a92', + description: 'The id of the user', }) - Id: string; + id: string; @ApiProperty({ - example: "john.doe@example.com", - description: "The email of the user", + example: 'john.doe@example.com', + description: 'The email of the user', }) email: string; @ApiProperty({ required: false, - example: "JohnDoe", - description: "The nickname of the user", + example: 'JohnDoe', + description: 'The nickname of the user', }) nickname?: string; @ApiProperty({ required: false, - example: "m", - description: "The gender of the user", + example: 'm', + description: 'The gender of the user', }) gender?: string; @ApiProperty({ required: false, example: 20, - description: "The age of the user", + description: 'The age of the user', }) age?: number; } diff --git a/src/modules/user/dto/get-user-info-response.dto.ts b/src/modules/user/dto/get-user-info-response.dto.ts index 4d8cc5a..e9438ce 100644 --- a/src/modules/user/dto/get-user-info-response.dto.ts +++ b/src/modules/user/dto/get-user-info-response.dto.ts @@ -1,37 +1,37 @@ -import { ApiProperty } from "@nestjs/swagger"; +import { ApiProperty } from '@nestjs/swagger'; // password 필드는 포함하지 않습니다. export class GetUserInfoResponseDto { @ApiProperty({ - example: "422838ab-3a92-4e5f-914c-5eae24249a92", - description: "The id of the user", + example: '422838ab-3a92-4e5f-914c-5eae24249a92', + description: 'The id of the user', }) - Id: string; + id: string; @ApiProperty({ - example: "john.doe@example.com", - description: "The email of the user", + example: 'john.doe@example.com', + description: 'The email of the user', }) email: string; @ApiProperty({ required: false, - example: "JohnDoe", - description: "The nickname of the user", + example: 'JohnDoe', + description: 'The nickname of the user', }) nickname?: string; @ApiProperty({ required: false, - example: "m", - description: "The gender of the user", + example: 'm', + description: 'The gender of the user', }) gender?: string; @ApiProperty({ required: false, example: 20, - description: "The age of the user", + description: 'The age of the user', }) age?: number; } diff --git a/src/modules/user/dto/get-user-test-response.dto.ts b/src/modules/user/dto/get-user-test-response.dto.ts index a9fa760..780e21b 100644 --- a/src/modules/user/dto/get-user-test-response.dto.ts +++ b/src/modules/user/dto/get-user-test-response.dto.ts @@ -1,69 +1,69 @@ -import { ApiProperty } from "@nestjs/swagger"; +import { ApiProperty } from '@nestjs/swagger'; export class Item { @ApiProperty({ - example: ["애플", "메타", "테슬라"], - description: "The total words", + example: ['애플', '메타', '테슬라'], + description: 'The total words', }) total_words: string[]; - @ApiProperty({ example: 3, description: "The total count" }) + @ApiProperty({ example: 3, description: 'The total count' }) total_count: number; @ApiProperty({ - example: ["애플", "메타", "테슬라"], - description: "The correct words", + example: ['애플', '메타', '테슬라'], + description: 'The correct words', }) correct_words: string[]; @ApiProperty({ - example: ["애플", "메타", "테슬라"], - description: "The input words", + example: ['애플', '메타', '테슬라'], + description: 'The input words', }) input_words: string[]; - @ApiProperty({ example: "advanced", description: "The level" }) + @ApiProperty({ example: 'advanced', description: 'The level' }) level: string; - @ApiProperty({ example: 1, description: "The expected count" }) + @ApiProperty({ example: 1, description: 'The expected count' }) expected_count: number; - @ApiProperty({ example: "test", description: "The category" }) + @ApiProperty({ example: 'test', description: 'The category' }) category: string; - @ApiProperty({ example: 0, description: "The score" }) + @ApiProperty({ example: 0, description: 'The score' }) score: number; @ApiProperty({ - example: "2024-03-30T01:44:00.232Z", - description: "The creation date", + example: '2024-03-30T01:44:00.232Z', + description: 'The creation date', }) createdAt: string; @ApiProperty({ - example: "20407a4c-2d79-4dd8-bd17-9271a367e96c", - description: "The ID", + example: '20407a4c-2d79-4dd8-bd17-9271a367e96c', + description: 'The ID', }) - Id: string; + id: string; @ApiProperty({ - example: "Test#2024-03-30T01:44:00.232Z", - description: "The SortKey", + example: 'Test#2024-03-30T01:44:00.232Z', + description: 'The SortKey', }) SortKey: string; } export class GetUserTestResponseDto { - @ApiProperty({ type: [Item], description: "The list of items" }) + @ApiProperty({ type: [Item], description: 'The list of items' }) items: Item[]; - @ApiProperty({ example: 1, description: "The count of items" }) + @ApiProperty({ example: 1, description: 'The count of items' }) count: number; @ApiProperty({ example: - "eyJJZCI6IjIwNDA3YTRjLTJkNzktNGRkOC1iZDE3LTkyNzFhMzY3ZTk2YyIsIlNvcnRLZXkiOiJUZXN0IzIwMjQtMDMtMzBUMDE6NDQ6MDAuMjMyWiJ9", - description: "The last evaluated key for pagination", + 'eyJJZCI6IjIwNDA3YTRjLTJkNzktNGRkOC1iZDE3LTkyNzFhMzY3ZTk2YyIsIlNvcnRLZXkiOiJUZXN0IzIwMjQtMDMtMzBUMDE6NDQ6MDAuMjMyWiJ9', + description: 'The last evaluated key for pagination', required: false, }) lastEvaluatedKey?: string; diff --git a/src/modules/user/user.controller.ts b/src/modules/user/user.controller.ts index 436109c..436141c 100644 --- a/src/modules/user/user.controller.ts +++ b/src/modules/user/user.controller.ts @@ -10,45 +10,46 @@ import { HttpStatus, UseGuards, Query, -} from "@nestjs/common"; +} from '@nestjs/common'; import { ApiTags, ApiResponse, ApiOperation, ApiBearerAuth, -} from "@nestjs/swagger"; -import { UserService } from "./user.service"; -import { AuthGuard } from "@nestjs/passport"; -import { CreateUserInfoRequestDto } from "./dto/create-user-info-request.dto"; -import { CreateUserInfoResponseDto } from "./dto/create-user-info-response.dto"; -import { GetUserInfoResponseDto } from "./dto/get-user-info-response.dto"; -import { UpdateUserInfoRequestDto } from "./dto/update-user-info-request.dto"; -import { UpdateUserInfoResponseDto } from "./dto/update-user-info-response.dto"; -import { GetUserTestQueryDto } from "./dto/get-user-test-query.dto"; -import { GetUserTestResponseDto } from "./dto/get-user-test-response.dto"; +} from '@nestjs/swagger'; +import { UserService } from './user.service'; +import { AuthGuard } from '@nestjs/passport'; +import { CreateUserInfoRequestDto } from './dto/create-user-info-request.dto'; +import { CreateUserInfoResponseDto } from './dto/create-user-info-response.dto'; +import { GetUserInfoResponseDto } from './dto/get-user-info-response.dto'; +import { UpdateUserInfoRequestDto } from './dto/update-user-info-request.dto'; +import { UpdateUserInfoResponseDto } from './dto/update-user-info-response.dto'; +import { GetUserTestQueryDto } from './dto/get-user-test-query.dto'; +import { GetUserTestResponseDto } from './dto/get-user-test-response.dto'; -@ApiTags("users") -@Controller({ path: "users" }) +@ApiTags('users') +@Controller({ path: 'users' }) export class UserController { constructor(private readonly userService: UserService) {} @Get() - @UseGuards(AuthGuard("jwt")) + @UseGuards(AuthGuard('jwt')) @ApiBearerAuth() - @ApiOperation({ summary: "유저 정보 조회", description: "" }) - @ApiResponse({ status: 200, description: "OK", type: GetUserInfoResponseDto }) + @ApiOperation({ summary: '유저 정보 조회', description: '' }) + @ApiResponse({ status: 200, description: 'OK', type: GetUserInfoResponseDto }) // @ApiResponse({ status: 403, description: 'Forbidden.'}) @HttpCode(HttpStatus.OK) async getUser(@Request() req) { - const id = req.user.Id; + const id = req.user.id; + return await this.userService.getUserById(id); } @Post() - @ApiOperation({ summary: "회원가입", description: "유저 정보 등록" }) + @ApiOperation({ summary: '회원가입', description: '유저 정보 등록' }) @ApiResponse({ status: 201, - description: "Created", + description: 'Created', type: CreateUserInfoResponseDto, }) // @ApiResponse({ status: 400, description: 'Bad request'}) @@ -58,12 +59,12 @@ export class UserController { } @Patch() - @UseGuards(AuthGuard("jwt")) + @UseGuards(AuthGuard('jwt')) @ApiBearerAuth() - @ApiOperation({ summary: "유저 정보 수정", description: "" }) + @ApiOperation({ summary: '유저 정보 수정', description: '' }) @ApiResponse({ status: 200, - description: "OK", + description: 'OK', type: UpdateUserInfoResponseDto, }) // @ApiResponse({ status: 400, description: 'Bad request'}) @@ -72,19 +73,19 @@ export class UserController { @Request() req, @Body() updateUserInfoDto: UpdateUserInfoRequestDto, ) { - const id = req.user.Id; + const id = req.user.id; return await this.userService.update(id, updateUserInfoDto); } - @Get("/test") - @UseGuards(AuthGuard("jwt")) + @Get('/test') + @UseGuards(AuthGuard('jwt')) @ApiBearerAuth() - @ApiOperation({ summary: "유저 테스트 정보 조회", description: "" }) - @ApiResponse({ status: 200, description: "OK", type: GetUserTestResponseDto }) + @ApiOperation({ summary: '유저 테스트 정보 조회', description: '' }) + @ApiResponse({ status: 200, description: 'OK', type: GetUserTestResponseDto }) // @ApiResponse({ status: 403, description: 'Forbidden.'}) @HttpCode(HttpStatus.OK) async geteUserTest(@Request() req: any, @Query() query: GetUserTestQueryDto) { - const id = req.user.Id; + const id = req.user.id; return await this.userService.getUserTest(id, query); } } diff --git a/src/modules/user/user.repository.ts b/src/modules/user/user.repository.ts index 74ff65e..c797ae6 100644 --- a/src/modules/user/user.repository.ts +++ b/src/modules/user/user.repository.ts @@ -1,23 +1,22 @@ -import { v4 as uuidv4 } from "uuid"; -import { Injectable, Inject } from "@nestjs/common"; -import { DynamoDBDocument } from "@aws-sdk/lib-dynamodb"; -import * as bcrypt from "bcrypt"; +import { v4 as uuidv4 } from 'uuid'; +import { Injectable, Inject } from '@nestjs/common'; +import { DynamoDBDocument } from '@aws-sdk/lib-dynamodb'; +import * as bcrypt from 'bcrypt'; @Injectable() export class UserRepository { private tableName: string; - constructor(@Inject("DYNAMODB") private dynamoDb: DynamoDBDocument) { - const env = process.env.NODE_ENV; - this.tableName = (env === "dev" ? "Dev_" : "") + "UserInfoTest"; + constructor(@Inject('DYNAMODB') private dynamoDb: DynamoDBDocument) { + this.tableName = process.env.AWS_DYNAMODB_TABLE_NAME; } async findOneById(id: string): Promise { + console.log('id:', id); const result = await this.dynamoDb.get({ - // TableName: this.tableName, TableName: this.tableName, Key: { - Id: id, - SortKey: `UserInfo#${id}`, + PK: id, + SK: `UserInfo#${id}`, }, }); const { Item, ...$metadata } = result; @@ -27,17 +26,17 @@ export class UserRepository { async findOneByEmail(email: string): Promise { const result = await this.dynamoDb.query({ TableName: this.tableName, - IndexName: "email-index", - KeyConditionExpression: "email = :email", + IndexName: 'email-index', + KeyConditionExpression: 'email = :email', ExpressionAttributeValues: { - ":email": email, + ':email': email, }, }); return result.Items ? result.Items[0] : null; } async create(userInfo: any): Promise { - let id = userInfo.Id; + let id = userInfo.id; if (!id) { id = uuidv4(); } @@ -45,8 +44,8 @@ export class UserRepository { const hashedPassword = await bcrypt.hash(userInfo.password, 10); const createdAt = new Date().toISOString(); const item = { - Id: id, - SortKey: `UserInfo#${id}`, + PK: id, + SK: `UserInfo#${id}`, ...userInfo, password: hashedPassword, createdAt, @@ -62,7 +61,7 @@ export class UserRepository { async update(id: string, userInfo: any): Promise { // Building the update expression - let updateExpression = "set"; + let updateExpression = 'set'; const ExpressionAttributeNames = {}; const ExpressionAttributeValues = {}; for (const property in userInfo) { @@ -75,13 +74,13 @@ export class UserRepository { const result = await this.dynamoDb.update({ TableName: this.tableName, Key: { - Id: id, - SortKey: `UserInfo#${id}`, + PK: id, + SK: `UserInfo#${id}`, }, UpdateExpression: updateExpression, ExpressionAttributeNames: ExpressionAttributeNames, ExpressionAttributeValues: ExpressionAttributeValues, - ReturnValues: "ALL_NEW", // return updated all data + ReturnValues: 'ALL_NEW', // return updated all data }); return result.Attributes; @@ -90,15 +89,14 @@ export class UserRepository { async findUserTest(id: string, limit: number, startKey?: any): Promise { const params = { TableName: this.tableName, - KeyConditionExpression: - "#id = :id and begins_with(#sortKey, :sortKeyPrefix)", + KeyConditionExpression: '#pk = :id and begins_with(#sk, :sortKeyPrefix)', ExpressionAttributeNames: { - "#id": "Id", - "#sortKey": "SortKey", + '#pk': 'PK', + '#sk': 'SK', }, ExpressionAttributeValues: { - ":id": id, - ":sortKeyPrefix": "Test#", + ':id': id, + ':sortKeyPrefix': 'Test#', }, Limit: limit, // pagination limit ExclusiveStartKey: startKey, // previous last key @@ -112,12 +110,12 @@ export class UserRepository { count: result.Count, lastEvaluatedKey: result.LastEvaluatedKey ? Buffer.from(JSON.stringify(result.LastEvaluatedKey)).toString( - "base64", + 'base64', ) : null, }; } catch (error) { - console.error("Server error", error); + console.error('Server error', error); throw error; } } diff --git a/src/modules/user/user.service.ts b/src/modules/user/user.service.ts index 0a50ea7..ff6e55c 100644 --- a/src/modules/user/user.service.ts +++ b/src/modules/user/user.service.ts @@ -15,8 +15,15 @@ export class UserService { if (!user) { throw new NotFoundException('user does not exists'); } - const { password, SortKey, ...result } = user; - return result; + + const responseItem = { + id: user.PK, + ...user, + }; + delete responseItem.PK; + delete responseItem.SK; + delete responseItem.password; + return responseItem; } async create( @@ -29,8 +36,15 @@ export class UserService { } const item = await this.usersRepository.create(userInfo); - const { password, SortKey, ...result } = item; - return result; + + const responseItem = { + id: item.PK, + ...item, + }; + delete responseItem.PK; + delete responseItem.SK; + delete responseItem.password; + return responseItem; } async update(id: string, userInfo: UpdateUserInfoRequestDto): Promise { @@ -41,8 +55,15 @@ export class UserService { } const user = await this.usersRepository.update(id, userInfo); - const { password, SortKey, ...result } = user; - return result; + + const responseItem = { + id: user.PK, + ...user, + }; + delete responseItem.PK; + delete responseItem.SK; + delete responseItem.password; + return responseItem; } async getUserTest(id: string, query: GetUserTestQueryDto): Promise {