From 21474c78c702242165f3c53f03580d91f3a5d3b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Fri, 6 Mar 2026 15:24:02 +0900 Subject: [PATCH 1/8] =?UTF-8?q?fix:=207=EC=9D=BC=20=EB=B3=B5=EA=B5=AC=20?= =?UTF-8?q?=EC=A0=95=EC=B1=85=20=EC=9C=84=ED=95=B4=20Apple=20=ED=86=A0?= =?UTF-8?q?=ED=81=B0=20=EC=A6=89=EC=8B=9C=20revoke=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/gg/agit/konect/domain/user/service/UserService.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/gg/agit/konect/domain/user/service/UserService.java b/src/main/java/gg/agit/konect/domain/user/service/UserService.java index 58214ebf..b92c9424 100644 --- a/src/main/java/gg/agit/konect/domain/user/service/UserService.java +++ b/src/main/java/gg/agit/konect/domain/user/service/UserService.java @@ -216,9 +216,8 @@ public void deleteUser(Integer userId) { validateNotClubPresident(userId); - if (user.getProvider() == Provider.APPLE) { - appleTokenRevocationService.revoke(user.getAppleRefreshToken()); - } + // Apple 토큰은 7일 복구 정책을 위해 즉시 revoke하지 않음 + // 스케줄러가 7일 경과 후에 revoke 처리 user.withdraw(LocalDateTime.now()); userRepository.save(user); From 74d1d85627404ed7260ff5da05e9db4784de8c31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Fri, 6 Mar 2026 15:24:09 +0900 Subject: [PATCH 2/8] =?UTF-8?q?chore:=207=EC=9D=BC=20=EA=B2=BD=EA=B3=BC=20?= =?UTF-8?q?Apple=20=ED=83=88=ED=87=B4=20=EC=9C=A0=EC=A0=80=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EC=BF=BC=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/user/repository/UserRepository.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/main/java/gg/agit/konect/domain/user/repository/UserRepository.java b/src/main/java/gg/agit/konect/domain/user/repository/UserRepository.java index 7b06a292..24ac2baf 100644 --- a/src/main/java/gg/agit/konect/domain/user/repository/UserRepository.java +++ b/src/main/java/gg/agit/konect/domain/user/repository/UserRepository.java @@ -1,7 +1,7 @@ package gg.agit.konect.domain.user.repository; +import java.time.LocalDateTime; import java.util.List; -import java.util.Optional; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.Repository; @@ -143,4 +143,16 @@ List findUserIdsByUniversityAndStudentYear( AND u.deletedAt IS NULL """) List findAllByIdIn(@Param("ids") List ids); + + @Query(""" + SELECT u + FROM User u + WHERE u.provider = :provider + AND u.deletedAt IS NOT NULL + AND u.deletedAt < :threshold + """) + List findByProviderAndDeletedAtBefore( + @Param("provider") Provider provider, + @Param("threshold") LocalDateTime threshold + ); } From d0b290a9d507186d437e58c53bacd1bc42bcd444 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Fri, 6 Mar 2026 15:24:15 +0900 Subject: [PATCH 3/8] =?UTF-8?q?feat:=20=ED=83=88=ED=87=B4=207=EC=9D=BC=20?= =?UTF-8?q?=ED=9B=84=20Apple=20=ED=86=A0=ED=81=B0=20revoke=20=EC=8A=A4?= =?UTF-8?q?=EC=BC=80=EC=A4=84=EB=9F=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/user/scheduler/UserScheduler.java | 32 +++++++++++ .../user/service/UserSchedulerService.java | 55 +++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 src/main/java/gg/agit/konect/domain/user/scheduler/UserScheduler.java create mode 100644 src/main/java/gg/agit/konect/domain/user/service/UserSchedulerService.java diff --git a/src/main/java/gg/agit/konect/domain/user/scheduler/UserScheduler.java b/src/main/java/gg/agit/konect/domain/user/scheduler/UserScheduler.java new file mode 100644 index 00000000..04fc3f30 --- /dev/null +++ b/src/main/java/gg/agit/konect/domain/user/scheduler/UserScheduler.java @@ -0,0 +1,32 @@ +package gg.agit.konect.domain.user.scheduler; + +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import gg.agit.konect.domain.user.service.UserSchedulerService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Component +@RequiredArgsConstructor +public class UserScheduler { + + private final UserSchedulerService userSchedulerService; + + /** + * 매일 자정(한국 시간 00:00)에 실행되어 7일 경과한 Apple 사용자 토큰을 revoke합니다. + * cron 표현식: 초 분 시 일 월 요일 + * 0 0 0 * * *: 매일 00:00:00 실행 + */ + @Scheduled(cron = "0 0 0 * * *", zone = "Asia/Seoul") + public void revokeAppleTokensAfterRestoreWindow() { + try { + log.info("Starting Apple token revocation task for users withdrawn more than 7 days ago"); + userSchedulerService.revokeAppleTokensAfterRestoreWindow(); + log.info("Successfully completed Apple token revocation task"); + } catch (Exception e) { + log.error("Failed to revoke Apple tokens for withdrawn users", e); + } + } +} diff --git a/src/main/java/gg/agit/konect/domain/user/service/UserSchedulerService.java b/src/main/java/gg/agit/konect/domain/user/service/UserSchedulerService.java new file mode 100644 index 00000000..22bae212 --- /dev/null +++ b/src/main/java/gg/agit/konect/domain/user/service/UserSchedulerService.java @@ -0,0 +1,55 @@ +package gg.agit.konect.domain.user.service; + +import static gg.agit.konect.domain.user.model.Provider.*; + +import java.time.LocalDateTime; +import java.util.List; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import gg.agit.konect.domain.user.model.Provider; +import gg.agit.konect.domain.user.model.User; +import gg.agit.konect.domain.user.repository.UserRepository; +import gg.agit.konect.infrastructure.oauth.AppleTokenRevocationService; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class UserSchedulerService { + + private static final int REVOKE_AFTER_DAYS = 7; + + private final UserRepository userRepository; + private final AppleTokenRevocationService appleTokenRevocationService; + + /** + * 7일 이상 경과한 Apple 사용자의 토큰을 revoke합니다. + * - 7일 복구 정책: 탈퇴 후 7일 이내 복구 가능하므로 즉시 revoke하지 않음 + * - 7일 경과 후: 복구 불가 시점이므로 Apple 토큰 영구 폐기 + */ + @Transactional + public void revokeAppleTokensAfterRestoreWindow() { + LocalDateTime threshold = LocalDateTime.now().minusDays(REVOKE_AFTER_DAYS); + List usersToRevoke = userRepository.findByProviderAndDeletedAtBefore( + APPLE, + threshold + ); + + if (usersToRevoke.isEmpty()) { + return; + } + + for (User user : usersToRevoke) { + try { + if (user.getAppleRefreshToken() != null) { + appleTokenRevocationService.revoke(user.getAppleRefreshToken()); + } + } catch (Exception e) { + // 개별 사용자의 토큰 revoke 실패 시 다른 사용자 처리 계속 + // 로깅은 스케줄러에서 처리 + throw e; + } + } + } +} From 55f7d18312e5bdbd7605016e3e55089e27b6bbb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Fri, 6 Mar 2026 15:58:23 +0900 Subject: [PATCH 4/8] =?UTF-8?q?chore:=20=EC=BD=94=EB=93=9C=20=ED=8F=AC?= =?UTF-8?q?=EB=A7=B7=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/gg/agit/konect/domain/user/controller/UserApi.java | 4 ++-- .../agit/konect/domain/user/controller/UserController.java | 4 ++-- .../agit/konect/domain/user/repository/UserRepository.java | 5 +++-- .../konect/domain/user/service/UserSchedulerService.java | 6 ++---- .../gg/agit/konect/domain/user/service/UserService.java | 2 +- 5 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/main/java/gg/agit/konect/domain/user/controller/UserApi.java b/src/main/java/gg/agit/konect/domain/user/controller/UserApi.java index 208631a3..22734e55 100644 --- a/src/main/java/gg/agit/konect/domain/user/controller/UserApi.java +++ b/src/main/java/gg/agit/konect/domain/user/controller/UserApi.java @@ -7,8 +7,8 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; -import gg.agit.konect.domain.user.dto.SignupRequest; import gg.agit.konect.domain.user.dto.SignupPrefillResponse; +import gg.agit.konect.domain.user.dto.SignupRequest; import gg.agit.konect.domain.user.dto.UserAccessTokenResponse; import gg.agit.konect.domain.user.dto.UserInfoResponse; import gg.agit.konect.global.auth.annotation.PublicApi; @@ -27,7 +27,7 @@ public interface UserApi { summary = "추가 정보가 필요한 사용자의 정보를 받아 회원가입을 진행한다.", description = """ 추가 정보를 입력받아 회원가입을 완료합니다. - + - `INVALID_SIGNUP_TOKEN` (401): 회원가입 토큰이 없거나 올바르지 않은 경우 - `INVALID_REQUEST_BODY` (400): 요청 본문의 형식이 올바르지 않거나 필수 값이 누락된 경우 - `DUPLICATE_STUDENT_NUMBER` (409): 동일 대학교 + 학번 조합이 이미 존재하는 경우 diff --git a/src/main/java/gg/agit/konect/domain/user/controller/UserController.java b/src/main/java/gg/agit/konect/domain/user/controller/UserController.java index 67225601..b7f7fbed 100644 --- a/src/main/java/gg/agit/konect/domain/user/controller/UserController.java +++ b/src/main/java/gg/agit/konect/domain/user/controller/UserController.java @@ -5,17 +5,17 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import gg.agit.konect.domain.user.dto.SignupRequest; import gg.agit.konect.domain.user.dto.SignupPrefillResponse; +import gg.agit.konect.domain.user.dto.SignupRequest; import gg.agit.konect.domain.user.dto.UserAccessTokenResponse; import gg.agit.konect.domain.user.dto.UserInfoResponse; import gg.agit.konect.domain.user.service.RefreshTokenService; import gg.agit.konect.domain.user.service.SignupTokenService; import gg.agit.konect.domain.user.service.UserActivityService; import gg.agit.konect.domain.user.service.UserService; -import gg.agit.konect.global.auth.jwt.JwtProvider; import gg.agit.konect.global.auth.annotation.PublicApi; import gg.agit.konect.global.auth.annotation.UserId; +import gg.agit.konect.global.auth.jwt.JwtProvider; import gg.agit.konect.global.auth.web.AuthCookieService; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; diff --git a/src/main/java/gg/agit/konect/domain/user/repository/UserRepository.java b/src/main/java/gg/agit/konect/domain/user/repository/UserRepository.java index 24ac2baf..301b6d50 100644 --- a/src/main/java/gg/agit/konect/domain/user/repository/UserRepository.java +++ b/src/main/java/gg/agit/konect/domain/user/repository/UserRepository.java @@ -2,16 +2,17 @@ import java.time.LocalDateTime; import java.util.List; +import java.util.Optional; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.Repository; import org.springframework.data.repository.query.Param; -import gg.agit.konect.global.code.ApiResponseCode; -import gg.agit.konect.global.exception.CustomException; import gg.agit.konect.domain.user.enums.Provider; import gg.agit.konect.domain.user.enums.UserRole; import gg.agit.konect.domain.user.model.User; +import gg.agit.konect.global.code.ApiResponseCode; +import gg.agit.konect.global.exception.CustomException; public interface UserRepository extends Repository { diff --git a/src/main/java/gg/agit/konect/domain/user/service/UserSchedulerService.java b/src/main/java/gg/agit/konect/domain/user/service/UserSchedulerService.java index 22bae212..a8d51d9a 100644 --- a/src/main/java/gg/agit/konect/domain/user/service/UserSchedulerService.java +++ b/src/main/java/gg/agit/konect/domain/user/service/UserSchedulerService.java @@ -1,14 +1,12 @@ package gg.agit.konect.domain.user.service; -import static gg.agit.konect.domain.user.model.Provider.*; - import java.time.LocalDateTime; import java.util.List; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import gg.agit.konect.domain.user.model.Provider; +import gg.agit.konect.domain.user.enums.Provider; import gg.agit.konect.domain.user.model.User; import gg.agit.konect.domain.user.repository.UserRepository; import gg.agit.konect.infrastructure.oauth.AppleTokenRevocationService; @@ -32,7 +30,7 @@ public class UserSchedulerService { public void revokeAppleTokensAfterRestoreWindow() { LocalDateTime threshold = LocalDateTime.now().minusDays(REVOKE_AFTER_DAYS); List usersToRevoke = userRepository.findByProviderAndDeletedAtBefore( - APPLE, + Provider.APPLE, threshold ); diff --git a/src/main/java/gg/agit/konect/domain/user/service/UserService.java b/src/main/java/gg/agit/konect/domain/user/service/UserService.java index b92c9424..aca83ebe 100644 --- a/src/main/java/gg/agit/konect/domain/user/service/UserService.java +++ b/src/main/java/gg/agit/konect/domain/user/service/UserService.java @@ -1,7 +1,7 @@ package gg.agit.konect.domain.user.service; -import static gg.agit.konect.domain.club.enums.ClubPosition.PRESIDENT; import static gg.agit.konect.domain.club.enums.ClubPosition.MANAGERS; +import static gg.agit.konect.domain.club.enums.ClubPosition.PRESIDENT; import static gg.agit.konect.global.code.ApiResponseCode.CANNOT_DELETE_CLUB_PRESIDENT; import java.time.LocalDateTime; From 22dd8fe6b3ade91d10887310f4b4e26c70321cfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Fri, 6 Mar 2026 16:02:03 +0900 Subject: [PATCH 5/8] =?UTF-8?q?fix:=20revoke=EB=90=9C=20=ED=86=A0=ED=81=B0?= =?UTF-8?q?=20null=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/gg/agit/konect/domain/user/model/User.java | 4 ++++ .../gg/agit/konect/domain/user/repository/UserRepository.java | 1 + .../agit/konect/domain/user/service/UserSchedulerService.java | 2 ++ 3 files changed, 7 insertions(+) diff --git a/src/main/java/gg/agit/konect/domain/user/model/User.java b/src/main/java/gg/agit/konect/domain/user/model/User.java index 32ea169c..6e16c509 100644 --- a/src/main/java/gg/agit/konect/domain/user/model/User.java +++ b/src/main/java/gg/agit/konect/domain/user/model/User.java @@ -214,4 +214,8 @@ public void restore() { public boolean canRestore(LocalDateTime now, long restoreWindowDays) { return deletedAt != null && deletedAt.isAfter(now.minusDays(restoreWindowDays)); } + + public void clearAppleRefreshToken() { + this.appleRefreshToken = null; + } } diff --git a/src/main/java/gg/agit/konect/domain/user/repository/UserRepository.java b/src/main/java/gg/agit/konect/domain/user/repository/UserRepository.java index 301b6d50..78201661 100644 --- a/src/main/java/gg/agit/konect/domain/user/repository/UserRepository.java +++ b/src/main/java/gg/agit/konect/domain/user/repository/UserRepository.java @@ -151,6 +151,7 @@ List findUserIdsByUniversityAndStudentYear( WHERE u.provider = :provider AND u.deletedAt IS NOT NULL AND u.deletedAt < :threshold + AND u.appleRefreshToken IS NOT NULL """) List findByProviderAndDeletedAtBefore( @Param("provider") Provider provider, diff --git a/src/main/java/gg/agit/konect/domain/user/service/UserSchedulerService.java b/src/main/java/gg/agit/konect/domain/user/service/UserSchedulerService.java index a8d51d9a..62397e17 100644 --- a/src/main/java/gg/agit/konect/domain/user/service/UserSchedulerService.java +++ b/src/main/java/gg/agit/konect/domain/user/service/UserSchedulerService.java @@ -42,6 +42,8 @@ public void revokeAppleTokensAfterRestoreWindow() { try { if (user.getAppleRefreshToken() != null) { appleTokenRevocationService.revoke(user.getAppleRefreshToken()); + user.clearAppleRefreshToken(); + userRepository.save(user); } } catch (Exception e) { // 개별 사용자의 토큰 revoke 실패 시 다른 사용자 처리 계속 From 8ae7d2cd7b28328c9f7e9570651679b9631b1a49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Fri, 6 Mar 2026 16:43:43 +0900 Subject: [PATCH 6/8] =?UTF-8?q?refactor:=20Apple=20revoke=20=EB=B0=B0?= =?UTF-8?q?=EC=B9=98=20=ED=8A=B8=EB=9E=9C=EC=9E=AD=EC=85=98=20=EA=B2=BD?= =?UTF-8?q?=EA=B3=84=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/repository/UserRepository.java | 7 ++++ .../user/service/UserSchedulerService.java | 38 +++++++++++-------- .../user/service/UserSchedulerTxService.java | 34 +++++++++++++++++ 3 files changed, 63 insertions(+), 16 deletions(-) create mode 100644 src/main/java/gg/agit/konect/domain/user/service/UserSchedulerTxService.java diff --git a/src/main/java/gg/agit/konect/domain/user/repository/UserRepository.java b/src/main/java/gg/agit/konect/domain/user/repository/UserRepository.java index 78201661..d91c1012 100644 --- a/src/main/java/gg/agit/konect/domain/user/repository/UserRepository.java +++ b/src/main/java/gg/agit/konect/domain/user/repository/UserRepository.java @@ -157,4 +157,11 @@ List findByProviderAndDeletedAtBefore( @Param("provider") Provider provider, @Param("threshold") LocalDateTime threshold ); + + @Query(""" + SELECT u + FROM User u + WHERE u.id = :id + """) + Optional findByIdIncludingDeleted(@Param("id") Integer id); } diff --git a/src/main/java/gg/agit/konect/domain/user/service/UserSchedulerService.java b/src/main/java/gg/agit/konect/domain/user/service/UserSchedulerService.java index 62397e17..51cb42d2 100644 --- a/src/main/java/gg/agit/konect/domain/user/service/UserSchedulerService.java +++ b/src/main/java/gg/agit/konect/domain/user/service/UserSchedulerService.java @@ -4,21 +4,20 @@ import java.util.List; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import gg.agit.konect.domain.user.enums.Provider; import gg.agit.konect.domain.user.model.User; -import gg.agit.konect.domain.user.repository.UserRepository; import gg.agit.konect.infrastructure.oauth.AppleTokenRevocationService; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +@Slf4j @Service @RequiredArgsConstructor public class UserSchedulerService { private static final int REVOKE_AFTER_DAYS = 7; - private final UserRepository userRepository; + private final UserSchedulerTxService userSchedulerTxService; private final AppleTokenRevocationService appleTokenRevocationService; /** @@ -26,30 +25,37 @@ public class UserSchedulerService { * - 7일 복구 정책: 탈퇴 후 7일 이내 복구 가능하므로 즉시 revoke하지 않음 * - 7일 경과 후: 복구 불가 시점이므로 Apple 토큰 영구 폐기 */ - @Transactional public void revokeAppleTokensAfterRestoreWindow() { LocalDateTime threshold = LocalDateTime.now().minusDays(REVOKE_AFTER_DAYS); - List usersToRevoke = userRepository.findByProviderAndDeletedAtBefore( - Provider.APPLE, - threshold - ); + List usersToRevoke = userSchedulerTxService.findUsersToRevoke(threshold); if (usersToRevoke.isEmpty()) { + log.info("No Apple users to revoke (threshold={})", threshold); return; } + int successCount = 0; + int failureCount = 0; + for (User user : usersToRevoke) { try { - if (user.getAppleRefreshToken() != null) { - appleTokenRevocationService.revoke(user.getAppleRefreshToken()); - user.clearAppleRefreshToken(); - userRepository.save(user); + String refreshToken = user.getAppleRefreshToken(); + if (refreshToken == null) { + continue; } + + appleTokenRevocationService.revoke(refreshToken); + userSchedulerTxService.clearAppleRefreshTokenIfMatches(user.getId(), refreshToken); + successCount++; } catch (Exception e) { - // 개별 사용자의 토큰 revoke 실패 시 다른 사용자 처리 계속 - // 로깅은 스케줄러에서 처리 - throw e; + failureCount++; + log.error("Failed to revoke Apple token for userId={}", user.getId(), e); } } + + log.info( + "Apple token revoke task finished: total={}, success={}, failure={}" + , usersToRevoke.size(), successCount, failureCount + ); } } diff --git a/src/main/java/gg/agit/konect/domain/user/service/UserSchedulerTxService.java b/src/main/java/gg/agit/konect/domain/user/service/UserSchedulerTxService.java new file mode 100644 index 00000000..41df5994 --- /dev/null +++ b/src/main/java/gg/agit/konect/domain/user/service/UserSchedulerTxService.java @@ -0,0 +1,34 @@ +package gg.agit.konect.domain.user.service; + +import java.time.LocalDateTime; +import java.util.List; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import gg.agit.konect.domain.user.enums.Provider; +import gg.agit.konect.domain.user.model.User; +import gg.agit.konect.domain.user.repository.UserRepository; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class UserSchedulerTxService { + + private final UserRepository userRepository; + + @Transactional(readOnly = true) + public List findUsersToRevoke(LocalDateTime threshold) { + return userRepository.findByProviderAndDeletedAtBefore(Provider.APPLE, threshold); + } + + @Transactional + public void clearAppleRefreshTokenIfMatches(Integer userId, String expectedRefreshToken) { + userRepository.findByIdIncludingDeleted(userId) + .filter(user -> expectedRefreshToken.equals(user.getAppleRefreshToken())) + .ifPresent(user -> { + user.clearAppleRefreshToken(); + userRepository.save(user); + }); + } +} From 355342c0d85653cca48fd4189244c354bad73533 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Fri, 6 Mar 2026 17:55:09 +0900 Subject: [PATCH 7/8] =?UTF-8?q?fix:=20=EC=8A=A4=EC=BC=80=EC=A4=84=EB=9F=AC?= =?UTF-8?q?=20=EC=9E=91=EB=8F=99=20=EC=8B=9C=EA=B0=84=EC=9D=84=20=EC=84=9C?= =?UTF-8?q?=EB=B2=84=20=EC=8B=9C=EA=B0=84=EB=8C=80=EC=97=90=20=EC=9D=98?= =?UTF-8?q?=EC=A1=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gg/agit/konect/domain/user/scheduler/UserScheduler.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/gg/agit/konect/domain/user/scheduler/UserScheduler.java b/src/main/java/gg/agit/konect/domain/user/scheduler/UserScheduler.java index 04fc3f30..328123c0 100644 --- a/src/main/java/gg/agit/konect/domain/user/scheduler/UserScheduler.java +++ b/src/main/java/gg/agit/konect/domain/user/scheduler/UserScheduler.java @@ -15,11 +15,11 @@ public class UserScheduler { private final UserSchedulerService userSchedulerService; /** - * 매일 자정(한국 시간 00:00)에 실행되어 7일 경과한 Apple 사용자 토큰을 revoke합니다. + * 매일 자정(서버 기본 시간대 기준 00:00)에 실행되어 7일 경과한 Apple 사용자 토큰을 revoke합니다. * cron 표현식: 초 분 시 일 월 요일 * 0 0 0 * * *: 매일 00:00:00 실행 */ - @Scheduled(cron = "0 0 0 * * *", zone = "Asia/Seoul") + @Scheduled(cron = "0 0 0 * * *") public void revokeAppleTokensAfterRestoreWindow() { try { log.info("Starting Apple token revocation task for users withdrawn more than 7 days ago"); From ffe4d1c841276f23802d78742e90ba9a848106b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Fri, 6 Mar 2026 17:55:34 +0900 Subject: [PATCH 8/8] =?UTF-8?q?refactor:=20=EB=B3=80=EA=B2=BD=EA=B0=90?= =?UTF-8?q?=EC=A7=80=20=ED=99=9C=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../konect/domain/user/service/UserSchedulerTxService.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/main/java/gg/agit/konect/domain/user/service/UserSchedulerTxService.java b/src/main/java/gg/agit/konect/domain/user/service/UserSchedulerTxService.java index 41df5994..76f4da79 100644 --- a/src/main/java/gg/agit/konect/domain/user/service/UserSchedulerTxService.java +++ b/src/main/java/gg/agit/konect/domain/user/service/UserSchedulerTxService.java @@ -26,9 +26,6 @@ public List findUsersToRevoke(LocalDateTime threshold) { public void clearAppleRefreshTokenIfMatches(Integer userId, String expectedRefreshToken) { userRepository.findByIdIncludingDeleted(userId) .filter(user -> expectedRefreshToken.equals(user.getAppleRefreshToken())) - .ifPresent(user -> { - user.clearAppleRefreshToken(); - userRepository.save(user); - }); + .ifPresent(User::clearAppleRefreshToken); } }