diff --git a/src/main/java/com/ward/ward_server/api/item/controller/PublicItemController.java b/src/main/java/com/ward/ward_server/api/item/controller/PublicItemController.java index 2a8f0be..cfa4564 100644 --- a/src/main/java/com/ward/ward_server/api/item/controller/PublicItemController.java +++ b/src/main/java/com/ward/ward_server/api/item/controller/PublicItemController.java @@ -25,6 +25,10 @@ @RequestMapping("/public/items") public class PublicItemController { + private static final int TOP_LIMIT_10 = 10; + private static final int TOP_LIMIT_50 = 50; + private static final List ALLOWED_LIMITS = List.of(TOP_LIMIT_10, TOP_LIMIT_50); + private final ItemService itemService; @GetMapping("/{itemId}/details") @@ -51,8 +55,8 @@ public ApiResponse> getItemPage(@Authentication @GetMapping("/top") public ApiResponse> getTopItemsByCategory(@RequestParam("category") Category category, @RequestParam("limit") int limit) { - if (limit != 10 && limit != 50) { - throw new ApiException(ExceptionCode.INVALID_INPUT, "Limit 는 10 or 50 이어야합니다."); + if (!ALLOWED_LIMITS.contains(limit)) { + throw new ApiException(ExceptionCode.INVALID_INPUT, "Limit는 10 또는 50이어야 합니다."); } List topItemsResponse = itemService.getTopItemsResponseByCategory(category, limit); diff --git a/src/main/java/com/ward/ward_server/api/item/scheduler/ItemViewCountScheduler.java b/src/main/java/com/ward/ward_server/api/item/scheduler/ItemViewCountScheduler.java index c2c8e42..e63a1b7 100644 --- a/src/main/java/com/ward/ward_server/api/item/scheduler/ItemViewCountScheduler.java +++ b/src/main/java/com/ward/ward_server/api/item/scheduler/ItemViewCountScheduler.java @@ -20,6 +20,8 @@ @RequiredArgsConstructor public class ItemViewCountScheduler { + private static final int ONE_DAY = 1; + private final ItemViewCountRepository itemViewCountRepository; private final ItemTopRankRepository itemTopRankRepository; @@ -32,7 +34,7 @@ public void updateViewCounts() { @Transactional public void executeUpdateViewCounts() { LocalDateTime now = LocalDateTime.now(); - LocalDateTime startTime = now.minusDays(1); + LocalDateTime startTime = now.minusDays(ONE_DAY); List viewCounts = itemViewCountRepository.findViewCountsBetween(startTime, now); @@ -48,7 +50,7 @@ public void executeUpdateViewCounts() { // 새로운 순위 데이터를 삽입 categoryItemViewCounts.forEach((category, itemViewCounts) -> { - int rank = 1; + int rank = ONE_DAY; for (Map.Entry entry : itemViewCounts.entrySet()) { ItemTopRank itemTopRank = ItemTopRank.builder() .item(entry.getKey()) diff --git a/src/main/java/com/ward/ward_server/api/item/service/BrandService.java b/src/main/java/com/ward/ward_server/api/item/service/BrandService.java index db98391..fdf2f14 100644 --- a/src/main/java/com/ward/ward_server/api/item/service/BrandService.java +++ b/src/main/java/com/ward/ward_server/api/item/service/BrandService.java @@ -30,6 +30,7 @@ @Service @RequiredArgsConstructor +@Transactional(readOnly = true) public class BrandService { private final BrandRepository brandRepository; private final ItemRepository itemRepository; @@ -52,13 +53,11 @@ public BrandResponse createBrand(String koreanName, String englishName, String b return getBrandResponse(savedBrand); } - @Transactional(readOnly = true) public PageResponse getBrandAndItem3Page(BasicSort sort, int page) { Page brandInfoPage = brandRepository.getBrandAndItem3Page(sort, PageRequest.of(page, API_PAGE_SIZE)); return new PageResponse<>(brandInfoPage.getContent(), brandInfoPage); } - @Transactional(readOnly = true) public List getRecommendedBrands() { return brandRepository.findTop10ByOrderByViewCountDesc() .stream() @@ -66,13 +65,11 @@ public List getRecommendedBrands() { .collect(Collectors.toList()); } - @Transactional(readOnly = true) public PageResponse getBrandItemPage(long brandId, BasicSort sort, int page) { Page brandInfoPage = itemRepository.getBrandItemPage(brandId, sort, PageRequest.of(page, API_PAGE_SIZE)); return new PageResponse<>(brandInfoPage.getContent(), brandInfoPage); } - @Transactional(readOnly = true) public PageResponse getBrandReleaseInfoPage(long brandId, int page) { Page releaseInfoInfoPage = releaseInfoRepository.getBrandReleaseInfoPage(brandId, PageRequest.of(page, API_PAGE_SIZE)); return new PageResponse<>(releaseInfoInfoPage.getContent(), releaseInfoInfoPage); diff --git a/src/main/java/com/ward/ward_server/api/item/service/ItemService.java b/src/main/java/com/ward/ward_server/api/item/service/ItemService.java index bb4c661..eb5c05c 100644 --- a/src/main/java/com/ward/ward_server/api/item/service/ItemService.java +++ b/src/main/java/com/ward/ward_server/api/item/service/ItemService.java @@ -31,6 +31,10 @@ @Transactional(readOnly = true) @Slf4j public class ItemService { + + private static final int FIRST_PAGE = 0; + private static final int HOURS_TO_SUBTRACT = 9; + private final ItemRepository itemRepository; private final BrandRepository brandRepository; private final ItemImageRepository itemImageRepository; @@ -89,34 +93,31 @@ public void increaseViewCount(Item item) { .build()); } - @Transactional(readOnly = true) public List getItem10List(Long userId, Section section, Category category) { return switch (section) { case DUE_TODAY, RELEASE_NOW, RELEASE_SCHEDULE -> - itemRepository.getItem10List(userId, LocalDateTime.now().minusHours(9), category, section); //HACK DB 시간 설정 전까지는 -9시간으로 비교해야 한다. + itemRepository.getItem10List(userId, LocalDateTime.now().minusHours(HOURS_TO_SUBTRACT), category, section); //HACK DB 시간 설정 전까지는 -9시간으로 비교해야 한다. default -> throw new ApiException(INVALID_INPUT, SECTION_NOT_AVAILABLE_THIS_PAGE.getMessage()); }; } - @Transactional(readOnly = true) public PageResponse getItemPage(Long userId, Section section, Category category, int page, String date) { log.info("하늘:{}", date); return switch (section) { case RELEASE_SCHEDULE, CLOSED -> { - Page itemPageInfo = itemRepository.getItemPage(userId, LocalDateTime.now().minusHours(9), category, section, date, PageRequest.of(page, API_PAGE_SIZE)); //HACK DB 시간 설정 전까지는 -9시간으로 비교해야 한다. + Page itemPageInfo = itemRepository.getItemPage(userId, LocalDateTime.now().minusHours(HOURS_TO_SUBTRACT), category, section, date, PageRequest.of(page, API_PAGE_SIZE)); //HACK DB 시간 설정 전까지는 -9시간으로 비교해야 한다. yield new PageResponse<>(itemPageInfo.getContent(), itemPageInfo); } default -> throw new ApiException(INVALID_INPUT, SECTION_NOT_AVAILABLE_THIS_PAGE.getMessage()); }; } - @Transactional(readOnly = true) public List getTopItemsResponseByCategory(Category category, int limit) { List topItems; if (category == Category.ALL) { - topItems = itemTopRankRepository.findTopItems(PageRequest.of(0, limit)); + topItems = itemTopRankRepository.findTopItems(PageRequest.of(FIRST_PAGE, limit)); } else { - topItems = itemTopRankRepository.findTopItemsByCategory(category, PageRequest.of(0, limit)); + topItems = itemTopRankRepository.findTopItemsByCategory(category, PageRequest.of(FIRST_PAGE, limit)); } return convertToTopResponse(topItems); } diff --git a/src/main/java/com/ward/ward_server/api/item/webCrawler/KasinaWebCrawler.java b/src/main/java/com/ward/ward_server/api/item/webCrawler/KasinaWebCrawler.java index 56cbfa0..8fc548c 100644 --- a/src/main/java/com/ward/ward_server/api/item/webCrawler/KasinaWebCrawler.java +++ b/src/main/java/com/ward/ward_server/api/item/webCrawler/KasinaWebCrawler.java @@ -1,6 +1,7 @@ package com.ward.ward_server.api.item.webCrawler; import com.ward.ward_server.api.item.dto.WebProductData; +import com.ward.ward_server.global.config.CrawlerConfig; import lombok.extern.slf4j.Slf4j; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; @@ -27,8 +28,8 @@ public class KasinaWebCrawler { private WebDriver driver; @Autowired - public KasinaWebCrawler(CrawlerProperties crawlerProperties) { - String chromeDriverPath = crawlerProperties.getChromeDriverPath(); + public KasinaWebCrawler(CrawlerConfig crawlerConfig) { + String chromeDriverPath = crawlerConfig.getChromeDriverPath(); ChromeOptions chromeOptions = new ChromeOptions(); // chromeOptions.setBinary("/usr/bin/google-chrome"); // EC2 쓸 때 해제 chromeOptions.addArguments("--headless"); //헤드리스 모드로 실행, 실제 창이 표시되지 않는다. diff --git a/src/main/java/com/ward/ward_server/api/item/webCrawler/NikeWebCrawler.java b/src/main/java/com/ward/ward_server/api/item/webCrawler/NikeWebCrawler.java index f9f7dcb..67f6890 100644 --- a/src/main/java/com/ward/ward_server/api/item/webCrawler/NikeWebCrawler.java +++ b/src/main/java/com/ward/ward_server/api/item/webCrawler/NikeWebCrawler.java @@ -1,6 +1,7 @@ package com.ward.ward_server.api.item.webCrawler; import com.ward.ward_server.api.item.dto.WebProductData; +import com.ward.ward_server.global.config.CrawlerConfig; import lombok.extern.slf4j.Slf4j; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; @@ -24,8 +25,8 @@ public class NikeWebCrawler { private WebDriver driver; @Autowired - public NikeWebCrawler(CrawlerProperties crawlerProperties) { - String chromeDriverPath = crawlerProperties.getChromeDriverPath(); + public NikeWebCrawler(CrawlerConfig crawlerConfig) { + String chromeDriverPath = crawlerConfig.getChromeDriverPath(); ChromeOptions chromeOptions = new ChromeOptions(); // chromeOptions.setBinary("/usr/bin/google-chrome"); // EC2 쓸 때 해제 chromeOptions.addArguments("--headless"); //헤드리스 모드로 실행, 실제 창이 표시되지 않는다. diff --git a/src/main/java/com/ward/ward_server/api/releaseInfo/service/ReleaseInfoService.java b/src/main/java/com/ward/ward_server/api/releaseInfo/service/ReleaseInfoService.java index 6560645..3814909 100644 --- a/src/main/java/com/ward/ward_server/api/releaseInfo/service/ReleaseInfoService.java +++ b/src/main/java/com/ward/ward_server/api/releaseInfo/service/ReleaseInfoService.java @@ -36,6 +36,7 @@ @Service @RequiredArgsConstructor +@Transactional(readOnly = true) public class ReleaseInfoService { private final ReleaseInfoRepository releaseInfoRepository; private final DrawPlatformRepository drawPlatformRepository; @@ -70,13 +71,11 @@ public ReleaseInfoDetailResponse createReleaseInfo(Long itemId, String platformN return getDetailResponse(item.getBrand(), item, platform, savedReleaseInfo); } - @Transactional(readOnly = true) public ReleaseInfoDetailResponse getReleaseInfo(Long releaseInfoId) { ReleaseInfo releaseInfo = releaseInfoRepository.findById(releaseInfoId).orElseThrow(() -> new ApiException(RELEASE_INFO_NOT_FOUND)); return getDetailResponse(releaseInfo.getItem().getBrand(), releaseInfo.getItem(), releaseInfo.getDrawPlatform(), releaseInfo); } - @Transactional(readOnly = true) public List getReleaseInfo10List(Long userId, Section section, Category category) { return switch (section){ case DUE_TODAY, RELEASE_WISH, REGISTER_TODAY -> releaseInfoRepository.getReleaseInfo10List(userId, LocalDateTime.now().minusHours(9), category, section); //HACK DB 시간 설정 전까지는 -9시간으로 비교해야 한다. @@ -84,7 +83,6 @@ public List getReleaseInfo10List(Long userId, Section }; } - @Transactional(readOnly = true) public PageResponse getReleaseInfoPage(Long userId, Section section, Category category, int page) { return switch (section){ case DUE_TODAY, RELEASE_NOW, REGISTER_TODAY ->{ @@ -95,14 +93,12 @@ public PageResponse getReleaseInfoPage(Long userId, S }; } - @Transactional(readOnly = true) public Page getOngoingReleaseInfos(Long itemId, int page, int size) { Item item = itemRepository.findById(itemId).orElseThrow(() -> new ApiException(ITEM_NOT_FOUND)); LocalDateTime now = LocalDateTime.now(); return releaseInfoRepository.findByItemAndDueDateAfter(item, now, PageRequest.of(page, size)); } - @Transactional(readOnly = true) public Page getCompletedReleaseInfos(Long itemId, int page, int size) { Item item = itemRepository.findById(itemId).orElseThrow(() -> new ApiException(ITEM_NOT_FOUND)); LocalDateTime now = LocalDateTime.now(); @@ -196,7 +192,6 @@ private ReleaseInfoDetailResponse getDetailResponse(Brand brand, Item item, Draw releaseInfo.getNotificationMethod().getDesc(), releaseInfo.getReleaseMethod().getDesc(), releaseInfo.getDeliveryMethod().getDesc()); } - @Transactional(readOnly = true) public List getExpiringItems(int limit) { LocalDateTime now = LocalDateTime.now(); Pageable pageable = PageRequest.of(0, limit); diff --git a/src/main/java/com/ward/ward_server/api/user/controller/HelloController.java b/src/main/java/com/ward/ward_server/api/user/controller/HelloController.java index 03db1da..5f50128 100644 --- a/src/main/java/com/ward/ward_server/api/user/controller/HelloController.java +++ b/src/main/java/com/ward/ward_server/api/user/controller/HelloController.java @@ -19,14 +19,14 @@ public ApiResponse greeting(){ @GetMapping("/user/secured") public ApiResponse secured(@AuthenticationPrincipal CustomUserDetails principal) { - String secured = "If you see this, then you're logged in as user " + principal.getEmail() + String secured = "User 권한으로 접근 성공 - " + principal.getEmail() + " User ID: " + principal.getUserId(); return ApiResponse.ok(secured); } @GetMapping("/admin") public ApiResponse admin(@AuthenticationPrincipal CustomUserDetails principal) { - String admin = "If you see this, then you are an Admin. User ID: " + principal.getUserId(); + String admin = "Admin 권한으로 접근 성공 - " + principal.getUserId(); return ApiResponse.ok(admin); } } diff --git a/src/main/java/com/ward/ward_server/api/user/dto/AddSocialLoginRequest.java b/src/main/java/com/ward/ward_server/api/user/dto/AddSocialLoginRequest.java index c186cdd..d0c9243 100644 --- a/src/main/java/com/ward/ward_server/api/user/dto/AddSocialLoginRequest.java +++ b/src/main/java/com/ward/ward_server/api/user/dto/AddSocialLoginRequest.java @@ -4,8 +4,8 @@ import jakarta.validation.constraints.NotBlank; public record AddSocialLoginRequest( - @NotBlank(message = "Provider cannot be empty") String provider, - @NotBlank(message = "Provider ID cannot be empty") String providerId, - @Email(message = "Email should be valid") String email, + @NotBlank(message = "provider는 필수 항목입니다.") String provider, + @NotBlank(message = "providerId는 필수 항목입니다.") String providerId, + @Email(message = "유효한 이메일 주소를 입력해 주세요.") String email, String appleRefreshToken ) {} diff --git a/src/main/java/com/ward/ward_server/api/user/dto/LoginRequest.java b/src/main/java/com/ward/ward_server/api/user/dto/LoginRequest.java index 18309bb..1d52de2 100644 --- a/src/main/java/com/ward/ward_server/api/user/dto/LoginRequest.java +++ b/src/main/java/com/ward/ward_server/api/user/dto/LoginRequest.java @@ -4,7 +4,7 @@ import jakarta.validation.constraints.NotBlank; public record LoginRequest( - @NotBlank(message = "Provider cannot be empty") String provider, - @NotBlank(message = "Provider ID cannot be empty") String providerId, - @Email(message = "Email should be valid") String email + @NotBlank(message = "provider는 필수 항목입니다.") String provider, + @NotBlank(message = "providerId는 필수 항목입니다.") String providerId, + @Email(message = "유효한 이메일 주소를 입력해 주세요.") String email ) {} diff --git a/src/main/java/com/ward/ward_server/api/user/dto/RegisterRequest.java b/src/main/java/com/ward/ward_server/api/user/dto/RegisterRequest.java index d8b58d0..50a722c 100644 --- a/src/main/java/com/ward/ward_server/api/user/dto/RegisterRequest.java +++ b/src/main/java/com/ward/ward_server/api/user/dto/RegisterRequest.java @@ -4,13 +4,19 @@ import jakarta.validation.constraints.NotBlank; public record RegisterRequest( - @NotBlank(message = "Provider cannot be empty") String provider, - @NotBlank(message = "Provider ID cannot be empty") String providerId, - @NotBlank(message = "Name cannot be empty") String name, - @Email(message = "Email should be valid") String email, + @NotBlank(message = "provider는 필수 항목입니다.") String provider, + @NotBlank(message = "providerId는 필수 항목입니다.") String providerId, + @NotBlank(message = "이름은 필수 항목입니다.") String name, + @Email(message = "유효한 이메일 주소를 입력해 주세요.") String email, String appleRefreshToken, - @NotBlank(message = "Nickname cannot be empty") String nickname, + @NotBlank(message = "닉네임은 필수 항목입니다.") String nickname, Boolean emailNotification, Boolean appPushNotification, Boolean snsNotification -) {} +) { + public RegisterRequest { + if (emailNotification == null) emailNotification = false; + if (appPushNotification == null) appPushNotification = false; + if (snsNotification == null) snsNotification = false; + } +} diff --git a/src/main/java/com/ward/ward_server/api/user/dto/UserInfoUpdateRequest.java b/src/main/java/com/ward/ward_server/api/user/dto/UserInfoUpdateRequest.java index f45d617..c83f831 100644 --- a/src/main/java/com/ward/ward_server/api/user/dto/UserInfoUpdateRequest.java +++ b/src/main/java/com/ward/ward_server/api/user/dto/UserInfoUpdateRequest.java @@ -4,8 +4,8 @@ import jakarta.validation.constraints.NotBlank; public record UserInfoUpdateRequest( - @NotBlank(message = "Provider cannot be empty") String provider, - @NotBlank(message = "Provider ID cannot be empty") String providerId, - @Email(message = "Email should be valid") String oldEmail, - @Email(message = "Email should be valid") String newEmail + @NotBlank(message = "provider는 필수 항목입니다.") String provider, + @NotBlank(message = "providerId는 필수 항목입니다.") String providerId, + @Email(message = "유효한 이전 이메일 주소를 입력해 주세요.") String oldEmail, + @Email(message = "유효한 새 이메일 주소를 입력해 주세요.") String newEmail ) {} diff --git a/src/main/java/com/ward/ward_server/api/user/service/AppleUserService.java b/src/main/java/com/ward/ward_server/api/user/service/AppleUserService.java index a093494..50be3a9 100644 --- a/src/main/java/com/ward/ward_server/api/user/service/AppleUserService.java +++ b/src/main/java/com/ward/ward_server/api/user/service/AppleUserService.java @@ -9,8 +9,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.Optional; - @Service @RequiredArgsConstructor @Slf4j @@ -19,43 +17,19 @@ public class AppleUserService { private final SocialLoginRepository socialLoginRepository; -// @Transactional -// public void updateUserInfo(String provider, String providerId, String oldEmail, String newEmail) { -// // SocialLogin 테이블에서 사용자 정보를 찾습니다. -// Optional optionalSocialLogin = socialLoginRepository.findByProviderAndProviderIdAndEmail(provider, providerId, oldEmail); -// -// if (optionalSocialLogin.isPresent()) { -// // SocialLogin 정보를 통해 사용자 정보를 가져옵니다. -// SocialLogin socialLogin = optionalSocialLogin.get(); -// User user = socialLogin.getUser(); -// -// // 사용자 이메일 업데이트 -// user.updateEmail(newEmail); -// log.info("사용자 정보 업데이트: {}", user); -// -// // 소셜 로그인 정보 이메일 업데이트 -// socialLogin.updateEmail(newEmail); -// log.info("소셜 로그인 정보 업데이트: {}", socialLogin); -// } else { -// // SocialLogin 정보가 존재하지 않으면 로깅하여 알립니다. -// log.warn("해당 이메일을 가진 사용자를 찾을 수 없습니다: {}", oldEmail); -// } -// } @Transactional public void updateUserInfo(String provider, String providerId, String oldEmail, String newEmail) { - // SocialLogin 테이블에서 사용자 정보를 찾습니다. - Optional optionalSocialLogin = socialLoginRepository.findByProviderAndProviderIdAndEmail(provider, providerId, oldEmail); - - if (optionalSocialLogin.isPresent()) { - // SocialLogin 정보를 통해 사용자 정보를 가져옵니다. - SocialLogin socialLogin = optionalSocialLogin.get(); - socialLogin.updateEmail(newEmail); + // SocialLogin 테이블에서 사용자 정보를 찾고, 없으면 예외를 던집니다. + SocialLogin socialLogin = socialLoginRepository + .findByProviderAndProviderIdAndEmail(provider, providerId, oldEmail) + .orElseThrow(() -> { + log.warn("해당 이메일을 가진 사용자를 찾을 수 없습니다. Provider: {}, ProviderId: {}, OldEmail: {}", provider, providerId, oldEmail); + return new ApiException(ExceptionCode.NON_EXISTENT_EMAIL); + }); - log.info("소셜 로그인 정보 업데이트: {}", socialLogin); - } else { - // SocialLogin 정보가 존재하지 않으면 로깅하여 알립니다. - log.warn("해당 이메일을 가진 사용자를 찾을 수 없습니다: {}", oldEmail); - throw new ApiException(ExceptionCode.NON_EXISTENT_EMAIL); - } + // 소셜 로그인 정보 이메일 업데이트 + socialLogin.updateEmail(newEmail); + log.info("소셜 로그인 정보 업데이트: {}", socialLogin); } } + diff --git a/src/main/java/com/ward/ward_server/api/user/service/AuthService.java b/src/main/java/com/ward/ward_server/api/user/service/AuthService.java index b7d3d3e..a823362 100644 --- a/src/main/java/com/ward/ward_server/api/user/service/AuthService.java +++ b/src/main/java/com/ward/ward_server/api/user/service/AuthService.java @@ -234,7 +234,7 @@ private JwtTokens generateJwtTokens(User user) { var accessToken = jwtIssuer.issueAccessToken(principal.getUserId(), principal.getEmail(), roles); var refreshToken = jwtIssuer.issueRefreshToken(); - refreshTokenService.saveRefreshToken(user, refreshToken); + refreshTokenService.saveNewRefreshToken(user, refreshToken); return new JwtTokens(accessToken, refreshToken); } catch (AuthenticationException e) { diff --git a/src/main/java/com/ward/ward_server/api/user/service/RefreshTokenService.java b/src/main/java/com/ward/ward_server/api/user/service/RefreshTokenService.java index bbfabdc..33544cd 100644 --- a/src/main/java/com/ward/ward_server/api/user/service/RefreshTokenService.java +++ b/src/main/java/com/ward/ward_server/api/user/service/RefreshTokenService.java @@ -14,14 +14,12 @@ public class RefreshTokenService { private final RefreshTokenRepository refreshTokenRepository; - // 토큰 생성 @Transactional - public void saveRefreshToken(User user, String refreshToken) { + public void saveNewRefreshToken(User user, String refreshToken) { var refreshTokenEntity = new RefreshToken(refreshToken, user); refreshTokenRepository.save(refreshTokenEntity); } - // 토큰 무효화 @Transactional public void invalidateRefreshToken(String refreshToken) { var token = refreshTokenRepository.findByToken(refreshToken) @@ -35,10 +33,9 @@ public RefreshToken findRefreshTokenByToken(String token) { .orElseThrow(() -> new ApiException(ExceptionCode.INVALID_REFRESH_TOKEN)); } - // 토큰 갱신 @Transactional public void invalidateAndSaveNewToken(RefreshToken oldToken, String newToken) { refreshTokenRepository.delete(oldToken); - saveRefreshToken(oldToken.getUser(), newToken); + saveNewRefreshToken(oldToken.getUser(), newToken); } } diff --git a/src/main/java/com/ward/ward_server/api/user/service/UserService.java b/src/main/java/com/ward/ward_server/api/user/service/UserService.java index 54fee52..81b5131 100644 --- a/src/main/java/com/ward/ward_server/api/user/service/UserService.java +++ b/src/main/java/com/ward/ward_server/api/user/service/UserService.java @@ -12,13 +12,15 @@ import com.ward.ward_server.api.wishBrand.repository.WishBrandRepository; import com.ward.ward_server.api.wishItem.WishItem; import com.ward.ward_server.api.wishItem.repository.WishItemRepository; +import com.ward.ward_server.global.Object.Constants; +import com.ward.ward_server.global.config.AppleConfig; +import com.ward.ward_server.global.config.KakaoConfig; import com.ward.ward_server.global.exception.ApiException; import com.ward.ward_server.global.exception.ExceptionCode; import com.ward.ward_server.global.util.AppleClientSecretGenerator; import com.ward.ward_server.global.util.ValidationUtils; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; import org.springframework.http.*; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -33,6 +35,12 @@ @Transactional(readOnly = true) public class UserService { + private static final String AUTHORIZATION_HEADER = "Authorization"; + private static final String KAKAO_AUTH_PREFIX = "KakaoAK "; + private static final String CLIENT_ID_KEY = "client_id="; + private static final String CLIENT_SECRET_KEY = "client_secret="; + private static final String TOKEN_KEY = "&token="; + private final UserRepository userRepository; private final EntryRecordRepository entryRecordRepository; private final WishItemRepository wishItemRepository; @@ -40,27 +48,8 @@ public class UserService { private final SocialLoginRepository socialLoginRepository; private final RefreshTokenRepository refreshTokenRepository; private final RestTemplate restTemplate; - - @Value("${kakao.unlink-url}") - private String kakaoUnlinkUrl; - - @Value("${kakao.service-app-admin-key}") - private String serviceAppAdminKey; - - @Value("${apple.unlink-url}") - private String appleUnlinkUrl; - - @Value("${apple.client-id}") - private String appleClientId; - - @Value("${apple.key-id}") - private String appleKeyId; - - @Value("${apple.team-id}") - private String appleTeamId; - - @Value("${apple.private-key}") - private String applePrivateKey; + private final KakaoConfig kakaoConfig; + private final AppleConfig appleConfig; public String getNickname(Long userId) { return userRepository.findNicknameById(userId) @@ -90,10 +79,8 @@ public void deleteUser(Long userId) { List refreshTokens = refreshTokenRepository.findByUserId(userId); try { - // 소셜 로그인 정보 삭제 (카카오) disconnectKakao(user); - // 소셜 로그인 정보 삭제 (애플) disconnectApple(user); // 연관 데이터 일괄 삭제 @@ -111,62 +98,64 @@ public void deleteUser(Long userId) { } private void disconnectKakao(User user) { - Optional socialLogin = socialLoginRepository.findByUserAndProvider(user, "kakao"); + Optional socialLogin = socialLoginRepository.findByUserAndProvider(user, Constants.KAKAO); if (socialLogin.isPresent()) { try { long providerId = Long.parseLong(socialLogin.get().getProviderId()); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); - headers.set("Authorization", "KakaoAK " + serviceAppAdminKey); + headers.set(AUTHORIZATION_HEADER, KAKAO_AUTH_PREFIX + kakaoConfig.getServiceAppAdminKey()); String body = "target_id_type=user_id&target_id=" + providerId; HttpEntity entity = new HttpEntity<>(body, headers); - ResponseEntity response = restTemplate.exchange(kakaoUnlinkUrl, HttpMethod.POST, entity, String.class); + ResponseEntity response = restTemplate.exchange(kakaoConfig.getUnlinkUrl(), HttpMethod.POST, entity, String.class); if (response.getStatusCode() != HttpStatus.OK) { - log.error("카카오 계정 연동 해제 실패: {}", response.getStatusCode()); + log.error("[UserService] 카카오 계정 연동 해제 실패. HTTP 상태 코드: {}, 사용자 ID: {}", response.getStatusCode(), user.getId()); throw new ApiException(ExceptionCode.SOCIAL_DISCONNECT_FAILED, "카카오 계정 연동 해제 실패"); } socialLoginRepository.delete(socialLogin.get()); } catch (NumberFormatException e) { - log.error("유효하지 않은 카카오 providerId입니다: {}", e.getMessage()); + log.error("[UserService] 유효하지 않은 카카오 providerId입니다. 사용자 ID: {}, providerId: {}", user.getId(), socialLogin.get().getProviderId()); throw new ApiException(ExceptionCode.INVALID_PROVIDER_ID, "유효하지 않은 카카오 providerId입니다."); } } } private void disconnectApple(User user) { - Optional socialLogin = socialLoginRepository.findByUserAndProvider(user, "apple"); + Optional socialLogin = socialLoginRepository.findByUserAndProvider(user, Constants.APPLE); if (socialLogin.isPresent()) { try { String appleRefreshToken = socialLogin.get().getAppleRefreshToken(); if (appleRefreshToken == null || appleRefreshToken.isBlank()) { - log.error("애플 리프레시 토큰이 없습니다: {}", user.getId()); + log.error("[UserService] 애플 리프레시 토큰이 없습니다. 사용자 ID: {}", user.getId()); throw new ApiException(ExceptionCode.MISSING_REFRESH_TOKEN); } - String clientSecret = AppleClientSecretGenerator.generateClientSecret(appleTeamId, appleClientId, appleKeyId, applePrivateKey); + String clientSecret = AppleClientSecretGenerator.generateClientSecret( + appleConfig.getTeamId(), appleConfig.getClientId(), appleConfig.getKeyId(), appleConfig.getPrivateKey() + ); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); - String body = "client_id=" + appleClientId + - "&client_secret=" + clientSecret + - "&token=" + appleRefreshToken; + String body = CLIENT_ID_KEY + appleConfig.getClientId() + + CLIENT_SECRET_KEY + clientSecret + + TOKEN_KEY + appleRefreshToken; HttpEntity entity = new HttpEntity<>(body, headers); - ResponseEntity response = restTemplate.exchange(appleUnlinkUrl, HttpMethod.POST, entity, String.class); + ResponseEntity response = restTemplate.exchange(appleConfig.getUnlinkUrl(), HttpMethod.POST, entity, String.class); if (response.getStatusCode() != HttpStatus.OK) { - log.error("애플 계정 연동 해제 실패: {}", response.getStatusCode()); + log.error("[UserService] 애플 계정 연동 해제 실패. HTTP 상태 코드: {}, 사용자 ID: {}", response.getStatusCode(), user.getId()); throw new ApiException(ExceptionCode.SOCIAL_DISCONNECT_FAILED, "애플 계정 연동 해제 실패"); } socialLoginRepository.delete(socialLogin.get()); } catch (Exception e) { - log.error("애플 계정 연동 해제 중 오류 발생: {}", e.getMessage()); + log.error("[UserService] 애플 계정 연동 해제 중 오류 발생. 사용자 ID: {}, 오류 메시지: {}", user.getId(), e.getMessage()); throw new ApiException(ExceptionCode.SERVER_ERROR, "애플 계정 연동 해제 중 오류 발생"); } } diff --git a/src/main/java/com/ward/ward_server/api/wishBrand/WishBrandService.java b/src/main/java/com/ward/ward_server/api/wishBrand/WishBrandService.java index 86c1f32..4832841 100644 --- a/src/main/java/com/ward/ward_server/api/wishBrand/WishBrandService.java +++ b/src/main/java/com/ward/ward_server/api/wishBrand/WishBrandService.java @@ -19,6 +19,7 @@ @Service @RequiredArgsConstructor +@Transactional(readOnly = true) public class WishBrandService { private final WishBrandRepository wishBrandRepository; private final BrandRepository brandRepository; @@ -33,7 +34,6 @@ public void createWishBrand(long userId, long brandId) { wishBrandRepository.save(new WishBrand(user, brand)); } - @Transactional(readOnly = true) public PageResponse getWishBrandListByUser(long userId, BasicSort basicSort, int page) { Page wishBrandPage = wishBrandRepository.getWishBrandPage(userId, basicSort, PageRequest.of(page, API_PAGE_SIZE)); return new PageResponse<>(wishBrandPage.getContent(), wishBrandPage); diff --git a/src/main/java/com/ward/ward_server/api/wishItem/WishItemService.java b/src/main/java/com/ward/ward_server/api/wishItem/WishItemService.java index 721293a..b76537f 100644 --- a/src/main/java/com/ward/ward_server/api/wishItem/WishItemService.java +++ b/src/main/java/com/ward/ward_server/api/wishItem/WishItemService.java @@ -1,9 +1,7 @@ package com.ward.ward_server.api.wishItem; -import com.ward.ward_server.api.entry.repository.EntryRecordRepository; import com.ward.ward_server.api.item.entity.Item; import com.ward.ward_server.api.item.repository.ItemRepository; -import com.ward.ward_server.api.releaseInfo.repository.ReleaseInfoRepository; import com.ward.ward_server.api.user.entity.User; import com.ward.ward_server.api.user.repository.UserRepository; import com.ward.ward_server.api.wishItem.repository.WishItemRepository; @@ -23,6 +21,7 @@ @Service @Slf4j @RequiredArgsConstructor +@Transactional(readOnly = true) public class WishItemService { private final WishItemRepository wishItemRepository; private final UserRepository userRepository; @@ -38,7 +37,6 @@ public void createWishItem(long userId, long itemId) { wishItemRepository.save(new WishItem(user, item)); } - @Transactional(readOnly = true) public PageResponse getWishItemListByUser(long userId, BasicSort sort, int page) { Page wishItemPage = wishItemRepository.getWishItemPage(userId, sort, PageRequest.of(page, API_PAGE_SIZE)); return new PageResponse<>(wishItemPage.getContent(), wishItemPage); diff --git a/src/main/java/com/ward/ward_server/global/config/AppleConfig.java b/src/main/java/com/ward/ward_server/global/config/AppleConfig.java new file mode 100644 index 0000000..fa305a2 --- /dev/null +++ b/src/main/java/com/ward/ward_server/global/config/AppleConfig.java @@ -0,0 +1,18 @@ +package com.ward.ward_server.global.config; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Getter +@Setter +@Configuration +@ConfigurationProperties(prefix = "apple") +public class AppleConfig { + private String unlinkUrl; + private String clientId; + private String keyId; + private String teamId; + private String privateKey; +} diff --git a/src/main/java/com/ward/ward_server/global/config/CrawlerConfig.java b/src/main/java/com/ward/ward_server/global/config/CrawlerConfig.java new file mode 100644 index 0000000..186a064 --- /dev/null +++ b/src/main/java/com/ward/ward_server/global/config/CrawlerConfig.java @@ -0,0 +1,15 @@ +package com.ward.ward_server.global.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +public class CrawlerConfig { + + @Value("${crawler.chromeDriverPath}") + private String chromeDriverPath; + + public String getChromeDriverPath() { + return chromeDriverPath; + } +} diff --git a/src/main/java/com/ward/ward_server/api/item/webCrawler/CrawlerProperties.java b/src/main/java/com/ward/ward_server/global/config/KakaoConfig.java similarity index 54% rename from src/main/java/com/ward/ward_server/api/item/webCrawler/CrawlerProperties.java rename to src/main/java/com/ward/ward_server/global/config/KakaoConfig.java index ba3a48f..27ba4c1 100644 --- a/src/main/java/com/ward/ward_server/api/item/webCrawler/CrawlerProperties.java +++ b/src/main/java/com/ward/ward_server/global/config/KakaoConfig.java @@ -1,4 +1,4 @@ -package com.ward.ward_server.api.item.webCrawler; +package com.ward.ward_server.global.config; import lombok.Getter; import lombok.Setter; @@ -8,7 +8,8 @@ @Getter @Setter @Configuration -@ConfigurationProperties(prefix = "crawler") -public class CrawlerProperties { - private String chromeDriverPath; +@ConfigurationProperties(prefix = "kakao") +public class KakaoConfig { + private String unlinkUrl; + private String serviceAppAdminKey; }