Skip to content

Commit c9002d8

Browse files
authored
Merge pull request #22 from YAPP-Github/fix/T3-87
[T3-87] 루틴 업데이트 엣지 케이스 추가
2 parents fe8ba95 + d5079e7 commit c9002d8

13 files changed

Lines changed: 120 additions & 54 deletions

File tree

src/main/java/bitnagil/bitnagil_backend/auth/jwt/JwtAuthenticationFilter.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
@RequiredArgsConstructor
2121
public class JwtAuthenticationFilter extends OncePerRequestFilter {
2222

23-
private final JwtProvider jwtProvider;
23+
private final JwtUtil jwtUtil;
2424
private static final String[] excludedEndpoints = new String[] {"/swagger-ui/**"};
2525

2626
@Override
@@ -37,12 +37,12 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
3737
throws IOException, ServletException {
3838

3939
// 1. Request Header 에서 토큰을 꺼냄
40-
String jwt = jwtProvider.resolveToken(request);
40+
String jwt = jwtUtil.resolveToken(request);
4141

4242
// 2. validateToken 으로 토큰 유효성 검사
4343
// 정상 토큰이면 해당 토큰으로 Authentication 을 가져와서 SecurityContext 에 저장
44-
if (StringUtils.hasText(jwt) && jwtProvider.validateToken(jwt)) {
45-
Authentication authentication = jwtProvider.getAuthentication(jwt);
44+
if (StringUtils.hasText(jwt) && jwtUtil.validateToken(jwt)) {
45+
Authentication authentication = jwtUtil.getAuthentication(jwt);
4646
SecurityContextHolder.getContext().setAuthentication(authentication);
4747
}
4848

src/main/java/bitnagil/bitnagil_backend/auth/jwt/JwtProvider.java renamed to src/main/java/bitnagil/bitnagil_backend/auth/jwt/JwtUtil.java

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package bitnagil.bitnagil_backend.auth.jwt;
22

33
import java.security.Key;
4+
import java.time.LocalDateTime;
45
import java.util.Collection;
56
import java.util.Collections;
67
import java.util.Date;
@@ -30,11 +31,10 @@
3031
import jakarta.annotation.PostConstruct;
3132
import jakarta.servlet.http.HttpServletRequest;
3233
import lombok.RequiredArgsConstructor;
33-
import lombok.extern.slf4j.Slf4j;
3434

3535
@Service
3636
@RequiredArgsConstructor
37-
public class JwtProvider {
37+
public class JwtUtil {
3838
private final AuthRedisService authRedisService;
3939

4040
@Value("${jwt.secret}")
@@ -110,13 +110,15 @@ public Authentication getAuthentication(String accessToken) {
110110

111111
// RefreshToken 혹은 AccessToken으로 인증된 유효 User 조회
112112
public User findValidUserByRefreshTokenOrAccessToken(String token) {
113+
LocalDateTime now = LocalDateTime.now();
114+
113115
// JWT에서 유저 관련 정보 추출 후, UserPk 생성
114116
UUID userId = UUID.fromString(parseClaims(token).get("userId", String.class));
115-
Long historySeq = parseClaims(token).get("userHistorySeq", Long.class);
116-
117-
HistoryPk userPk = new HistoryPk(userId, historySeq);
118117

119-
return userRepository.findByUserPk(userPk).orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_USER));
118+
return userRepository
119+
.findByUserPk_IdAndHistoryStartDateTimeLessThanAndHistoryEndDateTimeGreaterThanEqual(
120+
userId, now, now)
121+
.orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_USER));
120122
}
121123

122124
public boolean validateToken(String token) {

src/main/java/bitnagil/bitnagil_backend/auth/kakao/service/CustomOAuth2UserService.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import bitnagil.bitnagil_backend.enums.SocialType;
2121
import bitnagil.bitnagil_backend.user.domain.User;
2222
import lombok.RequiredArgsConstructor;
23+
import lombok.extern.slf4j.Slf4j;
2324

2425
/**
2526
* OAuth2 로그인 과정에서 사용자 정보를 처리하는 서비스 클래스입니다.
@@ -28,13 +29,15 @@
2829
* 외부 소셜 로그인(Kakao 등) 후 사용자 정보를 파싱하고 DB에 저장 또는 조회하여
2930
* 인증된 {@link CustomOAuth2User} 객체를 반환합니다.
3031
*/
32+
@Slf4j
3133
@Service
3234
@RequiredArgsConstructor
3335
public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
3436

3537
private static final String KAKAO = "kakao";
3638
private final UserRepository userRepository;
3739

40+
3841
/**
3942
* OAuth2 로그인 시 호출되는 메서드로, 외부 서비스에서 사용자 정보를 받아와 처리합니다.
4043
*
@@ -43,6 +46,8 @@ public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequ
4346
*/
4447
@Override
4548
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
49+
log.info("CustomOAuth2UserService 진입-----");
50+
4651

4752
OAuth2UserService<OAuth2UserRequest, OAuth2User> delegate = new DefaultOAuth2UserService();
4853
OAuth2User oAuth2User = delegate.loadUser(userRequest);

src/main/java/bitnagil/bitnagil_backend/global/config/SecurityConfig.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import bitnagil.bitnagil_backend.auth.jwt.JwtAccessDeniedHandler;
2020
import bitnagil.bitnagil_backend.auth.jwt.JwtAuthenticationEntryPoint;
2121
import bitnagil.bitnagil_backend.auth.jwt.JwtAuthenticationFilter;
22+
import bitnagil.bitnagil_backend.auth.kakao.service.CustomOAuth2UserService;
2223
import lombok.RequiredArgsConstructor;
2324

2425
@Configuration
@@ -41,6 +42,7 @@ public class SecurityConfig {
4142
private final JwtAuthenticationFilter jwtAuthenticationFilter;
4243
private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
4344
private final JwtAccessDeniedHandler jwtAccessDeniedHandler;
45+
private final CustomOAuth2UserService customOAuth2UserService;
4446

4547
@Bean
4648
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
@@ -64,6 +66,11 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
6466
.requestMatchers("/**").hasRole("USER")
6567
.anyRequest().authenticated()
6668
)
69+
.oauth2Login(oauth2 -> oauth2
70+
.userInfoEndpoint(userInfo -> userInfo
71+
.userService(customOAuth2UserService)
72+
)
73+
)
6774
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
6875

6976
return http.build();

src/main/java/bitnagil/bitnagil_backend/routine/domain/Routine.java

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import java.time.LocalDateTime;
55
import java.time.LocalTime;
66
import java.util.List;
7+
import java.util.UUID;
78

89
import bitnagil.bitnagil_backend.global.entity.BaseTimeEntity;
910
import bitnagil.bitnagil_backend.global.entity.HistoryPk;
@@ -58,24 +59,19 @@ public class Routine extends BaseTimeEntity {
5859
@NotNull
5960
private LocalDateTime historyEndDateTime;
6061

61-
@ManyToOne(fetch = FetchType.LAZY)
62-
@JoinColumns({
63-
@JoinColumn(name = "user_id", referencedColumnName = "user_id"),
64-
@JoinColumn(name = "user_history_seq", referencedColumnName = "history_seq")
65-
})
6662
@NotNull
67-
private User user;
63+
private UUID userId;
6864

6965
@Builder
7066
public Routine(HistoryPk routinePk, String name, List<DayOfWeek> repeatDay, LocalTime executionTime,
71-
LocalDateTime historyStartDateTime, LocalDateTime historyEndDateTime, User user) {
67+
LocalDateTime historyStartDateTime, LocalDateTime historyEndDateTime, UUID userId) {
7268
this.routinePk = routinePk;
7369
this.name = name;
7470
this.repeatDay = repeatDay;
7571
this.executionTime = executionTime;
7672
this.historyStartDateTime = historyStartDateTime;
7773
this.historyEndDateTime = historyEndDateTime;
78-
this.user = user;
74+
this.userId = userId;
7975
}
8076

8177
// 이전 루틴의 이력 종료일시를 갱신

src/main/java/bitnagil/bitnagil_backend/routine/domain/SubRoutine.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ public class SubRoutine extends BaseTimeEntity {
3737
@NotNull
3838
private String name;
3939

40+
@NotNull
41+
private Integer sortOrder;
42+
4043
@NotNull
4144
private LocalDateTime historyStartDateTime;
4245

@@ -47,10 +50,11 @@ public class SubRoutine extends BaseTimeEntity {
4750
private UUID routineId;
4851

4952
@Builder
50-
public SubRoutine(HistoryPk subRoutinePk, String name, LocalDateTime historyStartDateTime, LocalDateTime historyEndDateTime,
51-
UUID routineId) {
53+
public SubRoutine(HistoryPk subRoutinePk, String name, Integer sortOrder, LocalDateTime historyStartDateTime,
54+
LocalDateTime historyEndDateTime, UUID routineId) {
5255
this.subRoutinePk = subRoutinePk;
5356
this.name = name;
57+
this.sortOrder = sortOrder;
5458
this.historyStartDateTime = historyStartDateTime;
5559
this.historyEndDateTime = historyEndDateTime;
5660
this.routineId = routineId;
@@ -60,4 +64,10 @@ public SubRoutine(HistoryPk subRoutinePk, String name, LocalDateTime historyStar
6064
public void updateHistoryEndDateTime(LocalDateTime updateDateTime) {
6165
this.historyEndDateTime = updateDateTime;
6266
}
67+
68+
// 서브루틴 순서 갱신
69+
public void updateSortOrder(Integer sortOrder) {
70+
this.sortOrder = sortOrder;
71+
}
72+
6373
}

src/main/java/bitnagil/bitnagil_backend/routine/request/SubRoutineInfo.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@
1010
public class SubRoutineInfo {
1111
private UUID subRoutineId;
1212
private String subRoutineName;
13+
private Integer sortOrder;
1314
}

src/main/java/bitnagil/bitnagil_backend/routine/request/UpdateRoutineRequest.java

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,16 @@ public class UpdateRoutineRequest{
3939
@NotNull
4040
private LocalTime executionTime;
4141

42-
@Schema(description = "세부 루틴 이름에 대한 리스트입니다.",
43-
example = "[{\"subRoutineId\": \"4fa85f64-5717-4562-b3fc-2c963f66afa6\", \"subRoutineName\": \"손 씻기\"}, " +
44-
"{\"subRoutineId\": \"4fa85f64-5717-4562-b3fc-2c963f66afa6\", \"subRoutineName\": \"침대 정리하기\"}, " +
45-
"{\"subRoutineId\": \"4fa85f64-5717-4562-b3fc-2c963f66afa6\", \"subRoutineName\": null}]",
46-
required = true)
42+
@Schema(
43+
description = "세부 루틴 수정에 대한 정보입니다. 각각 기존 루틴 유지, 삭제, 새로운 루틴 추가 예시입니다.",
44+
example = "["
45+
+ "{\"subRoutineId\": \"4fa85f64-5717-4562-b3fc-2c963f66afa6\", \"subRoutineName\": \"손 씻기\", \"sortOrder\": 1},"
46+
+ "{\"subRoutineId\": \"4fa85f64-5717-4562-b3fc-2c963f66afa6\", \"subRoutineName\": null, \"sortOrder\": null},"
47+
+ "{\"subRoutineId\": null, \"subRoutineName\": \"침대 정리하기\", \"sortOrder\": 2}"
48+
+ "]",
49+
required = true
50+
)
4751
@NotNull
4852
private List<SubRoutineInfo> subRoutineInfos;
53+
4954
}

src/main/java/bitnagil/bitnagil_backend/routine/service/RoutineService.java

Lines changed: 47 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -52,24 +52,50 @@ public void updateRoutine(User user, UpdateRoutineRequest request) {
5252
addUpdatedRoutine(user, request, previousRoutine, now);
5353
}
5454

55-
// 갱신할 서브 루틴이 있는지 탐색 및 갱신 수행
55+
// 서브루틴 갱신
5656
for (SubRoutineInfo subRoutineInfo : request.getSubRoutineInfos()) {
5757

58-
SubRoutine previousSubRoutine = subRoutineRepository
59-
.findBySubRoutinePk_IdAndHistoryStartDateTimeLessThanAndHistoryEndDateTimeGreaterThanEqual(
60-
subRoutineInfo.getSubRoutineId(), now, now)
61-
.orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_SUB_ROUTINE));
62-
63-
// 갱신할 서브 루틴명이 null이면 해당 서브 루틴을 삭제
64-
if (subRoutineInfo.getSubRoutineName() == null) {
65-
previousSubRoutine.updateHistoryEndDateTime(now);
66-
continue;
58+
// 기존 서브루틴 변경 및 유지
59+
if (subRoutineInfo.getSubRoutineId() != null && subRoutineInfo.getSubRoutineName() != null) {
60+
SubRoutine previousSubRoutine = subRoutineRepository
61+
.findBySubRoutinePk_IdAndHistoryStartDateTimeLessThanAndHistoryEndDateTimeGreaterThanEqual(
62+
subRoutineInfo.getSubRoutineId(), now, now)
63+
.orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_SUB_ROUTINE));
64+
65+
// 기존 서브루틴의 이름을 변경한 경우 (이력 갱신)
66+
if (!subRoutineInfo.getSubRoutineName().equals(previousSubRoutine.getName())) {
67+
previousSubRoutine.updateHistoryEndDateTime(now);
68+
addUpdatedSubRoutine(subRoutineInfo, previousSubRoutine, now);
69+
}
70+
// 기존 서브루틴의 이름을 유지하고, 정렬 순서가 변경된 경우
71+
if (subRoutineInfo.getSubRoutineName().equals(previousSubRoutine.getName()) &&
72+
!previousSubRoutine.getSortOrder().equals(subRoutineInfo.getSortOrder())) {
73+
previousSubRoutine.updateSortOrder(subRoutineInfo.getSortOrder());
74+
}
6775
}
6876

69-
if (!subRoutineInfo.getSubRoutineName().equals(previousSubRoutine.getName())) {
77+
// 기존 서브루틴 삭제
78+
if (subRoutineInfo.getSubRoutineId() != null && subRoutineInfo.getSubRoutineName() == null) {
79+
SubRoutine removeSubRoutine = subRoutineRepository
80+
.findBySubRoutinePk_IdAndHistoryStartDateTimeLessThanAndHistoryEndDateTimeGreaterThanEqual(
81+
subRoutineInfo.getSubRoutineId(), now, now)
82+
.orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_SUB_ROUTINE));
83+
84+
removeSubRoutine.updateHistoryEndDateTime(now);
85+
}
7086

71-
previousSubRoutine.updateHistoryEndDateTime(now);
72-
addUpdatedSubRoutine(subRoutineInfo, previousSubRoutine, now);
87+
// 새로운 서브루틴 추가
88+
if (subRoutineInfo.getSubRoutineId() == null && subRoutineInfo.getSubRoutineName() != null) {
89+
SubRoutine newSubRoutine = SubRoutine.builder()
90+
.subRoutinePk(new HistoryPk(UUID.randomUUID(), 1L))
91+
.name(subRoutineInfo.getSubRoutineName())
92+
.sortOrder(subRoutineInfo.getSortOrder())
93+
.historyStartDateTime(now)
94+
.historyEndDateTime(TimeUtils.END_DATE_TIME)
95+
.routineId(previousRoutine.getRoutinePk().getId())
96+
.build();
97+
98+
subRoutineRepository.save(newSubRoutine);
7399
}
74100
}
75101
}
@@ -93,12 +119,13 @@ public void deleteRoutine(User user, UUID routineId) {
93119
private void addUpdatedSubRoutine(SubRoutineInfo subRoutineInfo, SubRoutine previousSubRoutine,
94120
LocalDateTime now) {
95121
// 서브루틴을 갱신하여 새로운 Row 추가
96-
HistoryPk nextSubRoutinePk = new HistoryPk(previousSubRoutine.getSubRoutinePk().getId(),
122+
HistoryPk subRoutinePk = new HistoryPk(previousSubRoutine.getSubRoutinePk().getId(),
97123
previousSubRoutine.getSubRoutinePk().getHistorySeq() + 1);
98124

99125
SubRoutine updateSubRoutine = SubRoutine.builder()
100-
.subRoutinePk(nextSubRoutinePk)
126+
.subRoutinePk(subRoutinePk)
101127
.name(subRoutineInfo.getSubRoutineName())
128+
.sortOrder(subRoutineInfo.getSortOrder())
102129
.historyStartDateTime(now)
103130
.historyEndDateTime(TimeUtils.END_DATE_TIME)
104131
.routineId(previousSubRoutine.getRoutineId())
@@ -125,7 +152,7 @@ private void addUpdatedRoutine(User user, UpdateRoutineRequest request, Routine
125152
previousRoutine.getExecutionTime() : request.getExecutionTime())
126153
.historyStartDateTime(now)
127154
.historyEndDateTime(TimeUtils.END_DATE_TIME)
128-
.user(user)
155+
.userId(user.getUserPk().getId())
129156
.build();
130157

131158
routineRepository.save(updateRoutine);
@@ -146,7 +173,7 @@ private Routine validateRoutineOwnership(UUID routineId, User user, LocalDateTim
146173
routineId, now, now)
147174
.orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_ROUTINE));
148175

149-
if (!user.getUserPk().equals(routine.getUser().getUserPk())) {
176+
if (!user.getUserPk().getId().equals(routine.getUserId())) {
150177
throw new CustomException(ErrorCode.ROUTINE_USER_NOT_MATCHED);
151178
}
152179

@@ -163,17 +190,19 @@ private Routine saveRoutine(User user, RegisterRoutineRequest request, LocalDate
163190
.executionTime(request.getExecutionTime())
164191
.historyStartDateTime(now)
165192
.historyEndDateTime(TimeUtils.END_DATE_TIME)
166-
.user(user)
193+
.userId(user.getUserPk().getId())
167194
.build();
168195

169196
return routineRepository.save(routine);
170197
}
171198

172199
private void saveSubRoutine(List<String> subRoutineNames, Routine routine, LocalDateTime now) {
200+
int sortOrder = 1;
173201
for (String subRoutineName : subRoutineNames) {
174202
SubRoutine subRoutine = SubRoutine.builder()
175203
.subRoutinePk(new HistoryPk(UUID.randomUUID(), 1L))
176204
.name(subRoutineName)
205+
.sortOrder(sortOrder++)
177206
.historyStartDateTime(now)
178207
.historyEndDateTime(TimeUtils.END_DATE_TIME)
179208
.routineId(routine.getRoutinePk().getId())

src/main/java/bitnagil/bitnagil_backend/user/repository/UserRepository.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import java.time.LocalDateTime;
44
import java.util.Optional;
5+
import java.util.UUID;
56

67
import org.springframework.data.jpa.repository.JpaRepository;
78
import org.springframework.stereotype.Repository;
@@ -19,5 +20,9 @@ public interface UserRepository extends JpaRepository<User, HistoryPk> {
1920
Optional<User> findBySocialTypeAndSocialIdAndHistoryStartDateTimeLessThanAndHistoryEndDateTimeGreaterThanEqual(
2021
SocialType socialType, String socialId, LocalDateTime historyStartDateBound, LocalDateTime historyEndDateBound);
2122

23+
// socialId 기반으로 유저 이력들 특정 후, 이력 시작일시 및 종료일시를 활용해서 현재시간 기준으로 유효한 유저 식별
24+
Optional<User> findByUserPk_IdAndHistoryStartDateTimeLessThanAndHistoryEndDateTimeGreaterThanEqual(
25+
UUID userId, LocalDateTime historyStartDateBound, LocalDateTime historyEndDateBound);
26+
2227
Optional<User> findByUserPk(HistoryPk userPk);
2328
}

0 commit comments

Comments
 (0)