From 416b32369068ce9b16b4ea731445706a6d92cca1 Mon Sep 17 00:00:00 2001 From: rlarlgnszx Date: Thu, 11 Jul 2024 18:37:24 +0900 Subject: [PATCH 1/3] =?UTF-8?q?[fix]=20=EC=82=AD=EC=A0=9C=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20rebase=20-=20#63?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/CICD-dev.yml | 10 +- .gitignore | 5 +- .../dto/response/AdvGetAllRes.java | 22 +++- .../dateroad/auth/config/SecurityConfig.java | 1 - .../course/service/CourseSpecifications.java | 33 +++--- .../org/dateroad/date/api/DateController.java | 38 ++++++ .../date/dto/request/DateCreateReq.java | 20 ++++ .../date/dto/request/PlaceCreateReq.java | 8 ++ .../date/dto/request/TagCreateReq.java | 8 ++ .../date/dto/response/DateDetailRes.java | 43 +++++++ .../date/dto/response/PlaceGetRes.java | 21 ++++ .../dateroad/date/dto/response/TagGetRes.java | 17 +++ .../dateroad/date/service/DateRepository.java | 7 ++ .../dateroad/date/service/DateService.java | 108 ++++++++++++++++++ .../org/dateroad/user/api/UserController.java | 19 +++ .../dto/request/AppleWithdrawAuthCodeReq.java | 6 + .../dateroad/user/service/AuthService.java | 72 +++++++++--- .../java/org/dateroad/code/FailureCode.java | 10 +- .../dateroad/common/ObjectMapperConfig.java | 2 +- .../exception/BadRequestException.java | 13 +++ .../exception/ForbiddenException.java | 13 +++ .../date/repository/DatePlaceRepository.java | 12 ++ .../date/repository/DateTagRepository.java | 12 ++ ...ository.java => DataAccessRepository.java} | 2 +- .../org/dateroad/place/domain/DatePlace.java | 2 +- .../repository/CoursePlaceRepository.java | 4 +- .../repository/RefreshTokenRepository.java | 2 +- .../java/org/dateroad/tag/domain/DateTag.java | 6 +- .../java/org/dateroad/user/domain/User.java | 16 +-- .../user/repository/UserRepository.java | 1 - dateroad-external/build.gradle | 4 + .../apple/AppleClientPublicKeyGenerator.java | 2 - .../apple/AppleClientSecretGenerator.java | 57 +++++++++ .../dateroad/feign/apple/AppleFeignApi.java | 15 +++ ...dProvider.java => AppleFeignProvider.java} | 30 ++++- .../apple/AppleIdentityJWTValidator.java | 11 +- .../dateroad/feign/apple/AppleProperties.java | 19 +++ .../dateroad/feign/apple/AppleTokenRes.java | 10 ++ .../dateroad/feign/kakao/KakaoErrorRes.java | 11 -- .../dateroad/feign/kakao/KakaoFeignApi.java | 12 +- .../feign/kakao/KakaoFeignProvider.java | 80 +++++++++++++ .../dateroad/feign/kakao/KakaoHeaderType.java | 14 +++ .../kakao/KakaoPlatformUserIdProvider.java | 60 ---------- .../dateroad/feign/kakao/KakaoProperties.java | 15 +++ .../feign/kakao/KakaoRequestType.java | 6 + .../kakao/dto/response/KaKaoErrorRes.java | 9 ++ .../kakao/dto/response/KaKaoUnlinkRes.java | 6 + .../response}/KakaoAccessTokenInfoRes.java | 2 +- 48 files changed, 752 insertions(+), 144 deletions(-) create mode 100644 dateroad-api/src/main/java/org/dateroad/date/api/DateController.java create mode 100644 dateroad-api/src/main/java/org/dateroad/date/dto/request/DateCreateReq.java create mode 100644 dateroad-api/src/main/java/org/dateroad/date/dto/request/PlaceCreateReq.java create mode 100644 dateroad-api/src/main/java/org/dateroad/date/dto/request/TagCreateReq.java create mode 100644 dateroad-api/src/main/java/org/dateroad/date/dto/response/DateDetailRes.java create mode 100644 dateroad-api/src/main/java/org/dateroad/date/dto/response/PlaceGetRes.java create mode 100644 dateroad-api/src/main/java/org/dateroad/date/dto/response/TagGetRes.java create mode 100644 dateroad-api/src/main/java/org/dateroad/date/service/DateRepository.java create mode 100644 dateroad-api/src/main/java/org/dateroad/date/service/DateService.java create mode 100644 dateroad-api/src/main/java/org/dateroad/user/dto/request/AppleWithdrawAuthCodeReq.java create mode 100644 dateroad-common/src/main/java/org/dateroad/exception/BadRequestException.java create mode 100644 dateroad-common/src/main/java/org/dateroad/exception/ForbiddenException.java create mode 100644 dateroad-domain/src/main/java/org/dateroad/date/repository/DatePlaceRepository.java create mode 100644 dateroad-domain/src/main/java/org/dateroad/date/repository/DateTagRepository.java rename dateroad-domain/src/main/java/org/dateroad/dateAccess/repository/{DateAccessRepository.java => DataAccessRepository.java} (88%) create mode 100644 dateroad-external/src/main/java/org/dateroad/feign/apple/AppleClientSecretGenerator.java rename dateroad-external/src/main/java/org/dateroad/feign/apple/{ApplePlatformUserIdProvider.java => AppleFeignProvider.java} (55%) create mode 100644 dateroad-external/src/main/java/org/dateroad/feign/apple/AppleProperties.java create mode 100644 dateroad-external/src/main/java/org/dateroad/feign/apple/AppleTokenRes.java delete mode 100644 dateroad-external/src/main/java/org/dateroad/feign/kakao/KakaoErrorRes.java create mode 100644 dateroad-external/src/main/java/org/dateroad/feign/kakao/KakaoFeignProvider.java create mode 100644 dateroad-external/src/main/java/org/dateroad/feign/kakao/KakaoHeaderType.java delete mode 100644 dateroad-external/src/main/java/org/dateroad/feign/kakao/KakaoPlatformUserIdProvider.java create mode 100644 dateroad-external/src/main/java/org/dateroad/feign/kakao/KakaoProperties.java create mode 100644 dateroad-external/src/main/java/org/dateroad/feign/kakao/KakaoRequestType.java create mode 100644 dateroad-external/src/main/java/org/dateroad/feign/kakao/dto/response/KaKaoErrorRes.java create mode 100644 dateroad-external/src/main/java/org/dateroad/feign/kakao/dto/response/KaKaoUnlinkRes.java rename dateroad-external/src/main/java/org/dateroad/feign/kakao/{ => dto/response}/KakaoAccessTokenInfoRes.java (78%) diff --git a/.github/workflows/CICD-dev.yml b/.github/workflows/CICD-dev.yml index f98254cd..a27ee0b6 100644 --- a/.github/workflows/CICD-dev.yml +++ b/.github/workflows/CICD-dev.yml @@ -27,7 +27,15 @@ jobs: echo "${{ secrets.CD_APPLICATION }}" > ./application.yml cat ./application.yml working-directory: ${{ env.working-directory }} - + + - name: AuthKey_39CUV6ST46.p8 생성 + run: | + mkdir -p dateroad-external/src/main/resources/static && cd $_ + touch ./AuthKey_39CUV6ST46.p8 + echo "${{ secrets.CD_APPLICATION }}" > ./AuthKey_39CUV6ST46.p8 + cat ./AuthKey_39CUV6ST46.p8 + working-directory: ${{ env.working-directory }} + - name: 빌드 run: | chmod +x gradlew diff --git a/.gitignore b/.gitignore index d157e19f..6b15a3d7 100644 --- a/.gitignore +++ b/.gitignore @@ -43,4 +43,7 @@ out/ ### Yml ### dateroad-api/src/main/resources/application.yml -dateroad-api/src/test/resources/application.yml \ No newline at end of file +dateroad-api/src/test/resources/application.yml + +### File ### +**/static/*.p8 \ No newline at end of file diff --git a/dateroad-api/src/main/java/org/dateroad/advertisment/dto/response/AdvGetAllRes.java b/dateroad-api/src/main/java/org/dateroad/advertisment/dto/response/AdvGetAllRes.java index f6c70650..7565ab40 100644 --- a/dateroad-api/src/main/java/org/dateroad/advertisment/dto/response/AdvGetAllRes.java +++ b/dateroad-api/src/main/java/org/dateroad/advertisment/dto/response/AdvGetAllRes.java @@ -1,23 +1,35 @@ package org.dateroad.advertisment.dto.response; import java.util.List; +import lombok.AccessLevel; +import lombok.Builder; import org.dateroad.advertisement.domain.AdTagType; import org.dateroad.advertisement.domain.Advertisment; +@Builder(access = AccessLevel.PRIVATE) public record AdvGetAllRes( List advertismentDtoResList ) { + public static AdvGetAllRes of(List advertismentDtoResList) { + return AdvGetAllRes.builder() + .advertismentDtoResList(advertismentDtoResList) + .build(); + } + + @Builder(access = AccessLevel.PRIVATE) public record AdvertismentDtoRes( Long advertismentId, String thumbnail, String title, AdTagType tag - ){ + ) { public static AdvertismentDtoRes of(Advertisment advertisment) { - return new AdvertismentDtoRes(advertisment.getId(), advertisment.getTitle(), advertisment.getThumbnail(), advertisment.getTag()); + return AdvertismentDtoRes.builder() + .advertismentId(advertisment.getId()) + .thumbnail(advertisment.getThumbnail()) + .title(advertisment.getTitle()) + .tag(advertisment.getTag()) + .build(); } } - public static AdvGetAllRes of(List advertismentDtoResList) { - return new AdvGetAllRes(advertismentDtoResList); - } } diff --git a/dateroad-api/src/main/java/org/dateroad/auth/config/SecurityConfig.java b/dateroad-api/src/main/java/org/dateroad/auth/config/SecurityConfig.java index a2ade14e..a57fabc7 100644 --- a/dateroad-api/src/main/java/org/dateroad/auth/config/SecurityConfig.java +++ b/dateroad-api/src/main/java/org/dateroad/auth/config/SecurityConfig.java @@ -6,7 +6,6 @@ import org.dateroad.auth.filter.JwtAuthenticationFilter; import org.dateroad.auth.jwt.JwtProvider; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; diff --git a/dateroad-api/src/main/java/org/dateroad/course/service/CourseSpecifications.java b/dateroad-api/src/main/java/org/dateroad/course/service/CourseSpecifications.java index 4d26f6c7..8e3cd2d1 100644 --- a/dateroad-api/src/main/java/org/dateroad/course/service/CourseSpecifications.java +++ b/dateroad-api/src/main/java/org/dateroad/course/service/CourseSpecifications.java @@ -1,33 +1,34 @@ package org.dateroad.course.service; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.Path; import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; import java.util.ArrayList; import java.util.List; +import java.util.Optional; +import java.util.function.BiFunction; import org.dateroad.course.dto.request.CourseGetAllReq; import org.dateroad.date.domain.Course; import org.springframework.data.jpa.domain.Specification; public class CourseSpecifications { public static Specification filterByCriteria(CourseGetAllReq courseGetAllReq) { - String city = courseGetAllReq.city(); - String country = courseGetAllReq.country(); - Integer cost = courseGetAllReq.cost(); return (root, query, criteriaBuilder) -> { List predicates = new ArrayList<>(); - - if (city != null && !city.isEmpty()) { - predicates.add(criteriaBuilder.equal(root.get("city"), city)); - } - - if (country != null && !country.isEmpty()) { - predicates.add(criteriaBuilder.equal(root.get("country"), country)); - } - - if (cost != null) { - predicates.add(criteriaBuilder.lessThanOrEqualTo(root.get("cost"), cost)); - } - + addPredicate(predicates, criteriaBuilder, root, "city", courseGetAllReq.city(), criteriaBuilder::equal); + addPredicate(predicates, criteriaBuilder, root, "country", courseGetAllReq.country(), criteriaBuilder::equal); + addPredicate(predicates, criteriaBuilder, root, "cost", courseGetAllReq.cost(), criteriaBuilder::lessThanOrEqualTo); return criteriaBuilder.and(predicates.toArray(new Predicate[0])); }; } + + private static void addPredicate(List predicates, CriteriaBuilder criteriaBuilder, Root root, + String attributeName, T value, + BiFunction, T, Predicate> predicateFunction) { + Optional.ofNullable(value) + .ifPresent(val -> predicates.add( + predicateFunction.apply(root.get(attributeName), val)) + ); + } } diff --git a/dateroad-api/src/main/java/org/dateroad/date/api/DateController.java b/dateroad-api/src/main/java/org/dateroad/date/api/DateController.java new file mode 100644 index 00000000..4374aff9 --- /dev/null +++ b/dateroad-api/src/main/java/org/dateroad/date/api/DateController.java @@ -0,0 +1,38 @@ +package org.dateroad.date.api; + +import lombok.RequiredArgsConstructor; +import org.dateroad.auth.argumentresolve.UserId; +import org.dateroad.date.dto.request.DateCreateReq; +import org.dateroad.date.dto.response.DateDetailRes; +import org.dateroad.date.service.DateService; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RequiredArgsConstructor +@RequestMapping("/api/v1/dates") +@RestController +public class DateController { + private final DateService dateService; + + @PostMapping + public ResponseEntity createDate(@UserId final Long userId, + @RequestBody final DateCreateReq dateCreateReq) { + dateService.createDate(userId, dateCreateReq); + return ResponseEntity.status(HttpStatus.CREATED).build(); + } + + @GetMapping("/{dateId}") + public ResponseEntity getDateDetail(@RequestHeader final Long userId, + @PathVariable final Long dateId) { + DateDetailRes dateDetailRes = dateService.getDateDetail(userId, dateId); + return ResponseEntity.ok(dateDetailRes); + } + + @DeleteMapping("/{dateId}") + public ResponseEntity deleteDate(@UserId final Long userId, + @PathVariable final Long dateId) { + dateService.deleteDate(userId, dateId); + return ResponseEntity.ok().build(); + } +} diff --git a/dateroad-api/src/main/java/org/dateroad/date/dto/request/DateCreateReq.java b/dateroad-api/src/main/java/org/dateroad/date/dto/request/DateCreateReq.java new file mode 100644 index 00000000..57832b3f --- /dev/null +++ b/dateroad-api/src/main/java/org/dateroad/date/dto/request/DateCreateReq.java @@ -0,0 +1,20 @@ +package org.dateroad.date.dto.request; + +import com.fasterxml.jackson.annotation.JsonFormat; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.List; + +public record DateCreateReq( + String title, + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy.MM.dd", timezone = "Asia/Seoul") + LocalDate date, + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm", timezone = "Asia/Seoul") + LocalTime startAt, + List tags, + String country, + String city, + List places +) { +} diff --git a/dateroad-api/src/main/java/org/dateroad/date/dto/request/PlaceCreateReq.java b/dateroad-api/src/main/java/org/dateroad/date/dto/request/PlaceCreateReq.java new file mode 100644 index 00000000..a44533e6 --- /dev/null +++ b/dateroad-api/src/main/java/org/dateroad/date/dto/request/PlaceCreateReq.java @@ -0,0 +1,8 @@ +package org.dateroad.date.dto.request; + +public record PlaceCreateReq( + String name, + float duration, + int sequence +) { +} diff --git a/dateroad-api/src/main/java/org/dateroad/date/dto/request/TagCreateReq.java b/dateroad-api/src/main/java/org/dateroad/date/dto/request/TagCreateReq.java new file mode 100644 index 00000000..d20b7ae3 --- /dev/null +++ b/dateroad-api/src/main/java/org/dateroad/date/dto/request/TagCreateReq.java @@ -0,0 +1,8 @@ +package org.dateroad.date.dto.request; + +import org.dateroad.tag.domain.DateTagType; + +public record TagCreateReq( + DateTagType tag +) { +} diff --git a/dateroad-api/src/main/java/org/dateroad/date/dto/response/DateDetailRes.java b/dateroad-api/src/main/java/org/dateroad/date/dto/response/DateDetailRes.java new file mode 100644 index 00000000..bea522ee --- /dev/null +++ b/dateroad-api/src/main/java/org/dateroad/date/dto/response/DateDetailRes.java @@ -0,0 +1,43 @@ +package org.dateroad.date.dto.response; + +import lombok.AccessLevel; +import lombok.Builder; +import org.dateroad.date.domain.Date; +import org.dateroad.place.domain.DatePlace; +import org.dateroad.tag.domain.DateTag; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.List; + +@Builder(access = AccessLevel.PRIVATE) +public record DateDetailRes( + Long dateId, + String title, + LocalTime startAt, + String city, + List tags, + LocalDate date, + List places +) { + + public static DateDetailRes of(Date date, List tags, List places) { + + List tagGetRes = tags.stream() + .map(TagGetRes::of) + .toList(); + + List placeGetRes = places.stream() + .map(PlaceGetRes::of) + .toList(); + + return DateDetailRes.builder() + .dateId(date.getId()) + .title(date.getTitle()) + .startAt(date.getStartAt()) + .tags(tagGetRes) + .date(date.getDate()) + .places(placeGetRes) + .build(); + } +} diff --git a/dateroad-api/src/main/java/org/dateroad/date/dto/response/PlaceGetRes.java b/dateroad-api/src/main/java/org/dateroad/date/dto/response/PlaceGetRes.java new file mode 100644 index 00000000..78051056 --- /dev/null +++ b/dateroad-api/src/main/java/org/dateroad/date/dto/response/PlaceGetRes.java @@ -0,0 +1,21 @@ +package org.dateroad.date.dto.response; + +import lombok.AccessLevel; +import lombok.Builder; +import org.dateroad.place.domain.DatePlace; + +@Builder(access = AccessLevel.PRIVATE) + +public record PlaceGetRes( + String name, + float duration, + int sequence +) { + public static PlaceGetRes of(DatePlace datePlace) { + return PlaceGetRes.builder() + .name(datePlace.getName()) + .duration(datePlace.getDuration()) + .sequence(datePlace.getSequence()) + .build(); + } +} diff --git a/dateroad-api/src/main/java/org/dateroad/date/dto/response/TagGetRes.java b/dateroad-api/src/main/java/org/dateroad/date/dto/response/TagGetRes.java new file mode 100644 index 00000000..6bb1b9da --- /dev/null +++ b/dateroad-api/src/main/java/org/dateroad/date/dto/response/TagGetRes.java @@ -0,0 +1,17 @@ +package org.dateroad.date.dto.response; + +import lombok.AccessLevel; +import lombok.Builder; +import org.dateroad.tag.domain.DateTag; +import org.dateroad.tag.domain.DateTagType; + +@Builder(access = AccessLevel.PRIVATE) +public record TagGetRes( + DateTagType tag +) { + public static TagGetRes of(DateTag dateTag) { + return TagGetRes.builder() + .tag(dateTag.getDateTagType()) + .build(); + } +} diff --git a/dateroad-api/src/main/java/org/dateroad/date/service/DateRepository.java b/dateroad-api/src/main/java/org/dateroad/date/service/DateRepository.java new file mode 100644 index 00000000..e723c8b3 --- /dev/null +++ b/dateroad-api/src/main/java/org/dateroad/date/service/DateRepository.java @@ -0,0 +1,7 @@ +package org.dateroad.date.service; + +import org.dateroad.date.domain.Date; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface DateRepository extends JpaRepository { +} diff --git a/dateroad-api/src/main/java/org/dateroad/date/service/DateService.java b/dateroad-api/src/main/java/org/dateroad/date/service/DateService.java new file mode 100644 index 00000000..b78a6c53 --- /dev/null +++ b/dateroad-api/src/main/java/org/dateroad/date/service/DateService.java @@ -0,0 +1,108 @@ +package org.dateroad.date.service; + +import lombok.RequiredArgsConstructor; +import org.dateroad.code.FailureCode; +import org.dateroad.date.domain.Date; +import org.dateroad.date.dto.request.DateCreateReq; +import org.dateroad.date.dto.request.PlaceCreateReq; +import org.dateroad.date.dto.request.TagCreateReq; +import org.dateroad.date.dto.response.DateDetailRes; +import org.dateroad.date.repository.DatePlaceRepository; +import org.dateroad.date.repository.DateTagRepository; +import org.dateroad.exception.EntityNotFoundException; +import org.dateroad.exception.ForbiddenException; +import org.dateroad.place.domain.DatePlace; +import org.dateroad.tag.domain.DateTag; +import org.dateroad.user.domain.User; +import org.dateroad.user.repository.UserRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@RequiredArgsConstructor +@Transactional(readOnly = true) +@Service +public class DateService { + private final DateRepository dateRepository; + private final UserRepository userRepository; + private final DateTagRepository dateTagRepository; + private final DatePlaceRepository datePlaceRepository; + + @Transactional + public void createDate(final Long userId, final DateCreateReq dateCreateReq) { + User findUser = getUser(userId); + Date date = createDate(findUser, dateCreateReq); + createDateTag(date, dateCreateReq.tags()); + createDatePlace(date, dateCreateReq.places()); + } + + public DateDetailRes getDateDetail(final Long userId, final Long dateId) { + User findUser = getUser(userId); + Date findDate = getDate(dateId); + validateDate(findUser, findDate); + List findDateTags = getDateTag(findDate); + List findDatePlaces = getDatePlace(findDate); + return DateDetailRes.of(findDate, findDateTags, findDatePlaces); + } + + @Transactional + public void deleteDate(final Long userId, final Long dateId) { + User findUser = getUser(userId); + Date findDate = getDate(dateId); + validateDate(findUser, findDate); + datePlaceRepository.deleteByDateId(dateId); + dateTagRepository.deleteByDateId(dateId); + dateRepository.deleteById(dateId); + } + + private User getUser(Long userId) { + return userRepository.findById(userId) + .orElseThrow(() -> new EntityNotFoundException(FailureCode.USER_NOT_FOUND)); + } + + private Date createDate(User findUser, DateCreateReq dateCreateReq) { + Date date = Date.create(findUser, dateCreateReq.title(), dateCreateReq.date(), + dateCreateReq.startAt(), dateCreateReq.country(), dateCreateReq.city()); + return dateRepository.save(date); + } + + private void createDateTag(Date date, List tags) { + List dateTags = tags.stream() + .map(t -> DateTag.create(date, t.tag())).toList(); + dateTagRepository.saveAll(dateTags); + } + + private void createDatePlace(Date date, List places) { + List datePlaces = places.stream() + .map(p -> DatePlace.create(date, p.name(), p.duration(), p.sequence())).toList(); + datePlaceRepository.saveAll(datePlaces); + } + + private Date getDate(Long dateId) { + return dateRepository.findById(dateId) + .orElseThrow(() -> new EntityNotFoundException(FailureCode.DATE_NOT_FOUND)); + } + + private void validateDate(User findUser, Date findDate) { + if (!findUser.equals(findDate.getUser())) { + throw new ForbiddenException(FailureCode.DATE_DELETE_ACCESS_DENIED); + } + } + + private List getDateTag(Date date) { + List dateTags = dateTagRepository.findByDate(date); + if (dateTags == null | dateTags.isEmpty()) { + throw new EntityNotFoundException(FailureCode.DATE_TAG_NOT_FOUND); + } + return dateTags; + } + + private List getDatePlace(Date date) { + List datePlaces = datePlaceRepository.findByDate(date); + if (datePlaces == null | datePlaces.isEmpty()) { + throw new EntityNotFoundException(FailureCode.DATE_PLACE_NOT_FOUND); + } + return datePlaces; + } +} diff --git a/dateroad-api/src/main/java/org/dateroad/user/api/UserController.java b/dateroad-api/src/main/java/org/dateroad/user/api/UserController.java index 26ac423a..d78723e5 100644 --- a/dateroad-api/src/main/java/org/dateroad/user/api/UserController.java +++ b/dateroad-api/src/main/java/org/dateroad/user/api/UserController.java @@ -1,6 +1,8 @@ package org.dateroad.user.api; import lombok.RequiredArgsConstructor; +import org.dateroad.auth.argumentresolve.UserId; +import org.dateroad.user.dto.request.AppleWithdrawAuthCodeReq; import org.dateroad.user.dto.request.UserSignInReq; import org.dateroad.user.dto.request.UserSignUpReq; import org.dateroad.user.dto.response.UserSignInRes; @@ -35,6 +37,14 @@ public ResponseEntity signIn(@RequestHeader(AUTHORIZATION) final .ok(userSignInRes); } + @DeleteMapping("/signout") + public ResponseEntity signout(@UserId final Long userId) { + authService.signout(userId); + return ResponseEntity + .ok() + .build(); + } + @GetMapping("/check") public ResponseEntity checkNickname(@RequestParam("name") final String nickname) { authService.checkNickname(nickname); @@ -42,4 +52,13 @@ public ResponseEntity checkNickname(@RequestParam("name") final String nic .ok() .build(); } + + @DeleteMapping("/withdraw") + public ResponseEntity withdraw(@UserId final Long userId, + @RequestBody final AppleWithdrawAuthCodeReq appleWithdrawAuthCodeReq) { + authService.withdraw(userId, appleWithdrawAuthCodeReq); + return ResponseEntity + .ok() + .build(); + } } diff --git a/dateroad-api/src/main/java/org/dateroad/user/dto/request/AppleWithdrawAuthCodeReq.java b/dateroad-api/src/main/java/org/dateroad/user/dto/request/AppleWithdrawAuthCodeReq.java new file mode 100644 index 00000000..9732fcc2 --- /dev/null +++ b/dateroad-api/src/main/java/org/dateroad/user/dto/request/AppleWithdrawAuthCodeReq.java @@ -0,0 +1,6 @@ +package org.dateroad.user.dto.request; + +public record AppleWithdrawAuthCodeReq( + String authCode +) { +} diff --git a/dateroad-api/src/main/java/org/dateroad/user/service/AuthService.java b/dateroad-api/src/main/java/org/dateroad/user/service/AuthService.java index 11a1546d..57364959 100644 --- a/dateroad-api/src/main/java/org/dateroad/user/service/AuthService.java +++ b/dateroad-api/src/main/java/org/dateroad/user/service/AuthService.java @@ -4,17 +4,17 @@ import org.dateroad.auth.jwt.JwtProvider; import org.dateroad.auth.jwt.Token; import org.dateroad.code.FailureCode; -import org.dateroad.exception.ConflictException; -import org.dateroad.exception.EntityNotFoundException; -import org.dateroad.exception.InvalidValueException; -import org.dateroad.feign.apple.ApplePlatformUserIdProvider; -import org.dateroad.feign.kakao.KakaoPlatformUserIdProvider; +import org.dateroad.exception.*; +import org.dateroad.feign.apple.AppleFeignProvider; +import org.dateroad.feign.kakao.KakaoFeignApi; +import org.dateroad.feign.kakao.KakaoFeignProvider; import org.dateroad.refreshtoken.repository.RefreshTokenRepository; import org.dateroad.tag.domain.DateTagType; import org.dateroad.tag.domain.UserTag; import org.dateroad.tag.repository.UserTagRepository; import org.dateroad.user.domain.Platform; import org.dateroad.user.domain.User; +import org.dateroad.user.dto.request.AppleWithdrawAuthCodeReq; import org.dateroad.user.dto.request.UserSignInReq; import org.dateroad.user.repository.UserRepository; import org.dateroad.user.dto.request.UserSignUpReq; @@ -26,14 +26,16 @@ import java.util.List; @RequiredArgsConstructor +@Transactional(readOnly = true) @Service public class AuthService { private final UserRepository userRepository; - private final KakaoPlatformUserIdProvider kakaoPlatformUserIdProvider; - private final ApplePlatformUserIdProvider applePlatformUserIdProvider; + private final KakaoFeignProvider kakaoFeignProvider; + private final AppleFeignProvider appleFeignProvider; private final UserTagRepository userTagRepository; private final JwtProvider jwtProvider; private final RefreshTokenRepository refreshTokenRepository; + private final KakaoFeignApi kakaoFeignApi; @Transactional public UsersignUpRes signUp(final String token, final UserSignUpReq userSignUpReq) { @@ -51,27 +53,53 @@ public UsersignUpRes signUp(final String token, final UserSignUpReq userSignUpRe @Transactional public UserSignInRes signIn(final String token, final UserSignInReq userSignInReq) { String platformUserId = getUserPlatformId(userSignInReq.platform(), token); - User foundUser = getUser(userSignInReq.platform(), platformUserId); + User foundUser = getUserByPlatformAndPlatformUserId(userSignInReq.platform(), platformUserId); Token issuedToken = jwtProvider.issueToken(foundUser.getId()); return UserSignInRes.of(foundUser.getId(), issuedToken.accessToken(), issuedToken.refreshToken()); } + @Transactional + public void withdraw(final Long userId, final AppleWithdrawAuthCodeReq AppleWithdrawAuthCodeReq) { + + //todo: #45브랜치 머지후, 메서드 이용 + User foundUser = userRepository.findById(userId).orElseThrow(EntityNotFoundException::new); + + if (foundUser.getPlatForm() == Platform.KAKAO) { //카카오 유저면 카카오와 연결 끊기 + kakaoFeignProvider.unLinkWithKakao(foundUser.getPlatformUserId()); + } else if (foundUser.getPlatForm() == Platform.APPLE) { //애플 유저면 애플이랑 연결 끊기 + appleFeignProvider.revokeUser(AppleWithdrawAuthCodeReq.authCode()); + } else { + throw new BadRequestException(FailureCode.INVALID_PLATFORM_TYPE); + } + + //todo: #45브랜치 머지후, 메서드 이용 + refreshTokenRepository.deleteByUserId(foundUser.getId()); + userRepository.deleteById(foundUser.getId()); + } + //닉네임 중복체크 public void checkNickname(final String nickname) { if (!userRepository.existsByName(nickname)) { return; } else { throw new ConflictException(FailureCode.DUPLICATE_NICKNAME); } + } + + @Transactional + public void signout(final long userId) { + User foundUser = getUserByUserId(userId); + deleteRefreshToken(foundUser.getId()); } + //플랫폼 유저 아이디 가져오기 (카카오 or 애플) private String getUserPlatformId(final Platform platform, final String token) { if (platform == Platform.APPLE) { - return applePlatformUserIdProvider.getApplePlatformUserId(token); + return appleFeignProvider.getApplePlatformUserId(token); } else if (platform == Platform.KAKAO) { - return kakaoPlatformUserIdProvider.getKakaoPlatformUserId(token); + return kakaoFeignProvider.getKakaoPlatformUserId(token); } else { - throw new InvalidValueException(FailureCode.INVALID_PLATFORM_TYPE); + throw new BadRequestException(FailureCode.INVALID_PLATFORM_TYPE); } } @@ -96,15 +124,29 @@ private void saveUserTag(final User savedUser, final List userTags) .toList(); } - //유저 가져오기(검색) - private User getUser(final Platform platform, final String platformUserId) { + //유저 가져오기(platform이랑 platformUserId 사용) + private User getUserByPlatformAndPlatformUserId(final Platform platform, final String platformUserId) { return userRepository.findUserByPlatFormAndPlatformUserId(platform, platformUserId) - .orElseThrow(() -> new EntityNotFoundException(FailureCode.USER_NOT_FOUND)); + .orElseThrow(() -> new EntityNotFoundException(FailureCode.USER_NOT_FOUND) + ); + } + + //유저 가져오기(userId 사용) + private User getUserByUserId(final long userId) { + return userRepository.findById(userId).orElseThrow( + () -> new EntityNotFoundException(FailureCode.USER_NOT_FOUND) + ); } + //태그 리스트 사이즈 검증 private void validateUserTagSize(final List userTags) { if (userTags.isEmpty() || userTags.size() > 3) { - throw new InvalidValueException((FailureCode.WRONG_USER_TAG_SIZE)); + throw new BadRequestException((FailureCode.WRONG_USER_TAG_SIZE)); } } + + //refreshToken 삭제 + private void deleteRefreshToken(final long userId) { + refreshTokenRepository.deleteByUserId(userId); + } } diff --git a/dateroad-common/src/main/java/org/dateroad/code/FailureCode.java b/dateroad-common/src/main/java/org/dateroad/code/FailureCode.java index 683dcdf2..ee6f5c1a 100644 --- a/dateroad-common/src/main/java/org/dateroad/code/FailureCode.java +++ b/dateroad-common/src/main/java/org/dateroad/code/FailureCode.java @@ -3,7 +3,6 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.RequiredArgsConstructor; -import org.springframework.boot.actuate.autoconfigure.observation.ObservationProperties; import org.springframework.http.HttpStatus; @Getter @@ -36,19 +35,26 @@ public enum FailureCode { INVALID_REFRESH_TOKEN_VALUE(HttpStatus.UNAUTHORIZED, "e4024", "잘못된 리프레시토큰입니다."), EXPIRED_REFRESH_TOKEN(HttpStatus.UNAUTHORIZED, "e4025", "리프레시 토큰 기간이 만료되었습니다. 재로그인 해주세요"), + INVALID_KAKAO_ACCESS(HttpStatus.UNAUTHORIZED, "e4026", "잘못된 카카오 통신 접근입니다."), + UN_LINK_WITH_KAKAO_UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "e4027", "카카오 연결 끊기 통신에 실패했습니다"), + INVALID_APPLE_TOKEN_ACCESS(HttpStatus.UNAUTHORIZED, "e4028", "잘못된 애플 토큰 통신 접근입니다."), /** * 403 Forbidden */ FORBIDDEN(HttpStatus.FORBIDDEN, "e4030", "리소스 접근 권한이 없습니다."), + DATE_DELETE_ACCESS_DENIED(HttpStatus.FORBIDDEN, "e4032", "해당 일정에 권한이 없습니다."), /** * 404 Not Found */ ENTITY_NOT_FOUND(HttpStatus.NOT_FOUND, "e4040", "대상을 찾을 수 없습니다."), TOKEN_TYPE_NOT_FOUND(HttpStatus.NOT_FOUND, "e4041", "찾을 수 없는 토큰 타입입니다."), + USER_NOT_FOUND(HttpStatus.NOT_FOUND, "e4042", "유저를 찾을 수 없습니다."), COURSE_THUMBNAIL_NOT_FOUND(HttpStatus.NOT_FOUND, "e4043", "코스 썸네일을 찾을수 없습니다."), - USER_NOT_FOUND(HttpStatus.NOT_FOUND, "e4042", "존재하지 않는 회원입니다."), + DATE_NOT_FOUND(HttpStatus.NOT_FOUND, "e4044", "데이트를 찾을 수 없습니다."), + DATE_TAG_NOT_FOUND(HttpStatus.NOT_FOUND, "e4045", "데이트 태그를 찾을 수 없습니다."), + DATE_PLACE_NOT_FOUND(HttpStatus.NOT_FOUND, "e4046", "데이트 장소를 찾을 수 없습니다."), /** * 405 Method Not Allowed diff --git a/dateroad-common/src/main/java/org/dateroad/common/ObjectMapperConfig.java b/dateroad-common/src/main/java/org/dateroad/common/ObjectMapperConfig.java index d01d1ef9..e6e59f9e 100644 --- a/dateroad-common/src/main/java/org/dateroad/common/ObjectMapperConfig.java +++ b/dateroad-common/src/main/java/org/dateroad/common/ObjectMapperConfig.java @@ -17,4 +17,4 @@ public ObjectMapper objectMapper() { return mapper; } -} +} \ No newline at end of file diff --git a/dateroad-common/src/main/java/org/dateroad/exception/BadRequestException.java b/dateroad-common/src/main/java/org/dateroad/exception/BadRequestException.java new file mode 100644 index 00000000..9be3090d --- /dev/null +++ b/dateroad-common/src/main/java/org/dateroad/exception/BadRequestException.java @@ -0,0 +1,13 @@ +package org.dateroad.exception; + +import org.dateroad.code.FailureCode; + +public class BadRequestException extends DateRoadException { + public BadRequestException() { + super(FailureCode.BAD_REQUEST); + } + + public BadRequestException(FailureCode failureCode) { + super(failureCode); + } +} diff --git a/dateroad-common/src/main/java/org/dateroad/exception/ForbiddenException.java b/dateroad-common/src/main/java/org/dateroad/exception/ForbiddenException.java new file mode 100644 index 00000000..f9fa3a58 --- /dev/null +++ b/dateroad-common/src/main/java/org/dateroad/exception/ForbiddenException.java @@ -0,0 +1,13 @@ +package org.dateroad.exception; + +import org.dateroad.code.FailureCode; + +public class ForbiddenException extends DateRoadException { + public ForbiddenException() { + super(FailureCode.FORBIDDEN); + } + + public ForbiddenException(FailureCode failureCode) { + super(failureCode); + } +} diff --git a/dateroad-domain/src/main/java/org/dateroad/date/repository/DatePlaceRepository.java b/dateroad-domain/src/main/java/org/dateroad/date/repository/DatePlaceRepository.java new file mode 100644 index 00000000..2af22e97 --- /dev/null +++ b/dateroad-domain/src/main/java/org/dateroad/date/repository/DatePlaceRepository.java @@ -0,0 +1,12 @@ +package org.dateroad.date.repository; + +import org.dateroad.date.domain.Date; +import org.dateroad.place.domain.DatePlace; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface DatePlaceRepository extends JpaRepository { + void deleteByDateId(Long dateId); + List findByDate(Date date); +} diff --git a/dateroad-domain/src/main/java/org/dateroad/date/repository/DateTagRepository.java b/dateroad-domain/src/main/java/org/dateroad/date/repository/DateTagRepository.java new file mode 100644 index 00000000..d94e1669 --- /dev/null +++ b/dateroad-domain/src/main/java/org/dateroad/date/repository/DateTagRepository.java @@ -0,0 +1,12 @@ +package org.dateroad.date.repository; + +import org.dateroad.date.domain.Date; +import org.dateroad.tag.domain.DateTag; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface DateTagRepository extends JpaRepository { + void deleteByDateId(Long dateId); + List findByDate(Date date); +} diff --git a/dateroad-domain/src/main/java/org/dateroad/dateAccess/repository/DateAccessRepository.java b/dateroad-domain/src/main/java/org/dateroad/dateAccess/repository/DataAccessRepository.java similarity index 88% rename from dateroad-domain/src/main/java/org/dateroad/dateAccess/repository/DateAccessRepository.java rename to dateroad-domain/src/main/java/org/dateroad/dateAccess/repository/DataAccessRepository.java index caacedbd..2edf845e 100644 --- a/dateroad-domain/src/main/java/org/dateroad/dateAccess/repository/DateAccessRepository.java +++ b/dateroad-domain/src/main/java/org/dateroad/dateAccess/repository/DataAccessRepository.java @@ -7,7 +7,7 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -public interface DateAccessRepository extends JpaRepository { +public interface DataAccessRepository extends JpaRepository { @Query("SELECT da.course FROM DateAccess da WHERE da.user.id = :userId") List findCoursesByUserId(@Param("userId") Long userId); } diff --git a/dateroad-domain/src/main/java/org/dateroad/place/domain/DatePlace.java b/dateroad-domain/src/main/java/org/dateroad/place/domain/DatePlace.java index edff585c..787f1bf2 100644 --- a/dateroad-domain/src/main/java/org/dateroad/place/domain/DatePlace.java +++ b/dateroad-domain/src/main/java/org/dateroad/place/domain/DatePlace.java @@ -23,7 +23,7 @@ public class DatePlace extends Place { @NotNull private Date date; - public static DatePlace create(final String name, float duration, final Date date, final int sequence) { + public static DatePlace create(final Date date, final String name, final float duration, final int sequence) { return DatePlace.builder() .name(name) .duration(duration) diff --git a/dateroad-domain/src/main/java/org/dateroad/place/repository/CoursePlaceRepository.java b/dateroad-domain/src/main/java/org/dateroad/place/repository/CoursePlaceRepository.java index 63816eb4..7037d6c2 100644 --- a/dateroad-domain/src/main/java/org/dateroad/place/repository/CoursePlaceRepository.java +++ b/dateroad-domain/src/main/java/org/dateroad/place/repository/CoursePlaceRepository.java @@ -1,6 +1,7 @@ package org.dateroad.place.repository; import java.util.List; +import org.dateroad.date.domain.Course; import org.dateroad.place.domain.CoursePlace; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; @@ -10,6 +11,5 @@ @Repository public interface CoursePlaceRepository extends JpaRepository { @Query("SELECT SUM(p.duration) FROM CoursePlace p WHERE p.course.id = :courseId") - Float findTotalDurationByCourseId(@Param("courseId") Long courseId); - + Integer findTotalDurationByCourseId(@Param("courseId") Long courseId); } diff --git a/dateroad-domain/src/main/java/org/dateroad/refreshtoken/repository/RefreshTokenRepository.java b/dateroad-domain/src/main/java/org/dateroad/refreshtoken/repository/RefreshTokenRepository.java index a8e5b970..be8a9c17 100644 --- a/dateroad-domain/src/main/java/org/dateroad/refreshtoken/repository/RefreshTokenRepository.java +++ b/dateroad-domain/src/main/java/org/dateroad/refreshtoken/repository/RefreshTokenRepository.java @@ -6,7 +6,7 @@ import org.springframework.stereotype.Repository; public interface RefreshTokenRepository extends JpaRepository { - public RefreshToken findUserIdByToken(@NotNull byte[] token); + RefreshToken findUserIdByToken(@NotNull byte[] token); void deleteByUserId(final Long userId); } diff --git a/dateroad-domain/src/main/java/org/dateroad/tag/domain/DateTag.java b/dateroad-domain/src/main/java/org/dateroad/tag/domain/DateTag.java index e17cd0cc..a789dedf 100644 --- a/dateroad-domain/src/main/java/org/dateroad/tag/domain/DateTag.java +++ b/dateroad-domain/src/main/java/org/dateroad/tag/domain/DateTag.java @@ -13,14 +13,12 @@ import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; import jakarta.validation.constraints.NotNull; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.NoArgsConstructor; +import lombok.*; import org.dateroad.common.BaseTimeEntity; import org.dateroad.date.domain.Date; @Entity +@Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor(access = AccessLevel.PRIVATE) @Builder(access = AccessLevel.PRIVATE) diff --git a/dateroad-domain/src/main/java/org/dateroad/user/domain/User.java b/dateroad-domain/src/main/java/org/dateroad/user/domain/User.java index e7c0b7e8..2a089682 100644 --- a/dateroad-domain/src/main/java/org/dateroad/user/domain/User.java +++ b/dateroad-domain/src/main/java/org/dateroad/user/domain/User.java @@ -1,13 +1,6 @@ package org.dateroad.user.domain; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.EnumType; -import jakarta.persistence.Enumerated; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.Table; +import jakarta.persistence.*; import jakarta.validation.constraints.NotNull; import lombok.AccessLevel; import lombok.AllArgsConstructor; @@ -15,6 +8,10 @@ import lombok.Getter; import lombok.NoArgsConstructor; import org.dateroad.common.BaseTimeEntity; +import org.dateroad.tag.domain.UserTag; + +import java.util.HashSet; +import java.util.Set; @NoArgsConstructor(access = AccessLevel.PROTECTED) @@ -55,6 +52,9 @@ public class User extends BaseTimeEntity { @NotNull private int totalPoint = 0; + @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) + private Set userTags; + public static User create(final String name, final String platformUserId, final Platform platForm, final String imageUrl) { return User.builder() .name(name) diff --git a/dateroad-domain/src/main/java/org/dateroad/user/repository/UserRepository.java b/dateroad-domain/src/main/java/org/dateroad/user/repository/UserRepository.java index 782cc0eb..03875f20 100644 --- a/dateroad-domain/src/main/java/org/dateroad/user/repository/UserRepository.java +++ b/dateroad-domain/src/main/java/org/dateroad/user/repository/UserRepository.java @@ -1,6 +1,5 @@ package org.dateroad.user.repository; -import java.util.List; import org.dateroad.user.domain.Platform; import org.dateroad.user.domain.User; import org.springframework.data.jpa.repository.JpaRepository; diff --git a/dateroad-external/build.gradle b/dateroad-external/build.gradle index ddbb2545..c440be1b 100644 --- a/dateroad-external/build.gradle +++ b/dateroad-external/build.gradle @@ -14,6 +14,10 @@ dependencies { implementation("software.amazon.awssdk:bom:2.21.0") implementation("software.amazon.awssdk:s3:2.21.0") + // apple secretKey + implementation 'org.bouncycastle:bcprov-jdk18on:1.75' + implementation 'org.bouncycastle:bcpkix-jdk18on:1.75' + implementation 'org.springframework.boot:spring-boot-starter-web' } diff --git a/dateroad-external/src/main/java/org/dateroad/feign/apple/AppleClientPublicKeyGenerator.java b/dateroad-external/src/main/java/org/dateroad/feign/apple/AppleClientPublicKeyGenerator.java index 97cb5565..f5ef8cf2 100644 --- a/dateroad-external/src/main/java/org/dateroad/feign/apple/AppleClientPublicKeyGenerator.java +++ b/dateroad-external/src/main/java/org/dateroad/feign/apple/AppleClientPublicKeyGenerator.java @@ -22,7 +22,6 @@ public PublicKey generateClientPublicKeyWithApplePublicKeys(final Map jwtHeader = new HashMap<>(); + jwtHeader.put("kid", appleProperties.getKid()); // kid + jwtHeader.put("alg", appleProperties.getAlg()); // alg + return Jwts.builder() + .setHeaderParams(jwtHeader) + .setIssuer(appleProperties.getIss()) // issuer + .setIssuedAt(new Date(System.currentTimeMillis())) // 발행 시간 + .setExpiration(expirationDate) // 만료 시간 + .setAudience(appleProperties.getAud()) // aud + .setSubject(appleProperties.getClientId()) // sub + .signWith(SignatureAlgorithm.ES256, getPrivateKey()) + .compact(); + } + + public PrivateKey getPrivateKey() throws IOException { + ClassPathResource resource = new ClassPathResource("static/AuthKey_39CUV6ST46.p8"); // .p8 key파일 위치 + String privateKey = new String(Files.readAllBytes(Paths.get(resource.getURI()))); + + Reader pemReader = new StringReader(privateKey); + PEMParser pemParser = new PEMParser(pemReader); + JcaPEMKeyConverter converter = new JcaPEMKeyConverter(); + PrivateKeyInfo object = (PrivateKeyInfo) pemParser.readObject(); + return converter.getPrivateKey(object); + } +} diff --git a/dateroad-external/src/main/java/org/dateroad/feign/apple/AppleFeignApi.java b/dateroad-external/src/main/java/org/dateroad/feign/apple/AppleFeignApi.java index ec279c9e..374ab369 100644 --- a/dateroad-external/src/main/java/org/dateroad/feign/apple/AppleFeignApi.java +++ b/dateroad-external/src/main/java/org/dateroad/feign/apple/AppleFeignApi.java @@ -1,11 +1,26 @@ package org.dateroad.feign.apple; +import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; @FeignClient(name = "${feign.apple.name}", url = "${feign.apple.url}") public interface AppleFeignApi { @GetMapping("/keys") ApplePublicKeys getApplePublicKeys(); + + @PostMapping(value = "/token", consumes = "application/x-www-form-urlencoded") + AppleTokenRes getAppleToken(@RequestParam("client_secret") String clientSecret, + @RequestParam("code") String authCode, + @RequestParam("grant_type") String grantType, + @RequestParam("client_id") String clientId); + + @PostMapping(value = "/revoke", consumes = "application/x-www-form-urlencoded") + void revokeUser(@RequestParam("client_id") String clientId, + @RequestParam("client_secret") String clientSecret, + @RequestParam("token") String accessToken, + @RequestParam("token_type_hint") String tokenTypeHint); } diff --git a/dateroad-external/src/main/java/org/dateroad/feign/apple/ApplePlatformUserIdProvider.java b/dateroad-external/src/main/java/org/dateroad/feign/apple/AppleFeignProvider.java similarity index 55% rename from dateroad-external/src/main/java/org/dateroad/feign/apple/ApplePlatformUserIdProvider.java rename to dateroad-external/src/main/java/org/dateroad/feign/apple/AppleFeignProvider.java index 872b13c8..3e772f2e 100644 --- a/dateroad-external/src/main/java/org/dateroad/feign/apple/ApplePlatformUserIdProvider.java +++ b/dateroad-external/src/main/java/org/dateroad/feign/apple/AppleFeignProvider.java @@ -2,20 +2,25 @@ import io.jsonwebtoken.Claims; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.dateroad.code.FailureCode; import org.dateroad.exception.UnauthorizedException; import org.springframework.stereotype.Component; +import java.io.IOException; import java.security.PublicKey; import java.util.Map; @RequiredArgsConstructor @Component -public class ApplePlatformUserIdProvider { +@Slf4j +public class AppleFeignProvider { private final AppleIdentityJWTParser appleIdentityJWTParser; private final AppleFeignApi appleFeignApi; private final AppleClientPublicKeyGenerator appleClientPublicKeyGenerator; private final AppleIdentityJWTValidator appleIdentityJWTValidator; + private final AppleClientSecretGenerator appleClientSecretGenerator; + private final AppleProperties appleProperties; public String getApplePlatformUserId(final String identityToken) { Map tokenHeaders = appleIdentityJWTParser.parseHeader(identityToken); @@ -26,9 +31,32 @@ public String getApplePlatformUserId(final String identityToken) { return claims.getSubject(); } + public void revokeUser(final String authCode) { + try { + String secretKey = appleClientSecretGenerator.createClientSecret(); + String appleAccessToken = getAppleAccess(secretKey, authCode); + appleFeignApi.revokeUser(secretKey, appleAccessToken, appleProperties.getClientId(), "access_token"); + } catch (IOException e) { + System.out.println(e); + throw new UnauthorizedException(FailureCode.INVALID_APPLE_TOKEN_ACCESS); + } + } + + private String getAppleAccess(final String secretKey, final String authCode) { + AppleTokenRes appleTokenRes = appleFeignApi + .getAppleToken( + secretKey, + authCode, + appleProperties.getType(), + appleProperties.getClientId() + ); + return appleTokenRes.access_token(); + } + private void validateClaims(final Claims claims) { if (!appleIdentityJWTValidator.isValidAppleIdentityToken(claims)) { throw new UnauthorizedException(FailureCode.INVALID_APPLE_IDENTITY_TOKEN_CLAIMS); } } + } diff --git a/dateroad-external/src/main/java/org/dateroad/feign/apple/AppleIdentityJWTValidator.java b/dateroad-external/src/main/java/org/dateroad/feign/apple/AppleIdentityJWTValidator.java index 756c07d9..d719510a 100644 --- a/dateroad-external/src/main/java/org/dateroad/feign/apple/AppleIdentityJWTValidator.java +++ b/dateroad-external/src/main/java/org/dateroad/feign/apple/AppleIdentityJWTValidator.java @@ -1,18 +1,17 @@ package org.dateroad.feign.apple; import io.jsonwebtoken.Claims; +import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Component +@RequiredArgsConstructor public class AppleIdentityJWTValidator { - @Value("${feign.apple.iss}") - private String iss; - @Value("${feign.apple.client-id}") - private String clientId; + private final AppleProperties appleProperties; public boolean isValidAppleIdentityToken(final Claims claims) { - return claims.getIssuer().contains(iss) - && claims.getAudience().equals(clientId); + return claims.getIssuer().contains(appleProperties.getAud()) + && claims.getAudience().equals(appleProperties.getClientId()); } } diff --git a/dateroad-external/src/main/java/org/dateroad/feign/apple/AppleProperties.java b/dateroad-external/src/main/java/org/dateroad/feign/apple/AppleProperties.java new file mode 100644 index 00000000..fcc77df4 --- /dev/null +++ b/dateroad-external/src/main/java/org/dateroad/feign/apple/AppleProperties.java @@ -0,0 +1,19 @@ +package org.dateroad.feign.apple; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Getter +@Setter +@ConfigurationProperties(prefix = "feign.apple") +@Component +public class AppleProperties { + private String iss; + private String aud; + private String clientId; + private String kid; + private String alg; + private String type; +} diff --git a/dateroad-external/src/main/java/org/dateroad/feign/apple/AppleTokenRes.java b/dateroad-external/src/main/java/org/dateroad/feign/apple/AppleTokenRes.java new file mode 100644 index 00000000..0bf0ca54 --- /dev/null +++ b/dateroad-external/src/main/java/org/dateroad/feign/apple/AppleTokenRes.java @@ -0,0 +1,10 @@ +package org.dateroad.feign.apple; + +public record AppleTokenRes( + String access_token, + String token_type, + Integer expires_in, + String refresh_token, + String id_token +) { +} diff --git a/dateroad-external/src/main/java/org/dateroad/feign/kakao/KakaoErrorRes.java b/dateroad-external/src/main/java/org/dateroad/feign/kakao/KakaoErrorRes.java deleted file mode 100644 index ba62619c..00000000 --- a/dateroad-external/src/main/java/org/dateroad/feign/kakao/KakaoErrorRes.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.dateroad.feign.kakao; - -import lombok.*; - -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@AllArgsConstructor(access = AccessLevel.PRIVATE) -public class KakaoErrorRes { - private String error; - private int code; -} diff --git a/dateroad-external/src/main/java/org/dateroad/feign/kakao/KakaoFeignApi.java b/dateroad-external/src/main/java/org/dateroad/feign/kakao/KakaoFeignApi.java index e2db7ba1..814c1eb1 100644 --- a/dateroad-external/src/main/java/org/dateroad/feign/kakao/KakaoFeignApi.java +++ b/dateroad-external/src/main/java/org/dateroad/feign/kakao/KakaoFeignApi.java @@ -1,11 +1,17 @@ package org.dateroad.feign.kakao; +import org.dateroad.feign.kakao.dto.response.KaKaoUnlinkRes; +import org.dateroad.feign.kakao.dto.response.KakaoAccessTokenInfoRes; import org.springframework.cloud.openfeign.FeignClient; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.*; @FeignClient(name = "${feign.kakao.name}", url = "${feign.kakao.url}") public interface KakaoFeignApi { - @GetMapping + @GetMapping("/access_token_info") KakaoAccessTokenInfoRes getKakaoPlatformUserId(@RequestHeader("Authorization") String accessTokenWithTokenType); + + @PostMapping("/unlink") + KaKaoUnlinkRes unlink(@RequestHeader("Authorization") String appAdminKey, + @RequestParam("target_id_type") String targetIdType, + @RequestParam("target_id") Long tagerId); } diff --git a/dateroad-external/src/main/java/org/dateroad/feign/kakao/KakaoFeignProvider.java b/dateroad-external/src/main/java/org/dateroad/feign/kakao/KakaoFeignProvider.java new file mode 100644 index 00000000..375d7c48 --- /dev/null +++ b/dateroad-external/src/main/java/org/dateroad/feign/kakao/KakaoFeignProvider.java @@ -0,0 +1,80 @@ +package org.dateroad.feign.kakao; + +import com.fasterxml.jackson.databind.ObjectMapper; +import feign.FeignException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.dateroad.code.FailureCode; +import org.dateroad.exception.UnauthorizedException; +import org.dateroad.feign.kakao.dto.response.KaKaoErrorRes; +import org.dateroad.feign.kakao.dto.response.KakaoAccessTokenInfoRes; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +@Slf4j +@RequiredArgsConstructor +@Component +public class KakaoFeignProvider { + private final KakaoProperties kakaoProperties; + private final KakaoFeignApi kakaoFeignApi; + private final ObjectMapper objectMapper; + + private final String TARGETIDTYPE = "user_id"; + + //AuthService에서 호출 : 카카오에서 주는 userId 받아오기 + public String getKakaoPlatformUserId(final String kakaoAccessToken) { + String kakaoRequestHeader = getKakaoRequestHeader(KakaoRequestType.ACCESS_TOKEN_INFO, kakaoAccessToken); + KakaoAccessTokenInfoRes kakaoAccessTokenInfoRes = getKakaoAccessTokenInfoFeign(kakaoRequestHeader); + return String.valueOf(kakaoAccessTokenInfoRes.id()); + } + + //AuthService에서 호출 : 회원탈퇴 할 때, 카카오톡과 연결 끊기 + public void unLinkWithKakao(final String kakaoPlatformUserId) { + String kakaoRequestHeader = getKakaoRequestHeader(KakaoRequestType.UN_LINK, kakaoPlatformUserId); + try { + kakaoFeignApi.unlink(kakaoRequestHeader, TARGETIDTYPE, Long.valueOf(kakaoPlatformUserId)); + } catch (FeignException e) { + throw new UnauthorizedException(FailureCode.UN_LINK_WITH_KAKAO_UNAUTHORIZED); + } + } + + private KakaoAccessTokenInfoRes getKakaoAccessTokenInfoFeign(final String accessTokenWithTokenType) { + try { + return kakaoFeignApi.getKakaoPlatformUserId(accessTokenWithTokenType); + } catch (FeignException e) { + log.error("Kakao feign exception : ", e); + + //kakaoResponseDTO로 변경 + KaKaoErrorRes errorResponse = convertToKakaoErrorResponse(e.contentUTF8()); + + //카카오에서 주는 에러 코드가 -1이면 카카오 내부 에러, 나머지는 카카오 액세스 토큰 에러 + if (errorResponse.code() == -1) { + log.error("Kakao feign exception : ", e); + throw new UnauthorizedException(FailureCode.KAKAO_INTERNER_ERROR); + } else { + log.error("feign exception : ", e); + throw new UnauthorizedException(FailureCode.INVALID_KAKAO_TOKEN); + } + } + } + + private String getKakaoRequestHeader(final KakaoRequestType kakaoRequestType, final String accessToken) { + if (kakaoRequestType == KakaoRequestType.ACCESS_TOKEN_INFO) { + return KakaoHeaderType.BEARER.getTokenType() + accessToken; + } else if (kakaoRequestType == KakaoRequestType.UN_LINK) { + return KakaoHeaderType.KAKAOAK.getTokenType() + kakaoProperties.getAdminKey(); + } else { + throw new UnauthorizedException(FailureCode.INVALID_KAKAO_ACCESS); + } + } + + private KaKaoErrorRes convertToKakaoErrorResponse(String responseBody) { + try { + return objectMapper.readValue(responseBody, KaKaoErrorRes.class); + } catch (IOException e) { + log.error("Convert To KakaoErrorResponse Error : ", e); + throw new UnauthorizedException(FailureCode.INVALID_KAKAO_TOKEN); + } + } +} diff --git a/dateroad-external/src/main/java/org/dateroad/feign/kakao/KakaoHeaderType.java b/dateroad-external/src/main/java/org/dateroad/feign/kakao/KakaoHeaderType.java new file mode 100644 index 00000000..be7869a1 --- /dev/null +++ b/dateroad-external/src/main/java/org/dateroad/feign/kakao/KakaoHeaderType.java @@ -0,0 +1,14 @@ +package org.dateroad.feign.kakao; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public enum KakaoHeaderType { + BEARER("Bearer "), + KAKAOAK("KakaoAK "), + ; + + private final String tokenType; +} diff --git a/dateroad-external/src/main/java/org/dateroad/feign/kakao/KakaoPlatformUserIdProvider.java b/dateroad-external/src/main/java/org/dateroad/feign/kakao/KakaoPlatformUserIdProvider.java deleted file mode 100644 index eb91bf0c..00000000 --- a/dateroad-external/src/main/java/org/dateroad/feign/kakao/KakaoPlatformUserIdProvider.java +++ /dev/null @@ -1,60 +0,0 @@ -package org.dateroad.feign.kakao; - -import com.fasterxml.jackson.databind.ObjectMapper; -import feign.FeignException; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.dateroad.code.FailureCode; -import org.dateroad.exception.UnauthorizedException; -import org.springframework.stereotype.Component; - -import java.io.IOException; - -@Slf4j -@RequiredArgsConstructor -@Component -public class KakaoPlatformUserIdProvider { - private final KakaoFeignApi kakaoFeignApi; - private final ObjectMapper objectMapper; - private static final String TOKEN_TYPE = "Bearer "; - - //AuthService에서 호출 : 카카오에서 주는 userId 받아오기 - public String getKakaoPlatformUserId(final String accessToken) { - String kakaoAccessTokenWithTokenType = getAccessTokenWithTokenType(accessToken); - KakaoAccessTokenInfoRes kakaoAccessTokenInfoRes = getKakaoAccessTokenInfo(kakaoAccessTokenWithTokenType); - return String.valueOf(kakaoAccessTokenInfoRes.id()); - } - - private KakaoAccessTokenInfoRes getKakaoAccessTokenInfo(final String token) { - try { - return kakaoFeignApi.getKakaoPlatformUserId(token); - } catch (FeignException e) { - log.error("Kakao feign exception : ", e); - - //kakaoResponseDTO로 변경 - KakaoErrorRes errorResponse = convertToKakaoErrorResponse(e.contentUTF8()); - - //카카오에서 주는 에러 코드가 -1이면 카카오 내부 에러, 나머지는 카카오 액세스 토큰 에러 - if (errorResponse.getCode() == -1) { - log.error("Kakao feign exception : ", e); - throw new UnauthorizedException(FailureCode.KAKAO_INTERNER_ERROR); - } else { - log.error("feign exception : ", e); - throw new UnauthorizedException(FailureCode.INVALID_KAKAO_TOKEN); - } - } - } - - private String getAccessTokenWithTokenType(String accessToken) { - return TOKEN_TYPE + accessToken; - } - - private KakaoErrorRes convertToKakaoErrorResponse(String responseBody) { - try { - return objectMapper.readValue(responseBody, KakaoErrorRes.class); - } catch (IOException e) { - log.error("Convert To KakaoErrorResponse Error : ", e); - throw new UnauthorizedException(FailureCode.INVALID_KAKAO_TOKEN); - } - } -} diff --git a/dateroad-external/src/main/java/org/dateroad/feign/kakao/KakaoProperties.java b/dateroad-external/src/main/java/org/dateroad/feign/kakao/KakaoProperties.java new file mode 100644 index 00000000..7d401922 --- /dev/null +++ b/dateroad-external/src/main/java/org/dateroad/feign/kakao/KakaoProperties.java @@ -0,0 +1,15 @@ +package org.dateroad.feign.kakao; + + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Getter +@Setter +@ConfigurationProperties("feign.kakao") +@Component +public class KakaoProperties { + private String adminKey; +} diff --git a/dateroad-external/src/main/java/org/dateroad/feign/kakao/KakaoRequestType.java b/dateroad-external/src/main/java/org/dateroad/feign/kakao/KakaoRequestType.java new file mode 100644 index 00000000..a2b62039 --- /dev/null +++ b/dateroad-external/src/main/java/org/dateroad/feign/kakao/KakaoRequestType.java @@ -0,0 +1,6 @@ +package org.dateroad.feign.kakao; + +public enum KakaoRequestType { + ACCESS_TOKEN_INFO, + UN_LINK +} diff --git a/dateroad-external/src/main/java/org/dateroad/feign/kakao/dto/response/KaKaoErrorRes.java b/dateroad-external/src/main/java/org/dateroad/feign/kakao/dto/response/KaKaoErrorRes.java new file mode 100644 index 00000000..5852a410 --- /dev/null +++ b/dateroad-external/src/main/java/org/dateroad/feign/kakao/dto/response/KaKaoErrorRes.java @@ -0,0 +1,9 @@ +package org.dateroad.feign.kakao.dto.response; + +import lombok.Getter; + +public record KaKaoErrorRes( + String error, + int code +) { +} diff --git a/dateroad-external/src/main/java/org/dateroad/feign/kakao/dto/response/KaKaoUnlinkRes.java b/dateroad-external/src/main/java/org/dateroad/feign/kakao/dto/response/KaKaoUnlinkRes.java new file mode 100644 index 00000000..ba1d490e --- /dev/null +++ b/dateroad-external/src/main/java/org/dateroad/feign/kakao/dto/response/KaKaoUnlinkRes.java @@ -0,0 +1,6 @@ +package org.dateroad.feign.kakao.dto.response; + +public record KaKaoUnlinkRes( + Long id +) { +} diff --git a/dateroad-external/src/main/java/org/dateroad/feign/kakao/KakaoAccessTokenInfoRes.java b/dateroad-external/src/main/java/org/dateroad/feign/kakao/dto/response/KakaoAccessTokenInfoRes.java similarity index 78% rename from dateroad-external/src/main/java/org/dateroad/feign/kakao/KakaoAccessTokenInfoRes.java rename to dateroad-external/src/main/java/org/dateroad/feign/kakao/dto/response/KakaoAccessTokenInfoRes.java index d5f5194e..c7906b54 100644 --- a/dateroad-external/src/main/java/org/dateroad/feign/kakao/KakaoAccessTokenInfoRes.java +++ b/dateroad-external/src/main/java/org/dateroad/feign/kakao/dto/response/KakaoAccessTokenInfoRes.java @@ -1,4 +1,4 @@ -package org.dateroad.feign.kakao; +package org.dateroad.feign.kakao.dto.response; public record KakaoAccessTokenInfoRes( Long id, From 7ec15134a04419edf8476ae93785da1fa50a62b1 Mon Sep 17 00:00:00 2001 From: rlarlgnszx Date: Thu, 11 Jul 2024 18:52:05 +0900 Subject: [PATCH 2/3] =?UTF-8?q?[fix]=20repository=20=EC=96=B4=EB=85=B8?= =?UTF-8?q?=ED=85=8C=EC=9D=B4=EC=85=98=20=EC=B6=94=EA=B0=80=20-=20#63?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dateroad/dateAccess/repository/DataAccessRepository.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dateroad-domain/src/main/java/org/dateroad/dateAccess/repository/DataAccessRepository.java b/dateroad-domain/src/main/java/org/dateroad/dateAccess/repository/DataAccessRepository.java index 2edf845e..4db6ca25 100644 --- a/dateroad-domain/src/main/java/org/dateroad/dateAccess/repository/DataAccessRepository.java +++ b/dateroad-domain/src/main/java/org/dateroad/dateAccess/repository/DataAccessRepository.java @@ -6,7 +6,9 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; +@Repository public interface DataAccessRepository extends JpaRepository { @Query("SELECT da.course FROM DateAccess da WHERE da.user.id = :userId") List findCoursesByUserId(@Param("userId") Long userId); From b045f6f7720a5213ddfa85c6dd8c261aa90afe47 Mon Sep 17 00:00:00 2001 From: rlarlgnszx Date: Thu, 11 Jul 2024 18:58:12 +0900 Subject: [PATCH 3/3] =?UTF-8?q?[fix]=20import=20error=20=ED=95=B4=EA=B2=B0?= =?UTF-8?q?=20-=20#63?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{DataAccessRepository.java => DateAccessRepository.java} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename dateroad-domain/src/main/java/org/dateroad/dateAccess/repository/{DataAccessRepository.java => DateAccessRepository.java} (89%) diff --git a/dateroad-domain/src/main/java/org/dateroad/dateAccess/repository/DataAccessRepository.java b/dateroad-domain/src/main/java/org/dateroad/dateAccess/repository/DateAccessRepository.java similarity index 89% rename from dateroad-domain/src/main/java/org/dateroad/dateAccess/repository/DataAccessRepository.java rename to dateroad-domain/src/main/java/org/dateroad/dateAccess/repository/DateAccessRepository.java index 4db6ca25..13030509 100644 --- a/dateroad-domain/src/main/java/org/dateroad/dateAccess/repository/DataAccessRepository.java +++ b/dateroad-domain/src/main/java/org/dateroad/dateAccess/repository/DateAccessRepository.java @@ -9,7 +9,7 @@ import org.springframework.stereotype.Repository; @Repository -public interface DataAccessRepository extends JpaRepository { +public interface DateAccessRepository extends JpaRepository { @Query("SELECT da.course FROM DateAccess da WHERE da.user.id = :userId") List findCoursesByUserId(@Param("userId") Long userId); }