Skip to content

Commit 16dfebe

Browse files
authored
Merge pull request #13 from HongChangMo/round-01
Round 01 - 사용자 관리 기능 PR 작성
2 parents d5ec397 + 878f495 commit 16dfebe

File tree

16 files changed

+1052
-0
lines changed

16 files changed

+1052
-0
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package com.loopers.application.user;
2+
3+
import com.loopers.domain.user.Gender;
4+
import com.loopers.domain.user.UserModel;
5+
import com.loopers.domain.user.UserService;
6+
import com.loopers.support.error.CoreException;
7+
import com.loopers.support.error.ErrorType;
8+
import lombok.RequiredArgsConstructor;
9+
import org.springframework.stereotype.Component;
10+
11+
@RequiredArgsConstructor
12+
@Component
13+
public class UserFacade {
14+
15+
private final UserService userService;
16+
17+
public UserInfo accountUser( String userId, String email, String birthdate, Gender gender ) {
18+
19+
UserModel user = userService.accountUser(userId, email, birthdate, gender);
20+
return UserInfo.from(user);
21+
22+
}
23+
24+
public UserInfo getUserInfo(String userId) {
25+
UserModel user = userService.getUserByUserId(userId);
26+
27+
if(user == null) {
28+
throw new CoreException(ErrorType.NOT_FOUND, "해당 ID를 가진 회원이 존재하지 않습니다.");
29+
}
30+
31+
return UserInfo.from(user);
32+
}
33+
34+
public Integer getUserPoint(String userId) {
35+
UserModel findUser = userService.getUserPointByUserId(userId);
36+
37+
if (findUser == null ) {
38+
throw new CoreException(ErrorType.NOT_FOUND, "해당 ID를 가진 회원이 존재하지 않습니다.");
39+
}
40+
41+
return findUser.getPoint();
42+
}
43+
44+
public UserInfo chargeUserPoint(String userId, Integer chargePoint) {
45+
UserModel findUser = userService.chargePointByUserId(userId, chargePoint);
46+
47+
return UserInfo.from(findUser);
48+
}
49+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.loopers.application.user;
2+
3+
import com.loopers.domain.user.Gender;
4+
import com.loopers.domain.user.UserModel;
5+
6+
public record UserInfo(Long id, String userId, String email, String birthdate, Gender gender, Integer point) {
7+
public static UserInfo from(UserModel userModel) {
8+
return new UserInfo(
9+
userModel.getId(),
10+
userModel.getUserId(),
11+
userModel.getEmail(),
12+
userModel.getBirthdate(),
13+
userModel.getGender(),
14+
userModel.getPoint()
15+
);
16+
}
17+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.loopers.domain.user;
2+
3+
import lombok.Getter;
4+
import lombok.RequiredArgsConstructor;
5+
6+
@Getter
7+
@RequiredArgsConstructor
8+
public enum Gender {
9+
MALE("M"),
10+
FEMALE("F");
11+
12+
private final String gender;
13+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package com.loopers.domain.user;
2+
3+
import com.loopers.domain.BaseEntity;
4+
import com.loopers.support.error.CoreException;
5+
import com.loopers.support.error.ErrorType;
6+
import jakarta.persistence.*;
7+
import lombok.Builder;
8+
import lombok.Getter;
9+
import lombok.NoArgsConstructor;
10+
11+
@NoArgsConstructor
12+
@Entity
13+
@Table(name = "users")
14+
@Getter
15+
public class UserModel extends BaseEntity {
16+
17+
@Id
18+
@GeneratedValue(strategy = GenerationType.IDENTITY)
19+
private Long id;
20+
private String userId;
21+
private String email;
22+
private String birthdate;
23+
24+
@Enumerated( EnumType.STRING )
25+
private Gender gender;
26+
private Integer point = 0;
27+
28+
@Builder
29+
public UserModel(String userId, String email, String birthdate, Gender gender) {
30+
31+
validUserInfo(userId, email, birthdate);
32+
33+
this.userId = userId;
34+
this.email = email;
35+
this.birthdate = birthdate;
36+
this.gender = gender;
37+
}
38+
39+
private static void validUserInfo(String userId, String email, String birthdate) {
40+
if( !userId.matches("^[a-zA-Z0-9]{1,10}$") ) {
41+
throw new CoreException(ErrorType.BAD_REQUEST, "유저 ID 형식 오류");
42+
}
43+
44+
if (!email.matches("^[\\w\\.]+@[\\w\\.]+\\.[a-zA-Z]{2,}$")) {
45+
throw new CoreException(ErrorType.BAD_REQUEST, "이메일 형식 오류");
46+
}
47+
48+
if (!birthdate.matches("^\\d{4}-\\d{2}-\\d{2}$")) {
49+
throw new CoreException(ErrorType.BAD_REQUEST, "생년월일 형식 오류");
50+
}
51+
}
52+
53+
protected void chargePoint(Integer point) {
54+
55+
if( point <= 0 ) {
56+
throw new CoreException(ErrorType.BAD_REQUEST, "충전할 포인트는 1 이상의 정수여야 합니다.");
57+
}
58+
59+
this.point += point;
60+
}
61+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.loopers.domain.user;
2+
3+
import java.util.Optional;
4+
5+
public interface UserRepository {
6+
7+
boolean existsByUserId(String userId);
8+
9+
UserModel save(UserModel userModel);
10+
11+
Optional<UserModel> findUserByUserId(String userId);
12+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package com.loopers.domain.user;
2+
3+
import com.loopers.support.error.CoreException;
4+
import com.loopers.support.error.ErrorType;
5+
import lombok.RequiredArgsConstructor;
6+
import org.springframework.stereotype.Component;
7+
import org.springframework.transaction.annotation.Transactional;
8+
9+
@RequiredArgsConstructor
10+
@Component
11+
public class UserService {
12+
13+
private final UserRepository userRepository;
14+
15+
16+
@Transactional
17+
public UserModel accountUser(String userId, String email, String birthdate, Gender gender) {
18+
19+
if( userRepository.existsByUserId(userId) ) {
20+
throw new CoreException(ErrorType.BAD_REQUEST, "이미 존재하는 ID 입니다.");
21+
}
22+
23+
UserModel user = UserModel.builder()
24+
.userId(userId)
25+
.email(email)
26+
.birthdate(birthdate)
27+
.gender(gender)
28+
.build();
29+
30+
return userRepository.save(user);
31+
}
32+
33+
@Transactional( readOnly = true )
34+
public UserModel getUserByUserId(String userId) {
35+
return userRepository.findUserByUserId(userId)
36+
.orElse(null);
37+
}
38+
39+
@Transactional( readOnly = true )
40+
public UserModel getUserPointByUserId(String userId) {
41+
return userRepository.findUserByUserId(userId)
42+
.orElse(null);
43+
}
44+
45+
@Transactional
46+
public UserModel chargePointByUserId(String notExistsUserId, Integer chargePoint) {
47+
48+
UserModel findUser = userRepository.findUserByUserId(notExistsUserId)
49+
.orElseThrow(
50+
() -> new CoreException(ErrorType.NOT_FOUND, "해당 ID 의 회원이 존재하지 않아 포인트 충전이 실패하였습니다.")
51+
);
52+
53+
findUser.chargePoint(chargePoint);
54+
55+
return findUser;
56+
}
57+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.loopers.infrastructure.user;
2+
3+
import com.loopers.domain.user.UserModel;
4+
import org.springframework.data.jpa.repository.JpaRepository;
5+
6+
public interface UserJpaRepository extends JpaRepository<UserModel, Long> {
7+
8+
boolean existsByUserId(String userId);
9+
10+
UserModel findByUserId(String userId);
11+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.loopers.infrastructure.user;
2+
3+
import com.loopers.domain.user.UserModel;
4+
import com.loopers.domain.user.UserRepository;
5+
import lombok.RequiredArgsConstructor;
6+
import org.springframework.stereotype.Component;
7+
8+
import java.util.Optional;
9+
10+
@RequiredArgsConstructor
11+
@Component
12+
public class UserRepositoryImpl implements UserRepository {
13+
14+
private final UserJpaRepository userJpaRepository;
15+
16+
@Override
17+
public UserModel save(UserModel userModel) {
18+
19+
return userJpaRepository.save(userModel);
20+
}
21+
22+
@Override
23+
public Optional<UserModel> findUserByUserId(String userId) {
24+
UserModel user = userJpaRepository.findByUserId(userId);
25+
26+
return Optional.ofNullable(user);
27+
}
28+
29+
@Override
30+
public boolean existsByUserId(String userId) {
31+
return userJpaRepository.existsByUserId(userId);
32+
}
33+
}

apps/commerce-api/src/main/java/com/loopers/interfaces/api/ApiControllerAdvice.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import lombok.extern.slf4j.Slf4j;
99
import org.springframework.http.ResponseEntity;
1010
import org.springframework.http.converter.HttpMessageNotReadableException;
11+
import org.springframework.web.bind.MethodArgumentNotValidException;
12+
import org.springframework.web.bind.MissingRequestHeaderException;
1113
import org.springframework.web.bind.MissingServletRequestParameterException;
1214
import org.springframework.web.bind.annotation.ExceptionHandler;
1315
import org.springframework.web.bind.annotation.RestControllerAdvice;
@@ -38,6 +40,15 @@ public ResponseEntity<ApiResponse<?>> handleBadRequest(MethodArgumentTypeMismatc
3840
return failureResponse(ErrorType.BAD_REQUEST, message);
3941
}
4042

43+
@ExceptionHandler
44+
public ResponseEntity<ApiResponse<?>> handleBadRequest(MethodArgumentNotValidException e) {
45+
String name = e.getObjectName();
46+
String type = e.getTypeMessageCode();
47+
48+
String message = String.format("요청 파라미터 '%s' (타입: %s)의 값이(가) 잘못되었습니다.", name, type);
49+
return failureResponse(ErrorType.BAD_REQUEST, message);
50+
}
51+
4152
@ExceptionHandler
4253
public ResponseEntity<ApiResponse<?>> handleBadRequest(MissingServletRequestParameterException e) {
4354
String name = e.getParameterName();
@@ -46,6 +57,15 @@ public ResponseEntity<ApiResponse<?>> handleBadRequest(MissingServletRequestPara
4657
return failureResponse(ErrorType.BAD_REQUEST, message);
4758
}
4859

60+
@ExceptionHandler
61+
public ResponseEntity<ApiResponse<?>> handleBadRequest(MissingRequestHeaderException e) {
62+
String name = e.getHeaderName();
63+
String type = e.getTypeMessageCode();
64+
65+
String message = String.format("필수 요청 헤더 '%s' (타입: %s)가 누락되었습니다.", name, type);
66+
return failureResponse(ErrorType.BAD_REQUEST, message);
67+
}
68+
4969
@ExceptionHandler
5070
public ResponseEntity<ApiResponse<?>> handleBadRequest(HttpMessageNotReadableException e) {
5171
String errorMessage;
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package com.loopers.interfaces.api.user;
2+
3+
import com.loopers.interfaces.api.ApiResponse;
4+
import io.swagger.v3.oas.annotations.Operation;
5+
import io.swagger.v3.oas.annotations.Parameter;
6+
import io.swagger.v3.oas.annotations.enums.ParameterIn;
7+
import io.swagger.v3.oas.annotations.media.Schema;
8+
9+
public interface UserV1ApiSpec {
10+
@Operation(
11+
summary = "회원 가입",
12+
description = "새로운 사용자를 등록한다."
13+
)
14+
ApiResponse<UserV1DTO.UserResponse> accountUser(
15+
@Schema(name = "회원 가입", description = "회원 가입시 필요한 사용자 정보")
16+
UserV1DTO.UserRequest request
17+
);
18+
19+
@Operation(
20+
summary = "회원 조회",
21+
description = "해당 ID에 해당하는 유저 정보를 반환한다."
22+
)
23+
ApiResponse<UserV1DTO.UserResponse> getUser(
24+
@Schema(name = "회원 조회", description = "조회할 회원의 ID")
25+
String userId
26+
);
27+
28+
@Operation(
29+
summary = "회원 포인트 조회",
30+
description = "해당 ID에 해당하는 유저의 포인트 정보를 반환한다."
31+
)
32+
ApiResponse<UserV1DTO.UserPointResponse> getUserPoint(
33+
@Schema(name = "회원 포인트 조회", description = "조회할 회원의 ID")
34+
String userId,
35+
@Parameter(
36+
name = "X-USER-ID",
37+
description = "요청 헤더로 전달되는 회원 ID",
38+
in = ParameterIn.HEADER,
39+
required = true
40+
)
41+
String headerUserId
42+
);
43+
44+
@Operation(
45+
summary = "회원 포인트 충전",
46+
description = "해당 ID에 해당하는 유저의 포인트를 충전한다."
47+
)
48+
ApiResponse<UserV1DTO.UserPointResponse> chargeUserPoint(
49+
@Schema(name = "회원 포인트 충전", description = "조회할 회원의 ID, 충전할 포인트")
50+
UserV1DTO.UserPointRequest request
51+
);
52+
53+
}

0 commit comments

Comments
 (0)