Skip to content

Commit 3490822

Browse files
authored
Merge pull request #26 from yeonjiyeon/main
round1 - 회원가입, 내 정보 조회, 포인트 조회, 포인트 충전 구현
2 parents d5ec397 + 639b17c commit 3490822

18 files changed

Lines changed: 1163 additions & 0 deletions
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.loopers.domain.point;
2+
3+
import com.loopers.domain.user.User;
4+
import com.loopers.domain.user.UserRepository;
5+
import com.loopers.interfaces.api.point.PointV1Dto.PointResponse;
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+
import org.springframework.transaction.annotation.Transactional;
11+
12+
@RequiredArgsConstructor
13+
@Component
14+
public class PointService {
15+
16+
private final UserRepository userRepository;
17+
18+
public Integer getPoint(String userId) {
19+
return userRepository.findByUserId(userId)
20+
.map(User::getPoint)
21+
.orElse(null);
22+
}
23+
24+
@Transactional
25+
public PointResponse charge(String userId, int amount) {
26+
User user = userRepository.findByUserId(userId)
27+
.orElseThrow(() -> new CoreException(ErrorType.NOT_FOUND, "유저를 찾을 수 없습니다."));
28+
int chargePoint = user.chargePoint(amount);
29+
30+
return PointResponse.from(chargePoint);
31+
}
32+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package com.loopers.domain.user;
2+
3+
import com.loopers.support.error.CoreException;
4+
import com.loopers.support.error.ErrorType;
5+
import jakarta.persistence.Entity;
6+
import jakarta.persistence.GeneratedValue;
7+
import jakarta.persistence.GenerationType;
8+
import jakarta.persistence.Id;
9+
import jakarta.persistence.Table;
10+
import java.time.LocalDate;
11+
import java.time.format.DateTimeParseException;
12+
13+
@Entity
14+
@Table(name = "user")
15+
public class User {
16+
17+
private static final String ID_PATTERN = "^[a-zA-Z0-9]{1,10}$";
18+
private static final String EMAIL_PATTERN = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,6}$";
19+
private static final String BIRTHDATE_PATTERN = "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$";
20+
21+
@Id
22+
@GeneratedValue(strategy = GenerationType.IDENTITY)
23+
private Long id;
24+
String userId;
25+
String email;
26+
String birthdate;
27+
private Gender gender;
28+
private int point;
29+
30+
public enum Gender {
31+
MALE, FEMALE
32+
}
33+
34+
protected User() {
35+
}
36+
37+
public User(String userId, String email, String birthdate, Gender gender) {
38+
39+
if (userId == null || !userId.matches(ID_PATTERN)) {
40+
throw new CoreException(ErrorType.BAD_REQUEST, "ID는 영문 및 숫자를 포함한 1~10자 형식에 맞아야 합니다.");
41+
}
42+
43+
if (email == null || !email.matches(EMAIL_PATTERN)) {
44+
throw new CoreException(ErrorType.BAD_REQUEST, "이메일이 'xx@yy.zz' 형식에 맞지 않습니다.");
45+
}
46+
47+
if (birthdate == null || !birthdate.matches(BIRTHDATE_PATTERN)) {
48+
throw new CoreException(ErrorType.BAD_REQUEST, "생년월일이 'yyyy-MM-dd' 형식에 맞지 않습니다.");
49+
}
50+
51+
try {
52+
LocalDate.parse(birthdate);
53+
} catch (DateTimeParseException e) {
54+
throw new CoreException(ErrorType.BAD_REQUEST, "생년월일이 유효하지 않은 날짜입니다.");
55+
}
56+
57+
if (gender == null) {
58+
throw new CoreException(ErrorType.BAD_REQUEST, "성별을 추가하셔야 합니다.");
59+
}
60+
61+
this.userId = userId;
62+
this.email = email;
63+
this.birthdate = birthdate;
64+
this.gender = gender;
65+
this.point = 0;
66+
}
67+
68+
public User(String userId, String email, String birthdate, Gender gender, int point) {
69+
this.userId = userId;
70+
this.email = email;
71+
this.birthdate = birthdate;
72+
this.gender = gender;
73+
this.point = point;
74+
}
75+
76+
public String getUserId() {
77+
return userId;
78+
}
79+
80+
public String getEmail() {
81+
return email;
82+
}
83+
84+
public String getBirthdate() {
85+
return birthdate;
86+
}
87+
88+
public Gender getGender() {
89+
return gender;
90+
}
91+
92+
public int getPoint() {
93+
return point;
94+
}
95+
96+
public int chargePoint(int amount) {
97+
if (amount <= 0) {
98+
throw new CoreException(ErrorType.BAD_REQUEST, "충전 금액은 0보다 커야 합니다.");
99+
}
100+
101+
return this.point += amount;
102+
}
103+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.loopers.domain.user;
2+
3+
import com.loopers.domain.user.User.Gender;
4+
5+
public class UserCommand {
6+
7+
public record UserCreationCommand(
8+
String userId,
9+
String email,
10+
String birthDate,
11+
Gender gender) {
12+
13+
public User toUser() {
14+
return new User(this.userId, this.email, this.birthDate, this.gender);
15+
}
16+
}
17+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.loopers.domain.user;
2+
3+
import java.util.Optional;
4+
5+
public interface UserRepository {
6+
User save(User user);
7+
8+
Optional<User> findByUserId(String userId);
9+
10+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.loopers.domain.user;
2+
3+
import com.loopers.domain.user.UserCommand.UserCreationCommand;
4+
import com.loopers.interfaces.api.user.UserV1Dto.UserResponse;
5+
import lombok.RequiredArgsConstructor;
6+
import org.springframework.stereotype.Component;
7+
8+
@RequiredArgsConstructor
9+
@Component
10+
public class UserService {
11+
12+
private final UserRepository userRepository;
13+
14+
public UserResponse signUp(UserCreationCommand command) {
15+
userRepository.findByUserId(command.userId()).ifPresent(user -> {
16+
throw new IllegalArgumentException("이미 가입된 ID입니다.");
17+
});
18+
19+
User user = userRepository.save(command.toUser());
20+
return UserResponse.from(user);
21+
}
22+
23+
public User getUser(String userId) {
24+
return userRepository.findByUserId(userId).orElse(null);
25+
}
26+
}
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.User;
4+
import java.util.Optional;
5+
import org.springframework.data.jpa.repository.JpaRepository;
6+
7+
public interface UserJpaRepository extends JpaRepository<User, Long> {
8+
9+
Optional<User> findByUserId(String userId);
10+
11+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.loopers.infrastructure.user;
2+
3+
import com.loopers.domain.user.User;
4+
import com.loopers.domain.user.UserRepository;
5+
import java.util.Optional;
6+
import lombok.RequiredArgsConstructor;
7+
import org.springframework.stereotype.Component;
8+
9+
@RequiredArgsConstructor
10+
@Component
11+
public class UserRepositoryImpl implements UserRepository {
12+
private final UserJpaRepository userJpaRepository;
13+
14+
@Override
15+
public User save(User user) {
16+
return userJpaRepository.save(user);
17+
}
18+
19+
@Override
20+
public Optional<User> findByUserId(String userId) {
21+
return userJpaRepository.findByUserId(userId);
22+
}
23+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.loopers.interfaces.api.point;
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+
7+
public interface PointV1ApiSpec {
8+
9+
@Operation(
10+
summary = "포인트 조회",
11+
description = "헤더의 ID로 보유 포인트를 조회합니다."
12+
)
13+
ApiResponse<PointV1Dto.PointResponse> getPoint(
14+
@Schema(name = "X-USER-ID", description = "조회할 사용자 ID (헤더)")
15+
String userId
16+
);
17+
18+
@Operation(
19+
summary = "포인트 충전",
20+
description = "ID에 해당하는 사용자의 포인트를 충전합니다."
21+
)
22+
ApiResponse<PointV1Dto.PointResponse> chargePoint(
23+
PointV1Dto.ChargePointsRequest request
24+
);
25+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package com.loopers.interfaces.api.point;
2+
3+
import com.loopers.domain.point.PointService;
4+
import com.loopers.interfaces.api.ApiResponse;
5+
import com.loopers.interfaces.api.point.PointV1Dto.ChargePointsRequest;
6+
import com.loopers.interfaces.api.point.PointV1Dto.PointResponse;
7+
import com.loopers.support.error.CoreException;
8+
import com.loopers.support.error.ErrorType;
9+
import jakarta.validation.Valid;
10+
import lombok.RequiredArgsConstructor;
11+
import org.springframework.web.bind.annotation.GetMapping;
12+
import org.springframework.web.bind.annotation.PostMapping;
13+
import org.springframework.web.bind.annotation.RequestBody;
14+
import org.springframework.web.bind.annotation.RequestHeader;
15+
import org.springframework.web.bind.annotation.RequestMapping;
16+
import org.springframework.web.bind.annotation.RestController;
17+
18+
@RequiredArgsConstructor
19+
@RestController
20+
@RequestMapping("/api/v1/point")
21+
public class PointV1Controller implements PointV1ApiSpec {
22+
23+
private final PointService pointService;
24+
25+
@GetMapping()
26+
@Override
27+
public ApiResponse<PointResponse> getPoint(@RequestHeader(value = "X-USER-ID", required = false) String userId) {
28+
if (userId == null || userId.isBlank()) {
29+
throw new CoreException(ErrorType.BAD_REQUEST, "필수 헤더인 X-USER-ID가 없거나 유효하지 않습니다.");
30+
}
31+
Integer point = pointService.getPoint(userId);
32+
PointResponse response = new PointResponse(point);
33+
return ApiResponse.success(response);
34+
}
35+
36+
@PostMapping()
37+
@Override
38+
public ApiResponse<PointResponse> chargePoint(@Valid @RequestBody ChargePointsRequest request) {
39+
PointResponse response = pointService.charge(request.userId(), request.point());
40+
return ApiResponse.success(response);
41+
}
42+
43+
44+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.loopers.interfaces.api.point;
2+
3+
import jakarta.validation.constraints.NotNull;
4+
5+
public class PointV1Dto {
6+
7+
public record PointResponse(Integer point) {
8+
9+
public static PointResponse from(int chargePoint) {
10+
return new PointResponse(
11+
chargePoint
12+
);
13+
}
14+
}
15+
16+
public record ChargePointsRequest(
17+
@NotNull String userId,
18+
@NotNull int point
19+
) {}
20+
}

0 commit comments

Comments
 (0)