Skip to content

Commit

Permalink
Merge pull request #78 from makevook/issue/76,77
Browse files Browse the repository at this point in the history
feat: 회원 정보 수정, 회원 탈퇴 API 추가
  • Loading branch information
seungyeop-lee authored Jun 11, 2024
2 parents 07b1bf7 + 4c63599 commit 804b7b7
Show file tree
Hide file tree
Showing 14 changed files with 268 additions and 13 deletions.
23 changes: 20 additions & 3 deletions api/src/main/java/vook/server/api/app/user/UserService.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@
import vook.server.api.app.user.data.OnboardingCommand;
import vook.server.api.app.user.data.RegisterCommand;
import vook.server.api.app.user.data.SignUpFromSocialCommand;
import vook.server.api.app.user.exception.AlreadyOnboardingException;
import vook.server.api.app.user.exception.AlreadyRegisteredException;
import vook.server.api.app.user.exception.NotReadyToOnboardingException;
import vook.server.api.app.user.exception.*;
import vook.server.api.app.user.repo.SocialUserRepository;
import vook.server.api.app.user.repo.UserInfoRepository;
import vook.server.api.app.user.repo.UserRepository;
Expand Down Expand Up @@ -49,6 +47,9 @@ public void register(RegisterCommand command) {
if (user.isRegistered()) {
throw new AlreadyRegisteredException();
}
if (user.isWithdrawn()) {
throw new WithdrawnUserException();
}

UserInfo userInfo = userInfoRepository.save(UserInfo.forRegisterOf(
command.getNickname(),
Expand All @@ -69,4 +70,20 @@ public void onboarding(OnboardingCommand command) {

user.onboarding(command.getFunnel(), command.getJob());
}

public void updateInfo(String uid, String nickname) {
User user = repository.findByUid(uid).orElseThrow();
if (!user.isRegistered()) {
throw new NotRegisteredException();
}
user.update(nickname);
}

public void withdraw(String uid) {
User user = repository.findByUid(uid).orElseThrow();
if (user.isWithdrawn()) {
return;
}
user.withdraw();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package vook.server.api.app.user.exception;

import vook.server.api.app.common.AppException;

public class NotRegisteredException extends AppException {
@Override
public String contents() {
return "NotRegistered";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package vook.server.api.app.user.exception;

import vook.server.api.app.common.AppException;

public class WithdrawnUserException extends AppException {
@Override
public String contents() {
return "WithdrawnUser";
}
}
16 changes: 15 additions & 1 deletion api/src/main/java/vook/server/api/model/user/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public class User {

private LocalDateTime lastUpdatedAt;

private LocalDateTime deletedAt;
private LocalDateTime withdrawnAt;

@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<SocialUser> socialUsers = new ArrayList<>();
Expand Down Expand Up @@ -76,4 +76,18 @@ public boolean isReadyToOnboarding() {
public boolean isRegistered() {
return status == UserStatus.REGISTERED;
}

public void update(String nickname) {
userInfo.update(nickname);
lastUpdatedAt = LocalDateTime.now();
}

public void withdraw() {
this.status = UserStatus.WITHDRAWN;
this.withdrawnAt = LocalDateTime.now();
}

public boolean isWithdrawn() {
return status == UserStatus.WITHDRAWN;
}
}
4 changes: 4 additions & 0 deletions api/src/main/java/vook/server/api/model/user/UserInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,8 @@ public void addOnboardingInfo(Funnel funnel, Job job) {
this.funnel = funnel;
this.job = job;
}

public void update(String nickname) {
this.nickname = nickname;
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package vook.server.api.helper.jwt;
package vook.server.api.web.auth.app;

import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import vook.server.api.helper.jwt.JWTReader;
import vook.server.api.helper.jwt.JWTWriter;

@Component
public class JWTHelperProvider {
Expand All @@ -19,7 +21,7 @@ public void init() {
jwtReaderBuilder = new JWTReader.Builder(jwtSecret);
}

public JWTWriter builder() {
public JWTWriter writer() {
return jwtWriterBuilder.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import vook.server.api.helper.jwt.JWTHelperProvider;
import vook.server.api.helper.jwt.JWTReader;
import vook.server.api.web.auth.data.GeneratedToken;

Expand Down Expand Up @@ -50,15 +49,15 @@ public GeneratedToken refreshToken(String refreshToken) {
}

private String buildAccessToken(String uid) {
return jwtHelperProvider.builder()
return jwtHelperProvider.writer()
.withExpiredMs(1000L * 60 * accessTokenExpiredMinute)
.withClaim("category", "access")
.withClaim("uid", uid)
.jwtString();
}

private String buildRefreshToken(String uid) {
return jwtHelperProvider.builder()
return jwtHelperProvider.writer()
.withExpiredMs(1000L * 60 * refreshTokenExpiredMinute)
.withClaim("category", "refresh")
.withClaim("uid", uid)
Expand Down
35 changes: 34 additions & 1 deletion api/src/main/java/vook/server/api/web/routes/user/UserApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class UserApiUerInfoResponse extends CommonApiResponse<UserInfoResponse> {
description = """
비즈니스 규칙 위반 내용
- AlreadyRegistered: 이미 회원가입이 완료된 유저가 해당 API를 호출 할 경우
"""
- WithdrawnUser: 탈퇴한 유저가 해당 API를 호출 할 경우"""
)
@ApiResponses(value = {
@ApiResponse(
Expand Down Expand Up @@ -87,4 +87,37 @@ class UserApiUerInfoResponse extends CommonApiResponse<UserInfoResponse> {
),
})
CommonApiResponse<Void> onboarding(VookLoginUser user, UserOnboardingRequest request);

@Operation(
summary = "사용자 정보 수정",
security = {
@SecurityRequirement(name = "AccessToken")
},
description = """
비즈니스 규칙 위반 내용
- NotRegistered: 가입하지 않은 유저가 해당 API를 호출 할 경우"""
)
@ApiResponses(value = {
@ApiResponse(
responseCode = "400",
content = @Content(
mediaType = "application/json",
schema = @Schema(ref = ComponentRefConsts.Schema.COMMON_API_RESPONSE),
examples = {
@ExampleObject(name = "유효하지 않은 파라미터", ref = ComponentRefConsts.Example.INVALID_PARAMETER),
@ExampleObject(name = "비즈니스 규칙 위반", ref = ComponentRefConsts.Example.VIOLATION_BUSINESS_RULE)
}
)
),
})
CommonApiResponse<Void> updateInfo(VookLoginUser user, UserUpdateInfoRequest request);

@Operation(
summary = "회원 탈퇴",
security = {
@SecurityRequirement(name = "AccessToken")
},
description = "탈퇴된 회원에 대한 요청은 무시됩니다."
)
CommonApiResponse<Void> withdraw(VookLoginUser user);
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,23 @@ public CommonApiResponse<Void> onboarding(
service.onboarding(user, request);
return CommonApiResponse.ok();
}

@Override
@PutMapping("/info")
public CommonApiResponse<Void> updateInfo(
@AuthenticationPrincipal VookLoginUser user,
@Validated @RequestBody UserUpdateInfoRequest request
) {
service.updateInfo(user, request);
return CommonApiResponse.ok();
}

@Override
@PostMapping("/withdraw")
public CommonApiResponse<Void> withdraw(
@AuthenticationPrincipal VookLoginUser user
) {
service.withdraw(user);
return CommonApiResponse.ok();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package vook.server.api.web.routes.user;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;

@Data
public class UserUpdateInfoRequest {

@NotBlank
@Size(min = 1, max = 10)
private String nickname;
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,12 @@ public void register(VookLoginUser loginUser, UserRegisterRequest request) {
public void onboarding(VookLoginUser loginUser, UserOnboardingRequest request) {
userService.onboarding(request.toCommand(loginUser.getUid()));
}

public void updateInfo(VookLoginUser loginUser, UserUpdateInfoRequest request) {
userService.updateInfo(loginUser.getUid(), request.getNickname());
}

public void withdraw(VookLoginUser loginUser) {
userService.withdraw(loginUser.getUid());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ public User createCompletedOnboardingUser() {
return userService.findByUid(user.getUid()).orElseThrow();
}

public User createWithdrawnUser() {
User user = createCompletedOnboardingUser();
userService.withdraw(user.getUid());
return userService.findByUid(user.getUid()).orElseThrow();
}

public GeneratedToken createToken(User user) {
return tokenService.generateToken(user.getUid());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import vook.server.api.model.user.User;
import vook.server.api.testhelper.HttpEntityBuilder;
import vook.server.api.testhelper.IntegrationTestBase;
Expand All @@ -22,6 +23,7 @@

import static org.assertj.core.api.Assertions.assertThat;

@Transactional
class UserRestControllerTest extends IntegrationTestBase {

@MockBean
Expand Down Expand Up @@ -133,4 +135,59 @@ Collection<DynamicTest> registerError() {
})
);
}

@Test
@DisplayName("회원 정보 수정 - 정상")
void updateInfo() {
// given
User registeredUser = testDataCreator.createRegisteredUser();
GeneratedToken token = testDataCreator.createToken(registeredUser);

// when
var res = rest.exchange(
"/user/info",
HttpMethod.PUT,
new HttpEntityBuilder()
.header("Authorization", "Bearer " + token.getAccessToken())
.body(Map.of(
"nickname", "newName"
))
.build(),
String.class
);

// then
assertThat(res.getStatusCode()).isEqualTo(HttpStatus.OK);
}

@TestFactory
@DisplayName("회원 정보 수정 - 실패")
Collection<DynamicTest> updateInfoError() {
// given
User registeredUser = testDataCreator.createRegisteredUser();
GeneratedToken token = testDataCreator.createToken(registeredUser);

Function<Map<String, Object>, ResponseEntity<String>> restExchange = body -> rest.exchange(
"/user/info",
HttpMethod.PUT,
new HttpEntityBuilder()
.header("Authorization", "Bearer " + token.getAccessToken())
.body(body)
.build(),
String.class
);

return List.of(
DynamicTest.dynamicTest("닉네임 누락", () -> {
var res = restExchange.apply(Map.of());
assertThat(res.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
}),
DynamicTest.dynamicTest("닉네임 길이 제한 초과", () -> {
var res = restExchange.apply(Map.of(
"nickname", "12345678901"
));
assertThat(res.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
})
);
}
}
Loading

0 comments on commit 804b7b7

Please sign in to comment.