Skip to content

Commit

Permalink
[Refactor]: 소셜 회원가입 API 리팩토링
Browse files Browse the repository at this point in the history
Request body 검증성 추가
비즈니스 로직 캡슐화
에러 처리

Related to: #157
  • Loading branch information
dev-Crayon committed Jul 28, 2024
1 parent 0a8143e commit 5e96109
Show file tree
Hide file tree
Showing 13 changed files with 120 additions and 48 deletions.
4 changes: 4 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ dependencies {

}

test {
useJUnitPlatform()
}

tasks.named('bootBuildImage') {
builder = 'paketobuildpacks/builder-jammy-base:latest'
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
package io.sobok.SobokSobok.auth.application;

import io.sobok.SobokSobok.auth.application.util.UserServiceUtil;
import io.sobok.SobokSobok.auth.domain.Role;
import io.sobok.SobokSobok.auth.domain.SocialInfo;
import io.sobok.SobokSobok.auth.domain.User;
import io.sobok.SobokSobok.auth.infrastructure.UserRepository;
import io.sobok.SobokSobok.auth.ui.dto.SocialLoginRequest;
import io.sobok.SobokSobok.auth.ui.dto.SocialLoginResponse;
import io.sobok.SobokSobok.auth.ui.dto.SocialSignupRequest;
import io.sobok.SobokSobok.auth.ui.dto.SocialSignupResponse;
import io.sobok.SobokSobok.exception.ErrorCode;
import io.sobok.SobokSobok.exception.model.ConflictException;
import io.sobok.SobokSobok.security.jwt.Jwt;
import io.sobok.SobokSobok.security.jwt.JwtProvider;

Expand All @@ -23,39 +21,34 @@
@RequiredArgsConstructor
public class SocialService {

private final UserCreator userCreator;

private final UserRepository userRepository;

private final JwtProvider jwtProvider;

@Transactional
public SocialSignupResponse signup(SocialSignupRequest request) {
if (userRepository.existsBySocialInfoSocialId(request.socialId())) {
throw new ConflictException(ErrorCode.ALREADY_EXISTS_USER);
}

if (userRepository.existsByUsername(request.username())) {
throw new ConflictException(ErrorCode.ALREADY_USING_USERNAME);
}
UserServiceUtil.checkAlreadySignupSocialId(userRepository, request.socialId());
UserServiceUtil.checkAlreadyUsedNickname(userRepository, request.nickname());

User signupUser = userRepository.save(User.builder()
.username(request.username())
.socialInfo(SocialInfo.builder()
.socialId(request.socialId())
.build())
.deviceToken(request.deviceToken())
.roles(Role.USER.name())
.platform(request.platform())
.build());
User signupUser = userCreator.create(
request.nickname(),
request.socialId(),
request.platform(),
request.deviceToken(),
Role.USER.name()
);

Jwt jwt = jwtProvider.getUserJwt(signupUser.getSocialInfo().getSocialId());

return SocialSignupResponse.builder()
.id(signupUser.getId())
.username(signupUser.getUsername())
.socialId(signupUser.getSocialInfo().getSocialId())
.accessToken(jwt.accessToken())
.refreshToken(jwt.refreshToken())
.build();
return SocialSignupResponse.of(
signupUser.getId(),
signupUser.getUsername(),
signupUser.getSocialInfo().getSocialId(),
jwt.accessToken(),
jwt.refreshToken()
);
}

@Transactional
Expand All @@ -70,7 +63,7 @@ public SocialLoginResponse login(SocialLoginRequest request) {
loginUser.updateDeviceToken(request.deviceToken());
}

if (!request.platform().equals(loginUser.getPlatform())) {
if (!request.platform().equals(loginUser.getSocialInfo().getPlatform())) {
loginUser.updatePlatform(request.platform());
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.sobok.SobokSobok.auth.application;

import io.sobok.SobokSobok.auth.domain.Platform;
import io.sobok.SobokSobok.auth.domain.SocialInfo;
import io.sobok.SobokSobok.auth.domain.User;
import io.sobok.SobokSobok.auth.infrastructure.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class UserCreator {

private UserRepository userRepository;

public User create(final String username, final String socialId, final Platform platform, final String deviceToken, final String roles) {
SocialInfo socialInfo = SocialInfo.newInstance(socialId, platform);
return User.newInstance(username, socialInfo, deviceToken, roles);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public void changeUsername(Long userId, String username) {
User user = UserServiceUtil.findUserById(userRepository, userId);

if (duplicateNickname(username)) {
throw new ConflictException(ErrorCode.ALREADY_USING_USERNAME);
throw new ConflictException(ErrorCode.ALREADY_USING_NICKNAME);
}

user.changeUsername(username);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,26 @@
import io.sobok.SobokSobok.auth.domain.User;
import io.sobok.SobokSobok.auth.infrastructure.UserRepository;
import io.sobok.SobokSobok.exception.ErrorCode;
import io.sobok.SobokSobok.exception.model.ConflictException;
import io.sobok.SobokSobok.exception.model.NotFoundException;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class UserServiceUtil {

public static void checkAlreadySignupSocialId(UserRepository userRepository, String socialId) {
if (userRepository.existsBySocialInfoSocialId(socialId)) {
throw new ConflictException(ErrorCode.ALREADY_EXISTS_USER);
}
}

public static void checkAlreadyUsedNickname(UserRepository userRepository, String nickname) {
if (userRepository.existsByUsername(nickname)) {
throw new ConflictException(ErrorCode.ALREADY_USING_NICKNAME);
}
}

public static User findUserById(UserRepository userRepository, Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new NotFoundException(ErrorCode.UNREGISTERED_USER));
Expand Down
21 changes: 16 additions & 5 deletions src/main/java/io/sobok/SobokSobok/auth/domain/SocialInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

Expand All @@ -17,12 +16,24 @@ public class SocialInfo {
@Column(nullable = false)
private String socialId;

public void removeSocialInfo() {
this.socialId = "";
@Column
@Enumerated(EnumType.STRING)
private Platform platform;

public static SocialInfo newInstance(String socialId, Platform platform) {
return new SocialInfo(socialId, platform);
}

@Builder
public SocialInfo(String socialId) {
private SocialInfo(String socialId, Platform platform) {
this.socialId = socialId;
this.platform = platform;
}

public void changeSocialPlatform(Platform platform) {
this.platform = platform;
}

public void removeSocialInfo() {
this.socialId = "";
}
}
12 changes: 5 additions & 7 deletions src/main/java/io/sobok/SobokSobok/auth/domain/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,15 @@ public class User extends BaseEntity implements UserDetails {
@Column
private String leaveReason;

@Column
@Enumerated(EnumType.STRING)
private Platform platform;
public static User newInstance(String username, SocialInfo socialInfo, String deviceToken, String roles) {
return new User(username, socialInfo, deviceToken, roles);
}

@Builder
public User(String username, SocialInfo socialInfo, String deviceToken, String roles, Platform platform) {
private User(String username, SocialInfo socialInfo, String deviceToken, String roles) {
this.username = username;
this.socialInfo = socialInfo;
this.deviceToken = deviceToken;
this.roles = roles;
this.platform = platform;
}

public void updateDeviceToken(String newDeviceToken) {
Expand All @@ -71,7 +69,7 @@ public void changeUsername(String username) {
}

public void updatePlatform(Platform platform) {
this.platform = platform;
this.socialInfo.changeSocialPlatform(platform);
}

public void deleteUser(String leaveReason) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,25 @@
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;

public record SocialSignupRequest(

@Schema(requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank
@NotBlank(message = "소셜 아이디가 입력되지 않았습니다.")
String socialId,

@Schema(requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank
String username,
@NotBlank(message = "닉네임이 입력되지 않았습니다.")
@Size(min = 2, max = 10, message = "닉네임은 2글자 이상 10글자 이하만 가능합니다.")
String nickname,

@Schema(requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank
@NotBlank(message = "디바이스 토큰이 입력되지 않았습니다.")
String deviceToken,

@Schema(requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull
@NotNull(message = "사용자의 모바일 플랫폼이 입력되지 않았습니다.")
Platform platform
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,13 @@ public record SocialSignupResponse(

String refreshToken
) {
public static SocialSignupResponse of(
final Long id,
final String username,
final String socialId,
final String accessToken,
final String refreshToken
) {
return new SocialSignupResponse(id, username, socialId, accessToken, refreshToken);
}
}
4 changes: 4 additions & 0 deletions src/main/java/io/sobok/SobokSobok/common/dto/ApiResponse.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,8 @@ public static <T> ApiResponse<T> success(SuccessCode successCode, T data) {
public static <T> ApiResponse<T> error(ErrorCode errorCode) {
return new ApiResponse<>(errorCode.getCode().value(), errorCode.getMessage(), null);
}

public static <T> ApiResponse<T> error(ErrorCode errorCode, String message) {
return new ApiResponse<>(errorCode.getCode().value(), message, null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,16 @@
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.HashMap;
import java.util.Map;

@RestControllerAdvice
@Slf4j
public class ControllerExceptionAdvice {
Expand Down Expand Up @@ -43,6 +48,19 @@ protected ResponseEntity<ApiResponse<Void>> handleHttpRequestMethodNotSupportedE
);
}

@ExceptionHandler(MethodArgumentNotValidException.class)
protected ResponseEntity<ApiResponse<Void>> handleMethodArgumentNotValidException(final MethodArgumentNotValidException exception) {
StringBuilder sb = new StringBuilder();
for (FieldError error : exception.getBindingResult().getFieldErrors()) {
sb.append(error.getField()).append(": ").append(error.getDefaultMessage()).append(" ");
}

return new ResponseEntity<>(
ApiResponse.error(ErrorCode.BAD_REQUEST_EXCEPTION, sb.toString()),
HttpStatus.BAD_REQUEST
);
}

/**
* external Error
*/
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/io/sobok/SobokSobok/exception/ErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public enum ErrorCode {
UNREGISTERED_TOKEN(HttpStatus.NOT_FOUND, "등록되지 않은 토큰입니다."),
NOT_LOGGED_IN_USER(HttpStatus.NOT_FOUND, "로그인되지 않은 사용자입니다."),
ALREADY_EXISTS_USER(HttpStatus.CONFLICT, "이미 회원가입이 완료된 사용자입니다."),
ALREADY_USING_USERNAME(HttpStatus.CONFLICT, "이미 사용중인 username입니다."),
ALREADY_USING_NICKNAME(HttpStatus.CONFLICT, "이미 사용중인 닉네임입니다."),
EMPTY_DEVICE_TOKEN(HttpStatus.NOT_FOUND, "디바이스 토큰이 존재하지 않습니다."),

// jwt
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,13 @@ public void sendNotificationByTokenWithFriendData(PushNotificationRequest reques
private void sendNotification(PushNotificationRequest request, String friendId) {
User user = UserServiceUtil.findUserById(userRepository, request.userId());
Message.Builder messageBuilder;
if (user.getPlatform().equals(Platform.ANDROID)) {
if (user.getSocialInfo().getPlatform().equals(Platform.ANDROID)) {
messageBuilder = Message.builder()
.setToken(user.getDeviceToken())
.putData("title", request.title())
.putData("body", request.body() == null ? "" : request.body())
.putData("type", request.type());
} else if (user.getPlatform().equals(Platform.iOS)) {
} else if (user.getSocialInfo().getPlatform().equals(Platform.iOS)) {
messageBuilder = Message.builder()
.setToken(user.getDeviceToken())
.setNotification(buildNotification(request))
Expand Down

0 comments on commit 5e96109

Please sign in to comment.