Skip to content

Commit 104a65d

Browse files
committed
Demo 테스트
1 parent d5ec397 commit 104a65d

14 files changed

Lines changed: 1082 additions & 0 deletions

File tree

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.loopers.application.user;
2+
3+
import com.loopers.domain.user.UserModel;
4+
import com.loopers.domain.user.UserService;
5+
import lombok.RequiredArgsConstructor;
6+
import org.springframework.stereotype.Component;
7+
8+
@RequiredArgsConstructor
9+
@Component
10+
public class UserFacade {
11+
private final UserService userService;
12+
13+
public UserInfo createUser(String id, String email, String birthDate, String gender) {
14+
UserModel user = userService.createUser(id, email, birthDate, gender);
15+
return UserInfo.from(user);
16+
}
17+
18+
public UserInfo getUserById(String id) {
19+
UserModel user = userService.getUserById(id);
20+
return user != null ? UserInfo.from(user) : null;
21+
}
22+
23+
public Integer getUserPoint(String id) {
24+
return userService.getUserPoint(id);
25+
}
26+
27+
public UserInfo chargePoint(String id, Integer amount) {
28+
UserModel user = userService.chargePoint(id, amount);
29+
return UserInfo.from(user);
30+
}
31+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.loopers.application.user;
2+
3+
import com.loopers.domain.user.UserModel;
4+
import java.time.LocalDate;
5+
6+
public record UserInfo(String id, String email, LocalDate birthDate, String gender, Integer point) {
7+
public static UserInfo from(UserModel model) {
8+
return new UserInfo(
9+
model.getUserId(),
10+
model.getEmail(),
11+
model.getBirthDate(),
12+
model.getGender(),
13+
model.getPoint()
14+
);
15+
}
16+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
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.Column;
7+
import jakarta.persistence.Entity;
8+
import jakarta.persistence.Table;
9+
import java.time.LocalDate;
10+
import java.time.format.DateTimeFormatter;
11+
import java.time.format.DateTimeParseException;
12+
import java.util.regex.Pattern;
13+
14+
@Entity
15+
@Table(name = "users")
16+
public class UserModel extends BaseEntity {
17+
18+
@Column(name = "user_id", unique = true, nullable = false)
19+
private String userId;
20+
21+
private String email;
22+
private LocalDate birthDate;
23+
private String gender;
24+
private Integer point;
25+
26+
protected UserModel() {}
27+
28+
public UserModel(String id, String email, String birthDate, String gender) {
29+
validateId(id);
30+
validateEmail(email);
31+
validateBirthDate(birthDate);
32+
33+
this.userId = id;
34+
this.email = email;
35+
this.birthDate = LocalDate.parse(birthDate, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
36+
this.gender = gender;
37+
this.point = 0;
38+
}
39+
40+
private void validateId(String id) {
41+
if (id == null || id.isBlank()) {
42+
throw new CoreException(ErrorType.BAD_REQUEST, "ID는 비어있을 수 없습니다.");
43+
}
44+
if (id.length() > 10) {
45+
throw new CoreException(ErrorType.BAD_REQUEST, "ID는 10자 이내여야 합니다.");
46+
}
47+
if (!Pattern.matches("^[a-zA-Z0-9]+$", id)) {
48+
throw new CoreException(ErrorType.BAD_REQUEST, "ID는 영문 및 숫자만 사용할 수 있습니다.");
49+
}
50+
}
51+
52+
private void validateEmail(String email) {
53+
if (email == null || email.isBlank()) {
54+
throw new CoreException(ErrorType.BAD_REQUEST, "이메일은 비어있을 수 없습니다.");
55+
}
56+
if (!Pattern.matches("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", email)) {
57+
throw new CoreException(ErrorType.BAD_REQUEST, "이메일 형식이 올바르지 않습니다.");
58+
}
59+
}
60+
61+
private void validateBirthDate(String birthDate) {
62+
if (birthDate == null || birthDate.isBlank()) {
63+
throw new CoreException(ErrorType.BAD_REQUEST, "생년월일은 비어있을 수 없습니다.");
64+
}
65+
try {
66+
LocalDate.parse(birthDate, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
67+
} catch (DateTimeParseException e) {
68+
throw new CoreException(ErrorType.BAD_REQUEST, "생년월일은 yyyy-MM-dd 형식이어야 합니다.");
69+
}
70+
}
71+
72+
public void chargePoint(Integer amount) {
73+
if (amount == null || amount <= 0) {
74+
throw new CoreException(ErrorType.BAD_REQUEST, "충전할 포인트는 0보다 큰 정수여야 합니다.");
75+
}
76+
this.point += amount;
77+
}
78+
79+
public String getUserId() {
80+
return userId;
81+
}
82+
83+
public String getEmail() {
84+
return email;
85+
}
86+
87+
public LocalDate getBirthDate() {
88+
return birthDate;
89+
}
90+
91+
public String getGender() {
92+
return gender;
93+
}
94+
95+
public Integer getPoint() {
96+
return point;
97+
}
98+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.loopers.domain.user;
2+
3+
import java.util.Optional;
4+
5+
public interface UserRepository {
6+
Optional<UserModel> findById(String id);
7+
UserModel save(UserModel user);
8+
boolean existsById(String id);
9+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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+
@Transactional
16+
public UserModel createUser(String id, String email, String birthDate, String gender) {
17+
if (userRepository.existsById(id)) {
18+
throw new CoreException(ErrorType.BAD_REQUEST, "이미 가입된 ID입니다.");
19+
}
20+
21+
UserModel user = new UserModel(id, email, birthDate, gender);
22+
return userRepository.save(user);
23+
}
24+
25+
@Transactional(readOnly = true)
26+
public UserModel getUserById(String id) {
27+
return userRepository.findById(id)
28+
.orElse(null);
29+
}
30+
31+
@Transactional(readOnly = true)
32+
public Integer getUserPoint(String id) {
33+
UserModel user = userRepository.findById(id)
34+
.orElse(null);
35+
return user != null ? user.getPoint() : null;
36+
}
37+
38+
@Transactional
39+
public UserModel chargePoint(String id, Integer amount) {
40+
UserModel user = userRepository.findById(id)
41+
.orElseThrow(() -> new CoreException(ErrorType.NOT_FOUND, "사용자를 찾을 수 없습니다."));
42+
43+
user.chargePoint(amount);
44+
return userRepository.save(user);
45+
}
46+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
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+
UserModel findByUserId(String userId);
8+
boolean existsByUserId(String userId);
9+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
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+
private final UserJpaRepository userJpaRepository;
14+
15+
@Override
16+
public Optional<UserModel> findById(String id) {
17+
UserModel user = userJpaRepository.findByUserId(id);
18+
return Optional.ofNullable(user);
19+
}
20+
21+
@Override
22+
public UserModel save(UserModel user) {
23+
return userJpaRepository.save(user);
24+
}
25+
26+
@Override
27+
public boolean existsById(String id) {
28+
return userJpaRepository.existsByUserId(id);
29+
}
30+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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.media.Schema;
6+
import io.swagger.v3.oas.annotations.tags.Tag;
7+
8+
@Tag(name = "User V1 API", description = "사용자 관리 API 입니다.")
9+
public interface UserV1ApiSpec {
10+
11+
@Operation(
12+
summary = "회원 가입",
13+
description = "새로운 사용자를 등록합니다."
14+
)
15+
ApiResponse<UserV1Dto.UserResponse> createUser(
16+
@Schema(name = "회원 가입 요청", description = "회원 가입에 필요한 정보")
17+
UserV1Dto.UserCreateRequest request
18+
);
19+
20+
@Operation(
21+
summary = "내 정보 조회",
22+
description = "사용자 ID로 회원 정보를 조회합니다."
23+
)
24+
ApiResponse<UserV1Dto.UserResponse> getUser(
25+
@Schema(name = "사용자 ID", description = "조회할 사용자의 ID")
26+
String userId
27+
);
28+
29+
@Operation(
30+
summary = "포인트 조회",
31+
description = "사용자의 보유 포인트를 조회합니다."
32+
)
33+
ApiResponse<UserV1Dto.PointResponse> getUserPoint(
34+
@Schema(name = "사용자 ID", description = "포인트를 조회할 사용자의 ID")
35+
String userId,
36+
@Schema(name = "X-USER-ID", description = "사용자 인증을 위한 헤더")
37+
String headerUserId
38+
);
39+
40+
@Operation(
41+
summary = "포인트 충전",
42+
description = "사용자의 포인트를 충전합니다."
43+
)
44+
ApiResponse<UserV1Dto.PointResponse> chargePoint(
45+
@Schema(name = "사용자 ID", description = "포인트를 충전할 사용자의 ID")
46+
String userId,
47+
@Schema(name = "포인트 충전 요청", description = "충전할 포인트 금액")
48+
UserV1Dto.PointChargeRequest request
49+
);
50+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package com.loopers.interfaces.api.user;
2+
3+
import com.loopers.application.user.UserFacade;
4+
import com.loopers.application.user.UserInfo;
5+
import com.loopers.interfaces.api.ApiResponse;
6+
import com.loopers.support.error.CoreException;
7+
import com.loopers.support.error.ErrorType;
8+
import lombok.RequiredArgsConstructor;
9+
import org.springframework.http.HttpStatus;
10+
import org.springframework.web.bind.annotation.*;
11+
12+
@RequiredArgsConstructor
13+
@RestController
14+
@RequestMapping("/api/v1/users")
15+
public class UserV1Controller implements UserV1ApiSpec {
16+
17+
private final UserFacade userFacade;
18+
19+
@PostMapping
20+
@Override
21+
public ApiResponse<UserV1Dto.UserResponse> createUser(
22+
@RequestBody UserV1Dto.UserCreateRequest request
23+
) {
24+
if (request.gender() == null || request.gender().isBlank()) {
25+
throw new CoreException(ErrorType.BAD_REQUEST, "성별은 필수입니다.");
26+
}
27+
28+
UserInfo info = userFacade.createUser(request.id(), request.email(), request.birthDate(), request.gender());
29+
UserV1Dto.UserResponse response = UserV1Dto.UserResponse.from(info);
30+
return ApiResponse.success(response);
31+
}
32+
33+
@GetMapping("/{userId}")
34+
@Override
35+
public ApiResponse<UserV1Dto.UserResponse> getUser(
36+
@PathVariable String userId
37+
) {
38+
UserInfo info = userFacade.getUserById(userId);
39+
if (info == null) {
40+
throw new CoreException(ErrorType.NOT_FOUND, "사용자를 찾을 수 없습니다.");
41+
}
42+
43+
UserV1Dto.UserResponse response = UserV1Dto.UserResponse.from(info);
44+
return ApiResponse.success(response);
45+
}
46+
47+
@GetMapping("/{userId}/points")
48+
@Override
49+
public ApiResponse<UserV1Dto.PointResponse> getUserPoint(
50+
@PathVariable String userId,
51+
@RequestHeader(value = "X-USER-ID", required = false) String headerUserId
52+
) {
53+
if (headerUserId == null || headerUserId.isBlank()) {
54+
throw new CoreException(ErrorType.BAD_REQUEST, "X-USER-ID 헤더가 필요합니다.");
55+
}
56+
57+
Integer point = userFacade.getUserPoint(userId);
58+
if (point == null) {
59+
throw new CoreException(ErrorType.NOT_FOUND, "사용자를 찾을 수 없습니다.");
60+
}
61+
62+
UserV1Dto.PointResponse response = UserV1Dto.PointResponse.from(point);
63+
return ApiResponse.success(response);
64+
}
65+
66+
@PostMapping("/{userId}/points/charge")
67+
@Override
68+
public ApiResponse<UserV1Dto.PointResponse> chargePoint(
69+
@PathVariable String userId,
70+
@RequestBody UserV1Dto.PointChargeRequest request
71+
) {
72+
UserInfo info = userFacade.chargePoint(userId, request.amount());
73+
UserV1Dto.PointResponse response = UserV1Dto.PointResponse.from(info.point());
74+
return ApiResponse.success(response);
75+
}
76+
}

0 commit comments

Comments
 (0)