Skip to content

Commit

Permalink
Merge pull request #6 from KNU-HAEDAL/issue/#4
Browse files Browse the repository at this point in the history
Issue/#4
  • Loading branch information
momnpa333 authored May 31, 2024
2 parents d923fe3 + 67becaa commit bda807a
Show file tree
Hide file tree
Showing 25 changed files with 716 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
import lombok.RequiredArgsConstructor;
import org.haedal.zzansuni.controller.user.UserRes;
import org.haedal.zzansuni.core.api.ApiResponse;
import org.haedal.zzansuni.domain.auth.AuthService;
import org.haedal.zzansuni.domain.user.UserModel;
import org.haedal.zzansuni.global.jwt.JwtToken;
import org.springframework.data.util.Pair;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
Expand All @@ -14,15 +18,14 @@
@RequiredArgsConstructor
@RestController
public class AuthController {
private final AuthService authService;

@Operation(summary = "oauth2 로그인", description = "oauth2 code를 이용하여 로그인한다.")
@PostMapping("/api/auth/oauth2")
public ApiResponse<AuthRes.LoginResponse> oauth2(@RequestBody @Valid AuthReq.OAuth2LoginRequest request) {
return ApiResponse.success(
new AuthRes.LoginResponse("accessToken", "refresh", new UserRes.UserInfoDto(
1L, "nickname", "https://picsum.photos/200/300", "email",
new UserRes.TierInfoDto("tier", 100, 50)
)));
Pair<JwtToken,UserModel> pair = authService.oAuth2LoginOrSignup(request.provider(), request.code(), request.state());
var response = AuthRes.LoginResponse.from(pair.getFirst(), pair.getSecond());
return ApiResponse.success(response);
}

@Operation(summary = "로그아웃", description = "로그아웃한다.")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
package org.haedal.zzansuni.controller.auth;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import org.haedal.zzansuni.domain.auth.OAuth2Provider;

public class AuthReq {
public record OAuth2LoginRequest(
@NotBlank(message = "provider는 필수입니다.")
@NotNull(message = "provider는 필수입니다.")
OAuth2Provider provider,
@NotBlank(message = "code는 필수입니다.")
String code
String code,
String state
) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import lombok.Builder;
import org.haedal.zzansuni.controller.user.UserRes;
import org.haedal.zzansuni.domain.user.UserModel;
import org.haedal.zzansuni.global.jwt.JwtToken;

public class AuthRes {
@Builder
Expand All @@ -10,5 +12,13 @@ public record LoginResponse(
String refreshToken,
UserRes.UserInfoDto userInfo
) {
public static LoginResponse from(JwtToken jwtToken, UserModel userModel) {
var userInfo = UserRes.UserInfoDto.from(userModel);
return LoginResponse.builder()
.accessToken(jwtToken.getAccessToken())
.refreshToken(jwtToken.getRefreshToken())
.userInfo(userInfo)
.build();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package org.haedal.zzansuni.controller.user;

import lombok.Builder;
import org.haedal.zzansuni.domain.user.TierSystem;
import org.haedal.zzansuni.domain.user.UserModel;

import java.time.LocalDate;
import java.util.List;
import java.util.Map;

public class UserRes {
@Builder
Expand All @@ -15,6 +16,16 @@ public record UserInfoDto(
String email,
TierInfoDto tierInfo
) {
public static UserInfoDto from(UserModel userModel) {
var tierInfo = TierInfoDto.from(userModel.getExp());
return UserInfoDto.builder()
.id(userModel.getId())
.nickname(userModel.getNickname())
.profileImageUrl(userModel.getProfileImageUrl())
.email(userModel.getEmail())
.tierInfo(tierInfo)
.build();
}
}

@Builder
Expand All @@ -24,6 +35,15 @@ public record UserDto(
String profileImageUrl,
TierInfoDto tierInfo
) {
public static UserDto from(UserModel userModel) {
var tierInfo = TierInfoDto.from(userModel.getExp());
return UserDto.builder()
.id(userModel.getId())
.nickname(userModel.getNickname())
.profileImageUrl(userModel.getProfileImageUrl())
.tierInfo(tierInfo)
.build();
}
}

@Builder
Expand All @@ -32,6 +52,15 @@ public record TierInfoDto(
Integer totalExp,
Integer currentExp
) {
public static TierInfoDto from(Integer exp) {
var tier = TierSystem.getTier(exp);

return TierInfoDto.builder()
.tier(tier.getKorean())
.totalExp(tier.getEndExp() - tier.getStartExp()) // 티어 시작 경험치부터 끝 경험치까지
.currentExp(exp - tier.getStartExp()) // 현재 경험치 - 티어 시작 경험치
.build();
}
}

@Builder
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,62 @@
package org.haedal.zzansuni.domain.auth;

import lombok.RequiredArgsConstructor;
import org.haedal.zzansuni.domain.user.*;
import org.haedal.zzansuni.global.jwt.JwtToken;
import org.haedal.zzansuni.global.jwt.JwtUser;
import org.haedal.zzansuni.global.jwt.JwtUtils;
import org.springframework.data.util.Pair;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
@RequiredArgsConstructor
public class AuthService {
private final List<OAuth2Client> oAuth2Clients;
private final JwtUtils jwtUtils;
private final UserReader userReader;
private final UserStore userStore;

/**
* OAuth2 로그인 또는 회원가입 <br>
* [state]는 nullable한 입력 값이다.<br>
* 1. OAuth2Client를 이용해 해당 provider로부터 유저정보를 가져옴
* 2. authToken으로 유저를 찾거나 없으면 회원가입
* 3. 토큰 발급, 유저정보 반환
*/
public Pair<JwtToken, UserModel> oAuth2LoginOrSignup(OAuth2Provider provider, @NonNull String code, @Nullable String state) {
OAuth2Client oAuth2Client = oAuth2Clients.stream()
.filter(client -> client.canHandle(provider))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("지원하지 않는 OAuth2Provider 입니다."));

// OAuth2Client를 이용해 해당 provider로부터 유저정보를 가져옴
OAuthUserInfoModel oAuthUserInfoModel = oAuth2Client.getAuthToken(code, state);

// authToken으로 유저를 찾아서 없으면 [OAuthUserInfoModel]를 통해서 회원가입 진행
User user = userReader
.findByAuthToken(oAuthUserInfoModel.authToken())
.orElseGet(() -> signup(oAuthUserInfoModel, provider));

// 토큰 발급, 유저정보 반환
JwtToken jwtToken = createToken(user);
UserModel userModel = UserModel.from(user);
return Pair.of(jwtToken, userModel);
}


private User signup(OAuthUserInfoModel oAuthUserInfoModel, OAuth2Provider provider) {
UserCommand.CreateOAuth2 command = oAuthUserInfoModel.toCreateCommand(provider);
User user = User.create(command);
return userStore.store(user);
}

private JwtToken createToken(User user) {
JwtUser jwtUser = JwtUser.of(user.getId(), user.getRole());
return jwtUtils.createToken(jwtUser);
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.haedal.zzansuni.domain.auth;

import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;

public interface OAuth2Client {
boolean canHandle(OAuth2Provider provider);

/**
* 인증코드를 이용하여 사용자 정보를 가져온다.
* [state]가 필요한 Client의 경우 해당 파라미터를 사용한다.
*/
OAuthUserInfoModel getAuthToken(@NonNull String code, @Nullable String state);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.haedal.zzansuni.domain.auth;

public enum OAuth2Provider {
KAKAO;
KAKAO,
NAVER,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.haedal.zzansuni.domain.auth;

import lombok.Builder;
import org.haedal.zzansuni.domain.user.UserCommand;

@Builder
public record OAuthUserInfoModel(
String authToken,
String nickname
) {
public UserCommand.CreateOAuth2 toCreateCommand(OAuth2Provider provider){
return UserCommand.CreateOAuth2
.builder()
.nickname(nickname)
.authToken(authToken)
.provider(provider)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package org.haedal.zzansuni.domain.user;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

import java.util.Arrays;

/**
* 티어 정보를 enum으로 정적으로 관리
* 향후 데이터베이스에 저장할수도 있음
*/
@Getter
@RequiredArgsConstructor
public enum TierSystem {
NOBI_4("노비 IV", 0, 5),
NOBI_3("노비 III", 5, 10),
NOBI_2("노비 II", 10, 20),
NOBI_1("노비 I", 20, 40),
SANGMIN_4("상민 IV", 40, 60),
SANGMIN_3("상민 III", 60, 80),
SANGMIN_2("상민 II", 80, 100),
SANGMIN_1("상민 I", 100, 120),
PYEONGMIN_4("평민 IV", 120, 140),
PYEONGMIN_3("평민 III", 140, 160),
PYEONGMIN_2("평민 II", 160, 180),
PYEONGMIN_1("평민 I", 180, 200),
YANGBAN_4("양반 IV", 200, 230),
YANGBAN_3("양반 III", 230, 260),
YANGBAN_2("양반 II", 260, 290),
YANGBAN_1("양반 I", 290, 320),
JINGOL_4("진골 IV", 320, 360),
JINGOL_3("진골 III", 360, 400),
JINGOL_2("진골 II", 400, 440),
JINGOL_1("진골 I", 440, 480),
SEONGOL_4("성골 IV", 480, 530),
SEONGOL_3("성골 III", 530, 580),
SEONGOL_2("성골 II", 580, 630),
SEONGOL_1("성골 I", 630, 680),
ECHO_4("에코 IV", 680, 740),
ECHO_3("에코 III", 740, 800),
ECHO_2("에코 II", 800, 860),
ECHO_1("에코 I", 860, Integer.MAX_VALUE);


private final String korean;
private final int startExp;
private final int endExp;

public static TierSystem getTier(int exp) {
return Arrays.stream(TierSystem.values())
.filter(tier -> tier.startExp <= exp && exp < tier.endExp)
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("티어를 찾을 수 없습니다."));

}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
package org.haedal.zzansuni.domain.user;

import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.*;
import org.haedal.zzansuni.domain.auth.OAuth2Provider;
import org.haedal.zzansuni.global.security.Role;

@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Builder
@AllArgsConstructor
@Table(name = "users")
public class User {
@Id
Expand All @@ -29,10 +30,26 @@ public class User {
@Column(nullable = false)
private Integer exp;

@Enumerated(EnumType.STRING)
private OAuth2Provider provider;

private String authToken;

private String profileImageUrl;

public static User create(UserCommand.CreateOAuth2 command) {
return User.builder()
.nickname(command.getNickname())
.email(null)
.password(null)
.role(Role.USER)
.provider(command.getProvider())
.authToken(command.getAuthToken())
.exp(0)
.profileImageUrl(null)
.build();
}

public void update(UserCommand.Update userUpdate) {
this.nickname = userUpdate.getNickname();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,39 @@
package org.haedal.zzansuni.domain.user;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import org.haedal.zzansuni.core.utils.SelfValidating;
import org.haedal.zzansuni.domain.auth.OAuth2Provider;

public class UserCommand {

@Getter
@Builder
public static class CreateOAuth2 extends SelfValidating<UserCommand.CreateOAuth2> {
private final String authToken;
private final String nickname;
@NotNull(message = "OAuth2Provider는 필수입니다.")
private final OAuth2Provider provider;

public CreateOAuth2(String authToken, String nickname, OAuth2Provider provider) {
this.authToken = authToken;
this.nickname = nickname;
this.provider = provider;
if(authToken.isBlank() || nickname.isBlank()){
throw new RuntimeException("authToken, nickname은 필수입니다.");
}
this.validateSelf();
}
}

@Getter
@Builder
public static class Update extends SelfValidating<UserCommand.Update> {
@NotBlank(message = "닉네임은 필수입니다.")
private String nickname;
private final String nickname;

public Update(String nickname) {
this.nickname = nickname;
Expand Down
Loading

0 comments on commit bda807a

Please sign in to comment.