-
Notifications
You must be signed in to change notification settings - Fork 1
[BE-Fix] 캘린더 연동 시 발생하는 버그 수정 #419
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
e5b22c0
2653c76
b811f80
1e50c96
4aee80f
117e008
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,56 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| package endolphin.backend.domain.calendar; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import endolphin.backend.domain.calendar.dto.CalendarResponse; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import endolphin.backend.domain.calendar.entity.Calendar; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import endolphin.backend.domain.calendar.event.GoogleCalendarLinkEvent; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import endolphin.backend.domain.user.UserService; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import endolphin.backend.domain.user.entity.User; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import endolphin.backend.global.dto.ListResponse; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import endolphin.backend.global.google.GoogleCalendarService; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import endolphin.backend.global.google.GoogleOAuthService; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import endolphin.backend.global.google.dto.GoogleCalendarDto; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import endolphin.backend.global.google.dto.GoogleTokens; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import endolphin.backend.global.google.dto.GoogleUserInfo; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.List; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import lombok.RequiredArgsConstructor; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.context.ApplicationEventPublisher; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.stereotype.Service; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Service | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @RequiredArgsConstructor | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public class CalendarFacade { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private final CalendarService calendarService; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private final UserService userService; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private final GoogleCalendarService googleCalendarService; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private final GoogleOAuthService googleOAuthService; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private final ApplicationEventPublisher eventPublisher; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public void linkGoogleCalendar(String code) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (code == null || code.isBlank()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| GoogleTokens tokens = googleOAuthService.getGoogleTokens(code, false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| GoogleUserInfo userInfo = googleOAuthService.getUserInfo(tokens.accessToken()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| User user = userService.getUserByEmail(userInfo.email()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| user = userService.updateTokens(user, tokens.accessToken(), tokens.refreshToken()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| GoogleCalendarDto calendarDto = googleCalendarService.getPrimaryCalendar(user); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| calendarService.attachGoogleCalendar(calendarDto, user); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| eventPublisher.publishEvent(new GoogleCalendarLinkEvent(user)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+30
to
+45
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 좋은 facade 패턴 구현이지만 에러 처리와 로깅 개선이 필요합니다.
다음과 같은 개선을 고려해보세요: public void linkGoogleCalendar(String code) {
if (code == null || code.isBlank()) {
+ log.warn("[linkGoogleCalendar] Invalid authorization code provided");
return;
}
+
+ try {
+ log.info("[linkGoogleCalendar] Starting Google Calendar linking process");
GoogleTokens tokens = googleOAuthService.getGoogleTokens(code, false);
GoogleUserInfo userInfo = googleOAuthService.getUserInfo(tokens.accessToken());
-
User user = userService.getUserByEmail(userInfo.email());
user = userService.updateTokens(user, tokens.accessToken(), tokens.refreshToken());
GoogleCalendarDto calendarDto = googleCalendarService.getPrimaryCalendar(user);
calendarService.attachGoogleCalendar(calendarDto, user);
+ log.info("[linkGoogleCalendar] Successfully linked calendar for user: {}", user.getEmail());
eventPublisher.publishEvent(new GoogleCalendarLinkEvent(user));
+ } catch (Exception e) {
+ log.error("[linkGoogleCalendar] Failed to link Google Calendar: {}", e.getMessage(), e);
+ throw e;
+ }
}또한 이 메서드가 트랜잭션 내에서 실행되어야 하는지 고려해보세요: +@Transactional
public void linkGoogleCalendar(String code) {🤖 Prompt for AI Agents🛠️ Refactor suggestion Implement comprehensive error handling for Google API operations. Based on the retrieved learnings and Google Calendar integration best practices, this method needs robust error handling for:
Consider wrapping the Google API calls in try-catch blocks and implementing proper rollback mechanisms. public void linkGoogleCalendar(String code) {
if (code == null || code.isBlank()) {
return;
}
- GoogleTokens tokens = googleOAuthService.getGoogleTokens(code, false);
- GoogleUserInfo userInfo = googleOAuthService.getUserInfo(tokens.accessToken());
-
-
- User user = userService.getUserByEmail(userInfo.email());
- user = userService.updateTokens(user, tokens.accessToken(), tokens.refreshToken());
-
- GoogleCalendarDto calendarDto = googleCalendarService.getPrimaryCalendar(user);
- calendarService.attachGoogleCalendar(calendarDto, user);
-
- eventPublisher.publishEvent(new GoogleCalendarLinkEvent(user));
+ try {
+ GoogleTokens tokens = googleOAuthService.getGoogleTokens(code, false);
+ GoogleUserInfo userInfo = googleOAuthService.getUserInfo(tokens.accessToken());
+
+ User user = userService.getUserByEmail(userInfo.email());
+ user = userService.updateTokens(user, tokens.accessToken(), tokens.refreshToken());
+
+ GoogleCalendarDto calendarDto = googleCalendarService.getPrimaryCalendar(user);
+ calendarService.attachGoogleCalendar(calendarDto, user);
+
+ eventPublisher.publishEvent(new GoogleCalendarLinkEvent(user));
+ } catch (GoogleJsonResponseException e) {
+ log.error("Google API error during calendar linking: {}", e.getMessage());
+ throw new CalendarException(ErrorCode.GC_API_ERROR, e);
+ } catch (IOException e) {
+ log.error("Network error during calendar linking: {}", e.getMessage());
+ throw new CalendarException(ErrorCode.NETWORK_ERROR, e);
+ }
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public ListResponse<CalendarResponse> getCalendarList() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| User user = userService.getCurrentUser(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| List<Calendar> calendars = calendarService.findAllByUserId(user.getId()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| List<CalendarResponse> response = calendars.stream() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .map(c -> new CalendarResponse(c.getName())).toList(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return new ListResponse<>(response); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -44,6 +44,16 @@ public void updateAccessToken(User user, String accessToken) { | |||||||||||||||||||||||||||||||||||||||
| userRepository.save(user); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| public User updateTokens(User user, String accessToken, String refreshToken) { | ||||||||||||||||||||||||||||||||||||||||
| if (user.getAccessToken() != null) { | ||||||||||||||||||||||||||||||||||||||||
| return user; | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| user.setAccessToken(accessToken); | ||||||||||||||||||||||||||||||||||||||||
| user.setRefreshToken(refreshToken); | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| return userRepository.save(user); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+47
to
+55
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 토큰 업데이트 로직 개선이 필요합니다. 새로운 다음과 같이 개선하는 것을 고려해보세요: public User updateTokens(User user, String accessToken, String refreshToken) {
- if (user.getAccessToken() != null) {
+ if (user.getAccessToken() != null && user.getRefreshToken() != null) {
return user;
}
user.setAccessToken(accessToken);
user.setRefreshToken(refreshToken);
return userRepository.save(user);
}또는 더 명확하게 하려면: public User updateTokens(User user, String accessToken, String refreshToken) {
- if (user.getAccessToken() != null) {
+ if (user.getAccessToken() != null && user.getRefreshToken() != null) {
return user;
}
+ if (accessToken != null) {
user.setAccessToken(accessToken);
+ }
+ if (refreshToken != null) {
user.setRefreshToken(refreshToken);
+ }
return userRepository.save(user);
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| public LoginUserDto upsertUser(GoogleUserInfo userInfo) { | ||||||||||||||||||||||||||||||||||||||||
| Optional<User> userOpt = userRepository.findByEmail(userInfo.email()); | ||||||||||||||||||||||||||||||||||||||||
| Boolean isFirstLogin = userOpt.isEmpty(); | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -34,14 +34,12 @@ | |||||||||||||
| import org.springframework.http.HttpStatus; | ||||||||||||||
| import org.springframework.http.ResponseEntity; | ||||||||||||||
| import org.springframework.stereotype.Service; | ||||||||||||||
| import org.springframework.transaction.annotation.Transactional; | ||||||||||||||
|
|
||||||||||||||
| import java.util.List; | ||||||||||||||
| import java.util.UUID; | ||||||||||||||
|
|
||||||||||||||
| @Service | ||||||||||||||
| @RequiredArgsConstructor | ||||||||||||||
| @Transactional | ||||||||||||||
| @Slf4j | ||||||||||||||
| public class GoogleCalendarService { | ||||||||||||||
|
|
||||||||||||||
|
|
@@ -55,13 +53,6 @@ public class GoogleCalendarService { | |||||||||||||
| private final RetryExecutor retryExecutor; | ||||||||||||||
| private final ApplicationEventPublisher eventPublisher; | ||||||||||||||
|
|
||||||||||||||
| public void upsertGoogleCalendar(User user) { | ||||||||||||||
| GoogleCalendarDto googleCalendarDto = getPrimaryCalendar(user); | ||||||||||||||
| Calendar calendar = calendarService.attachGoogleCalendar(googleCalendarDto, user); | ||||||||||||||
| syncWithCalendar(calendar.getCalendarId(), user); | ||||||||||||||
| subscribeToCalendar(calendar, user); | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| public void subscribeGoogleCalendar(User user) { | ||||||||||||||
| Calendar calendar = calendarService.getCalendarByUserId(user.getId()); | ||||||||||||||
| subscribeToCalendar(calendar, user); | ||||||||||||||
|
|
@@ -146,8 +137,8 @@ public GoogleCalendarDto getPrimaryCalendar(User user) { | |||||||||||||
|
|
||||||||||||||
| return retryExecutor.executeCalendarApiWithRetry( | ||||||||||||||
| () -> { | ||||||||||||||
| GoogleCalendarDto result = googleCalendarApi.getPrimaryCalendar( | ||||||||||||||
| eventsUrl, user.getAccessToken()); | ||||||||||||||
| GoogleCalendarDto result = | ||||||||||||||
| googleCalendarApi.getPrimaryCalendar(eventsUrl, user.getAccessToken()); | ||||||||||||||
|
Comment on lines
+140
to
+141
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainVerify transaction management after @transactional removal. The formatting change is fine, but the removal of
🏁 Script executed: #!/bin/bash
# Verify that CalendarFacade or calling methods have proper transaction management
ast-grep --pattern $'@Transactional
class CalendarFacade {
$$$
}'
# Check if calendarService calls are within transactional contexts
ast-grep --pattern $'calendarService.$_($$$)'Length of output: 3564 🏁 Script executed: #!/bin/bash
# Check for @Transactional annotation on CalendarService
rg '@Transactional' -n backend/src/main/java/endolphin/backend/domain/calendar/CalendarService.java || echo "No @Transactional in CalendarService"
# Check for @Service annotation on CalendarService
rg '@Service' -n backend/src/main/java/endolphin/backend/domain/calendar/CalendarService.java || echo "No @Service in CalendarService"Length of output: 418 Reintroduce The removal of Locations to update:
Suggested diff: @@ backend/src/main/java/endolphin/backend/global/google/GoogleCalendarService.java
- public class GoogleCalendarService {
+ @Transactional
+ public class GoogleCalendarService {📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||
| log.info("[getPrimaryCalendar] success: {}", result); | ||||||||||||||
| return result; | ||||||||||||||
| }, user, null | ||||||||||||||
|
|
||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. before commit으로 하신 이유가 있을까여?
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 잘 몰라서 그냥 질문임당
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 기존에 비동기로 처리할 때는 AFTER_COMMIT이어도 다른 쓰레드로 실행되어 새로운 트랜잭션을 시작하게 됩니다. 하지만 동기로 처리할 때 AFTER_COMMIT인 상태라면 이전의 트랜잭션 내에서 실행하게 되는데, 이 때 commit이 실행된 이후이기 때문에 이후에 실행되는 SQL이 commit되지 않는 문제가 있다고 합니다. 그래서 BEFORE_COMMIT으로 하게 되었습니다. 그런데 더 찾아보니 현재 구조에서 BEFORE_COMMIT을 하면 문제가 생길 거 같아서 수정하고 다시 올리겠습니다 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,5 @@ | ||
| package endolphin.backend.global.google.event.handler; | ||
|
|
||
| import endolphin.backend.domain.calendar.entity.Calendar; | ||
| import endolphin.backend.domain.calendar.event.GoogleCalendarLinkEvent; | ||
| import endolphin.backend.domain.personal_event.entity.PersonalEvent; | ||
| import endolphin.backend.domain.personal_event.event.DeletePersonalEvent; | ||
|
|
@@ -11,6 +10,7 @@ | |
| import endolphin.backend.domain.personal_event.event.InsertPersonalEvent; | ||
| import java.util.List; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.context.event.EventListener; | ||
| import org.springframework.scheduling.annotation.Async; | ||
| import org.springframework.stereotype.Component; | ||
| import org.springframework.transaction.event.TransactionalEventListener; | ||
|
|
@@ -30,15 +30,15 @@ public void insert(InsertPersonalEvent event) { | |
| } | ||
|
|
||
| @Async | ||
| @TransactionalEventListener(classes = {GoogleCalendarLinkEvent.class}) | ||
| public void link(GoogleCalendarLinkEvent event) { | ||
| @TransactionalEventListener(classes = {LoginEvent.class}) | ||
| public void link(LoginEvent event) { | ||
| User user = event.user(); | ||
| googleCalendarService.upsertGoogleCalendar(user); | ||
| googleCalendarService.subscribeGoogleCalendar(user); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainGoogleCalendarService의 메서드 변경 확인 필요
🏁 Script executed: #!/bin/bash
# Description: subscribeGoogleCalendar 메서드의 구현을 확인합니다.
# GoogleCalendarService에서 새로운 메서드 검색
ast-grep --pattern 'subscribeGoogleCalendar($_) {
$$$
}'
# 이전 upsertGoogleCalendar 메서드가 제거되었는지 확인
rg -A 5 "upsertGoogleCalendar"Length of output: 1572 Missing subscribeGoogleCalendar implementation in GoogleCalendarService The Please add or locate the
And update the tests in:
so that they no longer reference 🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| @Async | ||
| @TransactionalEventListener(classes = {LoginEvent.class}) | ||
| public void link(LoginEvent event) { | ||
| @EventListener(classes = {GoogleCalendarLinkEvent.class}) | ||
| public void link(GoogleCalendarLinkEvent event) { | ||
| User user = event.user(); | ||
| googleCalendarService.subscribeGoogleCalendar(user); | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add transaction management to the facade.
The facade orchestrates multiple service calls that modify data but lacks
@Transactionalannotation. If any step inlinkGoogleCalendarfails (e.g., after token update but before calendar attachment), it could leave the system in an inconsistent state.🤖 Prompt for AI Agents