-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
users api 추가 #11
base: develop
Are you sure you want to change the base?
users api 추가 #11
Changes from all commits
4bf8501
286511c
f9e6d0e
516c70f
a6591e3
c1f4a43
65cea91
923ba6d
4109983
e9aad97
70807a8
aea59f8
373e6e4
9247f33
50661ea
f630f80
8c07eb7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 해당 Dto는 사용이 되지 않는 것 같은데 맞나욥? |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export class SuccessDto { | ||
success: true; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { IsAlphanumeric, IsNotEmpty, IsString, Length } from "class-validator"; | ||
import { ApiProperty } from "@nestjs/swagger"; | ||
|
||
export class CreateUserDto { | ||
@ApiProperty({ description: "유저 닉네임" }) | ||
@IsNotEmpty() | ||
@IsString() | ||
@IsAlphanumeric() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 지금 확인해보니까 명세서 상으로는 한글 닉네임이 안된다고 써있었군요..! 한글도 허용하면 좀 복잡해질까요? |
||
@Length(3, 15) | ||
nickname: string; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { ApiProperty } from "@nestjs/swagger"; | ||
import { | ||
IsAlphanumeric, | ||
IsBoolean, | ||
IsInt, | ||
IsNotEmpty, | ||
IsOptional, | ||
IsString, | ||
Length, | ||
ValidateNested, | ||
} from "class-validator"; | ||
|
||
class File { | ||
@IsString() | ||
@IsNotEmpty() | ||
uuid: string; | ||
} | ||
|
||
export class UpdateUserDto { | ||
@ApiProperty({ description: "유저 닉네임" }) | ||
@IsOptional() | ||
@IsString() | ||
@IsAlphanumeric() | ||
@Length(3, 15) | ||
nickname?: string; | ||
|
||
@ApiProperty({ description: "유저 생년" }) | ||
@IsOptional() | ||
@IsInt() | ||
birthYear?: number; | ||
|
||
@ApiProperty({ description: "유저 활성화 여부" }) | ||
@IsOptional() | ||
@IsBoolean() | ||
isActivated?: boolean; | ||
|
||
@ApiProperty({ description: "유저 프로필 사진 정보" }) | ||
@IsOptional() | ||
@ValidateNested({ each: true }) | ||
file?: Partial<File>; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import { File, User } from "@prisma/client"; | ||
import { ApiProperty } from "@nestjs/swagger"; | ||
|
||
export class UserResponseDto { | ||
constructor(user: User & { Files: Partial<File>[] }) { | ||
this.id = Number(user.id); | ||
this.email = user.email; | ||
this.nickname = user.nickname; | ||
this.defaultPhotoId = user.defaultPhotoId; | ||
this.file = user.Files[0]; | ||
this.birthYear = user.birthYear; | ||
this.isActivated = user.isActivated; | ||
this.createdAt = user.createdAt; | ||
this.updatedAt = user.updatedAt; | ||
} | ||
|
||
@ApiProperty() | ||
id: number; | ||
@ApiProperty({ required: false }) | ||
email?: string; | ||
@ApiProperty() | ||
nickname: string; | ||
@ApiProperty() | ||
defaultPhotoId: number; | ||
@ApiProperty({ required: false }) | ||
file?: Partial<File>; | ||
@ApiProperty({ required: false }) | ||
birthYear?: number; | ||
@ApiProperty() | ||
isActivated: boolean; | ||
@ApiProperty() | ||
createdAt: Date; | ||
@ApiProperty({ required: false }) | ||
updatedAt?: Date; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import { | ||
Body, | ||
Controller, | ||
Delete, | ||
Get, | ||
HttpCode, | ||
HttpStatus, | ||
Patch, | ||
Post, | ||
UseGuards, | ||
} from "@nestjs/common"; | ||
import { | ||
ApiBadRequestResponse, | ||
ApiCreatedResponse, | ||
ApiInternalServerErrorResponse, | ||
ApiOkResponse, | ||
ApiOperation, | ||
ApiTags, | ||
ApiUnauthorizedResponse, | ||
} from "@nestjs/swagger"; | ||
import { UsersService } from "src/modules/users/users.service"; | ||
import { CreateUserDto } from "src/modules/users/dtos/create-user.dto"; | ||
import { UserResponseDto } from "src/modules/users/dtos/user-response.dto"; | ||
import { UpdateUserDto } from "src/modules/users/dtos/update-user.dto"; | ||
import { Oauth2Guard } from "src/modules/auth/guards/oauth2.guard"; | ||
import { CurrentUser } from "src/common/decorators/current-user.decorator"; | ||
import { User } from "@prisma/client"; | ||
|
||
@ApiTags("유저 API") | ||
@Controller("users") | ||
export class UsersController { | ||
constructor(private readonly usersService: UsersService) {} | ||
|
||
@Post() | ||
@UseGuards(Oauth2Guard({ strict: true, isSignUp: true })) | ||
@ApiOperation({ | ||
summary: "회원가입", | ||
description: "카카오 로그인 정보를 통해 유저를 DB에 추가한다.", | ||
}) | ||
@ApiCreatedResponse({ type: UserResponseDto, description: "회원가입 성공" }) | ||
@ApiBadRequestResponse({ description: "이미 가입한 소셜 아이디" }) | ||
@ApiInternalServerErrorResponse({ description: "서버 내부 오류" }) | ||
async createUser( | ||
@Body() createUserDto: CreateUserDto, | ||
@CurrentUser() user: { oauthId: string; email: string } | ||
) { | ||
const userInfo = await this.usersService.createUser(createUserDto, user); | ||
return new UserResponseDto(userInfo); | ||
} | ||
|
||
@Get("me") | ||
@UseGuards(Oauth2Guard({ strict: true })) | ||
@ApiOperation({ | ||
summary: "본인 정보 조회", | ||
description: "본인의 유저 정보를 조회한다.", | ||
}) | ||
@ApiOkResponse({ type: UserResponseDto, description: "본인 정보 조회 성공" }) | ||
@ApiUnauthorizedResponse({ description: "인증 실패 (유효한 토큰이 아니거나 토큰 없음)" }) | ||
@ApiInternalServerErrorResponse({ description: "서버 내부 오류" }) | ||
async getMe(@CurrentUser() user: User) { | ||
const myInfo = await this.usersService.getMe(user); | ||
return new UserResponseDto(myInfo); | ||
} | ||
|
||
@Patch("me") | ||
@UseGuards(Oauth2Guard({ strict: true })) | ||
@ApiOperation({ | ||
summary: "본인 정보 수정", | ||
description: "본인의 유저 정보를 수정한다.", | ||
}) | ||
@ApiOkResponse({ type: UserResponseDto, description: "본인 정보 수정 성공" }) | ||
@ApiUnauthorizedResponse({ description: "인증 실패 (유효한 토큰이 아니거나 토큰 없음)" }) | ||
@ApiInternalServerErrorResponse({ description: "서버 내부 오류" }) | ||
async updateMe(@Body() updateUserDto: UpdateUserDto, @CurrentUser() user: User) { | ||
const updatedMyInfo = await this.usersService.updateMe(updateUserDto, user); | ||
return new UserResponseDto(updatedMyInfo); | ||
} | ||
|
||
@Delete("me") | ||
@HttpCode(HttpStatus.NO_CONTENT) | ||
@UseGuards(Oauth2Guard({ strict: true })) | ||
@ApiUnauthorizedResponse({ description: "인증 실패 (유효한 토큰이 아니거나 토큰 없음)" }) | ||
@ApiInternalServerErrorResponse({ description: "서버 내부 오류" }) | ||
async deleteMe(@CurrentUser() user: User) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 여기도 일괄적으로 @ApiOperation() 들어가면 좋을 것 같습니다! |
||
await this.usersService.deleteMe(user); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { Module } from "@nestjs/common"; | ||
import { UsersController } from "./users.controller"; | ||
import { UsersService } from "./users.service"; | ||
import { AuthModule } from "src/modules/auth/auth.module"; | ||
|
||
@Module({ | ||
imports: [AuthModule], | ||
controllers: [UsersController], | ||
providers: [UsersService], | ||
}) | ||
export class UsersModule {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
import { BadRequestException, Injectable } from "@nestjs/common"; | ||
import { CreateUserDto } from "src/modules/users/dtos/create-user.dto"; | ||
import { UpdateUserDto } from "src/modules/users/dtos/update-user.dto"; | ||
import { User } from "@prisma/client"; | ||
import { PrismaService } from "src/config/database/prisma.service"; | ||
|
||
@Injectable() | ||
export class UsersService { | ||
constructor(private readonly prisma: PrismaService) {} | ||
|
||
async createUser(createUserDto: CreateUserDto, user: { oauthId: string; email: string }) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. email을 받아보려고 시도하던 흔적이 남아있네요....ㅎ CurrentUser의 리턴 타입에도 없어서 날리겠습니다 |
||
return this.prisma.$transaction(async (tx) => { | ||
const existingUser = await tx.user.findUnique({ | ||
where: { | ||
oauthId: user.oauthId, | ||
}, | ||
}); | ||
if (existingUser) { | ||
throw new BadRequestException("이미 가입한 소셜아이디입니다."); | ||
} | ||
return tx.user.create({ | ||
data: { | ||
nickname: createUserDto.nickname, | ||
provider: "KAKAO", | ||
isActivated: true, | ||
defaultPhotoId: Math.floor(Math.random() / 6 + 1), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
로 수정해야할 것 같습니다! |
||
oauthId: user.oauthId, | ||
}, | ||
include: { | ||
Files: { | ||
select: { | ||
uuid: true, | ||
name: true, | ||
mimeType: true, | ||
size: true, | ||
createdAt: true, | ||
updatedAt: true, | ||
}, | ||
}, | ||
}, | ||
}); | ||
}); | ||
} | ||
|
||
async getMe(user: User) { | ||
return this.prisma.user.findUnique({ | ||
where: { oauthId: user.oauthId }, | ||
include: { | ||
Files: { | ||
select: { | ||
uuid: true, | ||
name: true, | ||
mimeType: true, | ||
size: true, | ||
createdAt: true, | ||
updatedAt: true, | ||
}, | ||
}, | ||
}, | ||
}); | ||
} | ||
|
||
async updateMe(updateUserDto: UpdateUserDto, user: User) { | ||
return await this.prisma.$transaction(async (tx) => { | ||
if (updateUserDto.file) { | ||
await tx.file.update({ | ||
where: { uuid: updateUserDto.file.uuid }, | ||
data: { userId: user.id }, | ||
}); | ||
await tx.file.deleteMany({ | ||
where: { userId: user.id, NOT: { uuid: updateUserDto.file.uuid } }, | ||
}); | ||
} | ||
R3gardless marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return tx.user.update({ | ||
where: { oauthId: user.oauthId }, | ||
data: { | ||
nickname: updateUserDto.nickname, | ||
birthYear: updateUserDto.birthYear, | ||
isActivated: updateUserDto.isActivated, | ||
Comment on lines
+77
to
+79
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 리퀘스트 바디에 값이 없으면 자동으로 업데이트를 패스해주는군요...! 하나 배워 갑니다 👍 |
||
}, | ||
include: { | ||
Files: { | ||
select: { | ||
uuid: true, | ||
name: true, | ||
mimeType: true, | ||
size: true, | ||
createdAt: true, | ||
updatedAt: true, | ||
}, | ||
}, | ||
}, | ||
}); | ||
}); | ||
} | ||
|
||
async deleteMe(user: User) { | ||
await this.prisma.user.delete({ where: { id: user.id } }); | ||
return true; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
소셜 로그인으로 변경되면서 email, password 는 삭제해도 되지 않을까 싶습니다.
그리고 gender가 API 전체에서 쓰이지 않았는데 처음 명세서 계획대로 가면 updateUser에서 수정 가능해야할 것 같습니다!