From 6f5e976a73f2e97927b0ed6b6bd3dd15949ffce9 Mon Sep 17 00:00:00 2001 From: Awhn <69659322+Awhn@users.noreply.github.com> Date: Tue, 29 Oct 2024 13:32:48 +0900 Subject: [PATCH 1/5] =?UTF-8?q?fix:=20=EC=95=84=EB=B0=94=ED=83=80=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=20=EC=8B=9C=20=EC=98=A4=EB=A5=98=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0=20(#44)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 유저 기록 기능 구현 * test: 유저 기록 기능 테스트 * fix: activity 관련 의존성 수정 * fix: 요구사항 변경에 따른 선택적 무한 스크롤 * fix: 아바타 역직렬화 과정에서의 오류 수정 --- .../activity/dto/ActivitySaveRequest.java | 49 ++++++++++--------- .../com/gamsa/avatar/constant/AgeRange.java | 17 +++++++ .../gamsa/avatar/constant/Experienced.java | 17 +++++++ .../avatar/controller/AvatarController.java | 6 +-- .../gamsa/avatar/dto/AvatarSaveRequest.java | 6 +-- .../gamsa/avatar/service/AvatarService.java | 1 - .../history/controller/HistoryController.java | 15 +++++- 7 files changed, 79 insertions(+), 32 deletions(-) diff --git a/src/main/java/com/gamsa/activity/dto/ActivitySaveRequest.java b/src/main/java/com/gamsa/activity/dto/ActivitySaveRequest.java index cc0bc8d..8f4bc59 100644 --- a/src/main/java/com/gamsa/activity/dto/ActivitySaveRequest.java +++ b/src/main/java/com/gamsa/activity/dto/ActivitySaveRequest.java @@ -4,11 +4,12 @@ import com.gamsa.activity.domain.Activity; import com.gamsa.activity.domain.District; import com.gamsa.activity.domain.Institute; -import java.time.LocalDateTime; import lombok.Builder; import lombok.Getter; import lombok.RequiredArgsConstructor; +import java.time.LocalDateTime; + @Getter @Builder @RequiredArgsConstructor @@ -38,27 +39,27 @@ public class ActivitySaveRequest { public Activity toModel(Institute institute, District sidoGungu) { return Activity.builder() - .actId(actId) - .actTitle(actTitle) - .actLocation(actLocation) - .description(description) - .noticeStartDate(noticeStartDate) - .noticeEndDate(noticeEndDate) - .actStartDate(actStartDate) - .actEndDate(actEndDate) - .actStartTime(actStartTime) - .actEndTime(actEndTime) - .recruitTotalNum(recruitTotalNum) - .adultPossible(adultPossible) - .teenPossible(teenPossible) - .groupPossible(groupPossible) - .actWeek(actWeek) - .actManager(actManager) - .actPhone(actPhone) - .url(url) - .category(category) - .institute(institute) - .sidoGungu(sidoGungu) - .build(); + .actId(actId) + .actTitle(actTitle) + .actLocation(actLocation) + .description(description) + .noticeStartDate(noticeStartDate) + .noticeEndDate(noticeEndDate) + .actStartDate(actStartDate) + .actEndDate(actEndDate) + .actStartTime(actStartTime) + .actEndTime(actEndTime) + .recruitTotalNum(recruitTotalNum) + .adultPossible(adultPossible) + .teenPossible(teenPossible) + .groupPossible(groupPossible) + .actWeek(actWeek) + .actManager(actManager) + .actPhone(actPhone) + .url(url) + .category(category) + .institute(institute) + .sidoGungu(sidoGungu) + .build(); } -} +} \ No newline at end of file diff --git a/src/main/java/com/gamsa/avatar/constant/AgeRange.java b/src/main/java/com/gamsa/avatar/constant/AgeRange.java index 9da6952..fd857c3 100644 --- a/src/main/java/com/gamsa/avatar/constant/AgeRange.java +++ b/src/main/java/com/gamsa/avatar/constant/AgeRange.java @@ -1,5 +1,7 @@ package com.gamsa.avatar.constant; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -11,4 +13,19 @@ public enum AgeRange { ADULT("성인"); private final String name; + + @JsonCreator + public static AgeRange fromValue(String value) { + for (AgeRange ageRange : AgeRange.values()) { + if (ageRange.name.equals(value)) { + return ageRange; + } + } + throw new IllegalArgumentException("Unknown value: " + value); + } + + @JsonValue + public String toValue() { + return this.name; + } } diff --git a/src/main/java/com/gamsa/avatar/constant/Experienced.java b/src/main/java/com/gamsa/avatar/constant/Experienced.java index c54cddc..392c006 100644 --- a/src/main/java/com/gamsa/avatar/constant/Experienced.java +++ b/src/main/java/com/gamsa/avatar/constant/Experienced.java @@ -1,5 +1,7 @@ package com.gamsa.avatar.constant; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -11,4 +13,19 @@ public enum Experienced { EXPERT("상급자"); private final String name; + + @JsonCreator + public static Experienced fromValue(String value) { + for (Experienced level : Experienced.values()) { + if (level.name.equals(value)) { + return level; + } + } + throw new IllegalArgumentException("Unknown value: " + value); + } + + @JsonValue + public String toValue() { + return this.name; + } } diff --git a/src/main/java/com/gamsa/avatar/controller/AvatarController.java b/src/main/java/com/gamsa/avatar/controller/AvatarController.java index 9008cbd..ee6c805 100644 --- a/src/main/java/com/gamsa/avatar/controller/AvatarController.java +++ b/src/main/java/com/gamsa/avatar/controller/AvatarController.java @@ -23,18 +23,18 @@ public ResponseEntity saveAvatar(@RequestBody AvatarSaveRequest saveRequ } @GetMapping("{avatar-id}") - public AvatarFindResponse getAvatar(@PathVariable Long avatarId) { + public AvatarFindResponse getAvatar(@PathVariable("avatar-id") Long avatarId) { return avatarService.findById(avatarId); } @PutMapping("{avatar-id}") - public ResponseEntity updateAvatar(@PathVariable Long avatarId, @RequestBody AvatarSaveRequest saveRequest) { + public ResponseEntity updateAvatar(@PathVariable("avatar-id") Long avatarId, @RequestBody AvatarSaveRequest saveRequest) { avatarService.update(avatarId, saveRequest); return new ResponseEntity<>(HttpStatus.OK); } @DeleteMapping("{avatar-Id}") - public ResponseEntity deleteAvatar(@PathVariable Long avatarId) { + public ResponseEntity deleteAvatar(@PathVariable("avatar-id") Long avatarId) { avatarService.delete(avatarId); return new ResponseEntity<>(HttpStatus.NO_CONTENT); } diff --git a/src/main/java/com/gamsa/avatar/dto/AvatarSaveRequest.java b/src/main/java/com/gamsa/avatar/dto/AvatarSaveRequest.java index 3efd859..28af1b5 100644 --- a/src/main/java/com/gamsa/avatar/dto/AvatarSaveRequest.java +++ b/src/main/java/com/gamsa/avatar/dto/AvatarSaveRequest.java @@ -7,16 +7,16 @@ import jakarta.validation.constraints.NotNull; import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.RequiredArgsConstructor; -import java.time.LocalDateTime; - @Getter @Builder @RequiredArgsConstructor +@NoArgsConstructor(force = true) public class AvatarSaveRequest { @NotNull(message = "닉네임은 비어있으면 안됩니다.") - @Max(value=10, message = "닉네임은 최대 10자입니다.") + @Max(value = 10, message = "닉네임은 최대 10자입니다.") private final String nickname; @NotNull(message = "연령대를 선택해야 합니다.") private final AgeRange ageRange; diff --git a/src/main/java/com/gamsa/avatar/service/AvatarService.java b/src/main/java/com/gamsa/avatar/service/AvatarService.java index b55c41a..3eb0193 100644 --- a/src/main/java/com/gamsa/avatar/service/AvatarService.java +++ b/src/main/java/com/gamsa/avatar/service/AvatarService.java @@ -21,7 +21,6 @@ public AvatarFindResponse findById(Long id) { public void save(AvatarSaveRequest saveRequest) { Avatar avatar = saveRequest.toModel(); - avatarRepository.findById(avatar.getAvatarId()).orElseThrow(NoSuchElementException::new); avatarRepository.save(avatar); } diff --git a/src/main/java/com/gamsa/history/controller/HistoryController.java b/src/main/java/com/gamsa/history/controller/HistoryController.java index 1dac62f..3e57205 100644 --- a/src/main/java/com/gamsa/history/controller/HistoryController.java +++ b/src/main/java/com/gamsa/history/controller/HistoryController.java @@ -4,8 +4,10 @@ import com.gamsa.history.dto.HistorySaveRequest; import com.gamsa.history.service.HistoryService; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; +import org.springframework.data.domain.Sort; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -16,6 +18,8 @@ public class HistoryController { private HistoryService historyService; + private static final int MAX_SIZE = Integer.MAX_VALUE; + @PostMapping public ResponseEntity addHistory(@RequestBody HistorySaveRequest saveRequest) { historyService.save(saveRequest); @@ -23,7 +27,16 @@ public ResponseEntity addHistory(@RequestBody HistorySaveRequest saveReq } @GetMapping("{avatar-id}") - public Slice findSliceByUserId(@PathVariable("avatar-id") long avatarId, Pageable pageable) { + public Slice findSliceByUserId(@PathVariable("avatar-id") long avatarId, + @RequestParam(value = "page", required = false) Integer page, + @RequestParam(value = "size", required = false) Integer size) { + Pageable pageable; + + if (page == null || size == null) { + pageable = PageRequest.of(0, MAX_SIZE, Sort.unsorted()); + } else { + pageable = PageRequest.of(page, size, Sort.unsorted()); + } return historyService.findSliceByAvatarId(avatarId, pageable); } From e9b3e9aba224af59f8e07ab3c6f79c1708563091 Mon Sep 17 00:00:00 2001 From: Awhn <69659322+Awhn@users.noreply.github.com> Date: Tue, 29 Oct 2024 13:34:44 +0900 Subject: [PATCH 2/5] =?UTF-8?q?=20fix:=20=EC=9A=94=EA=B5=AC=EC=82=AC?= =?UTF-8?q?=ED=95=AD=20=EB=B3=80=EA=B2=BD=EC=97=90=20=EB=94=B0=EB=A5=B8=20?= =?UTF-8?q?=EC=84=A0=ED=83=9D=EC=A0=81=20=EB=AC=B4=ED=95=9C=20=EC=8A=A4?= =?UTF-8?q?=ED=81=AC=EB=A1=A4=20=20(#43)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 유저 기록 기능 구현 * test: 유저 기록 기능 테스트 * fix: activity 관련 의존성 수정 * fix: 요구사항 변경에 따른 선택적 무한 스크롤 --- .../java/com/gamsa/history/controller/HistoryController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/gamsa/history/controller/HistoryController.java b/src/main/java/com/gamsa/history/controller/HistoryController.java index 3e57205..3d3ace9 100644 --- a/src/main/java/com/gamsa/history/controller/HistoryController.java +++ b/src/main/java/com/gamsa/history/controller/HistoryController.java @@ -19,7 +19,7 @@ public class HistoryController { private HistoryService historyService; private static final int MAX_SIZE = Integer.MAX_VALUE; - + @PostMapping public ResponseEntity addHistory(@RequestBody HistorySaveRequest saveRequest) { historyService.save(saveRequest); From 6a4687fe8e152e4d178af46e17a5b6f9a2a941fe Mon Sep 17 00:00:00 2001 From: 5win <94297900+5win@users.noreply.github.com> Date: Tue, 29 Oct 2024 23:46:45 +0900 Subject: [PATCH 3/5] =?UTF-8?q?infra:=20=EB=B0=B0=ED=8F=AC=20CI=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=B4=ED=94=84=EB=9D=BC=EC=9D=B8=20(#48)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [#37] infra: 배포 CI 파이프라인 * [#37] fix: 브랜치 및 오류 수정 --- .github/workflows/prod-ci.yml | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 .github/workflows/prod-ci.yml diff --git a/.github/workflows/prod-ci.yml b/.github/workflows/prod-ci.yml new file mode 100644 index 0000000..5d4dfc3 --- /dev/null +++ b/.github/workflows/prod-ci.yml @@ -0,0 +1,34 @@ +name: Prod-CI + +on: + pull_request: + branches: [ "master" ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0 + + - name: Build with Gradle Wrapper + run: ./gradlew build + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ap-northeast-2 + + run: | + aws s3 sync build s3://gamja-bongsa \ No newline at end of file From f0045bf98f6cefb4c2da72d1a917826851015eb6 Mon Sep 17 00:00:00 2001 From: 5win <94297900+5win@users.noreply.github.com> Date: Wed, 30 Oct 2024 19:25:42 +0900 Subject: [PATCH 4/5] =?UTF-8?q?feat:=20=EC=B9=B4=EC=B9=B4=EC=98=A4=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EA=B5=AC=ED=98=84=20=EB=B0=8F=20?= =?UTF-8?q?=EC=95=84=EB=B0=94=ED=83=80,=20=ED=9E=88=EC=8A=A4=ED=86=A0?= =?UTF-8?q?=EB=A6=AC=EC=99=80=20=EC=97=B0=EB=8F=99=20(#45)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [#19] feat: 유저 카카오 회원가입/로그인 구현 - JWT + Interceptor를 사용하여 구현 * [#19] feat: 유저, 아바타 연동 - userId로 아바타 생성 기능 추가. - user와 avatar 1:1 연관관계 매핑. * [#19] fix: 테스트용 로깅 제거 * [#19] feat: 로그인 시, 아바타 유무 응답 반환 - 헤더의 token : JWT - 바디의 avatarExists: true/false * [#19] refact: 아바타 URI 변경 - /api/v1/avatar -> /api/v1/avatars * [#19] refact: kakao-token 바디에서 헤더로 변경 - @RequesetBody -> @RequestHeader 로 수정 * [#19] refact: 유저 도메인과 영속성 엔티티 분리 - User 클래스 생성 * [#19] test: 유저 테스트 추가 및 다른 테스트코드 수정 - 유저 단위테스트 추가 - 다른 테스트 충돌 부분 해결 * [#19] fix: history URI 수정 및 불필요한 test 삭제 - /api/v1/history -> /api/v1/histories - ApplicationTest.java 삭제 * [#19] feat: User, Avatar, History 연동 및 수정 - INTERMEDIATE 오타 수정 - 인터셉터 인증 경로에 /api/v1/histories 추가 - NoSuchElement, IllegalArgument 예외 핸들러 추가 - History 관련 QueryDsl 수정 - JWT 파싱 후, 아바타 조회한 뒤 해당 아바타ID로 history 조회하도록 변경 * [#19] fix: History 생성 API의 avatarId 제거 - JWT 인증 토큰을 통해 userId를 받으므로 제거함 * [#19] refact: JWT userId 추출 로직 공통화 - common.utils.ExtractUserIdFromJwt.java * [#19] feat: 아바타 존재시 아바타 정보 반환 - body에 아바타 객체 정보 담아서 반환. - 없으면 null * [#19] test: 테스트 코드 수정 --- build.gradle | 6 +- src/main/java/com/gamsa/Application.java | 2 + .../java/com/gamsa/auth/SecurityConfig.java | 27 ---- .../gamsa/avatar/constant/Experienced.java | 2 +- .../avatar/controller/AvatarController.java | 42 ++++-- .../java/com/gamsa/avatar/domain/Avatar.java | 2 + .../gamsa/avatar/dto/AvatarFindResponse.java | 18 ++- .../gamsa/avatar/dto/AvatarSaveRequest.java | 16 ++- .../gamsa/avatar/entity/AvatarJpaEntity.java | 55 +++++--- .../repository/AvatarJpaRepository.java | 8 +- .../avatar/repository/AvatarRepository.java | 5 +- .../repository/AvatarRepositoryImpl.java | 15 ++- .../gamsa/avatar/service/AvatarService.java | 41 ++++-- .../com/gamsa/common/config/WebConfig.java | 27 ++++ .../exception/GlobalExceptionHandler.java | 18 +++ .../exception/RestClientErrorHandler.java | 27 ++++ .../common/interceptor/JwtInterceptor.java | 51 +++++++ .../java/com/gamsa/common/jwt/JwtUtil.java | 63 +++++++++ .../common/utils/ExtractUserIdFromJwt.java | 11 ++ .../history/controller/HistoryController.java | 39 ++++-- .../gamsa/history/dto/HistorySaveRequest.java | 1 - .../HistoryCustomRepositoryImpl.java | 14 +- .../gamsa/history/service/HistoryService.java | 23 ++-- .../gamsa/user/controller/UserController.java | 30 +++++ .../com/gamsa/user/domain/KakaoLogin.java | 29 ++++ src/main/java/com/gamsa/user/domain/User.java | 12 ++ .../gamsa/user/dto/KakaoLoginResponse.java | 12 ++ .../com/gamsa/user/dto/KakaoProperties.java | 13 ++ .../gamsa/user/dto/KakaoUserInfoResponse.java | 34 +++++ .../com/gamsa/user/entity/UserJpaEntity.java | 42 ++++++ .../user/exception/KakaoApiErrorCode.java | 20 +++ .../user/exception/KakaoApiException.java | 11 ++ .../KakaoAccessTokenRepository.java | 20 +++ .../user/repository/UserJpaRepository.java | 8 ++ .../gamsa/user/repository/UserRepository.java | 12 ++ .../user/repository/UserRepositoryImpl.java | 25 ++++ .../com/gamsa/user/service/UserService.java | 57 ++++++++ src/main/resources/application-dev.yml | 3 + src/main/resources/application.yml | 3 + src/test/java/com/gamsa/ApplicationTests.java | 13 -- .../avatar/entity/AvatarJpaEntityTest.java | 40 ++++-- .../avatar/service/AvatarServiceTest.java | 45 +++++-- .../avatar/stub/StubAvatarRepository.java | 31 ----- .../stub/StubEmptyAvatarRepository.java | 33 +++++ .../stub/StubExistsAvatarRepository.java | 47 +++++++ .../history/entity/HistoryJpaEntityTest.java | 124 +++++++++--------- .../repository/HistoryJpaRepositoryTest.java | 114 ++++++++-------- .../history/service/HitstoryServiceTest.java | 23 ++-- .../gamsa/user/service/UserServiceTest.java | 58 ++++++++ .../stub/DummyKakaoAccessTokenRepository.java | 17 +++ .../com/gamsa/user/stub/DummyKakaoLogin.java | 17 +++ .../user/stub/StubExistsUserRepository.java | 23 ++++ 52 files changed, 1113 insertions(+), 316 deletions(-) delete mode 100644 src/main/java/com/gamsa/auth/SecurityConfig.java create mode 100644 src/main/java/com/gamsa/common/config/WebConfig.java create mode 100644 src/main/java/com/gamsa/common/exception/RestClientErrorHandler.java create mode 100644 src/main/java/com/gamsa/common/interceptor/JwtInterceptor.java create mode 100644 src/main/java/com/gamsa/common/jwt/JwtUtil.java create mode 100644 src/main/java/com/gamsa/common/utils/ExtractUserIdFromJwt.java create mode 100644 src/main/java/com/gamsa/user/controller/UserController.java create mode 100644 src/main/java/com/gamsa/user/domain/KakaoLogin.java create mode 100644 src/main/java/com/gamsa/user/domain/User.java create mode 100644 src/main/java/com/gamsa/user/dto/KakaoLoginResponse.java create mode 100644 src/main/java/com/gamsa/user/dto/KakaoProperties.java create mode 100644 src/main/java/com/gamsa/user/dto/KakaoUserInfoResponse.java create mode 100644 src/main/java/com/gamsa/user/entity/UserJpaEntity.java create mode 100644 src/main/java/com/gamsa/user/exception/KakaoApiErrorCode.java create mode 100644 src/main/java/com/gamsa/user/exception/KakaoApiException.java create mode 100644 src/main/java/com/gamsa/user/repository/KakaoAccessTokenRepository.java create mode 100644 src/main/java/com/gamsa/user/repository/UserJpaRepository.java create mode 100644 src/main/java/com/gamsa/user/repository/UserRepository.java create mode 100644 src/main/java/com/gamsa/user/repository/UserRepositoryImpl.java create mode 100644 src/main/java/com/gamsa/user/service/UserService.java delete mode 100644 src/test/java/com/gamsa/ApplicationTests.java delete mode 100644 src/test/java/com/gamsa/avatar/stub/StubAvatarRepository.java create mode 100644 src/test/java/com/gamsa/avatar/stub/StubEmptyAvatarRepository.java create mode 100644 src/test/java/com/gamsa/avatar/stub/StubExistsAvatarRepository.java create mode 100644 src/test/java/com/gamsa/user/service/UserServiceTest.java create mode 100644 src/test/java/com/gamsa/user/stub/DummyKakaoAccessTokenRepository.java create mode 100644 src/test/java/com/gamsa/user/stub/DummyKakaoLogin.java create mode 100644 src/test/java/com/gamsa/user/stub/StubExistsUserRepository.java diff --git a/build.gradle b/build.gradle index 7d091ce..18efdfa 100644 --- a/build.gradle +++ b/build.gradle @@ -24,7 +24,6 @@ repositories { } dependencies { - implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' @@ -34,6 +33,11 @@ dependencies { annotationProcessor 'jakarta.persistence:jakarta.persistence-api' implementation 'org.hibernate.validator:hibernate-validator' + // jwt + implementation 'io.jsonwebtoken:jjwt-api:0.12.6' + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.6' + runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.6' + compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.h2database:h2' annotationProcessor 'org.projectlombok:lombok' diff --git a/src/main/java/com/gamsa/Application.java b/src/main/java/com/gamsa/Application.java index df58f1c..1ac8a46 100644 --- a/src/main/java/com/gamsa/Application.java +++ b/src/main/java/com/gamsa/Application.java @@ -2,8 +2,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +@ConfigurationPropertiesScan @EnableJpaAuditing @SpringBootApplication public class Application { diff --git a/src/main/java/com/gamsa/auth/SecurityConfig.java b/src/main/java/com/gamsa/auth/SecurityConfig.java deleted file mode 100644 index 0898128..0000000 --- a/src/main/java/com/gamsa/auth/SecurityConfig.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.gamsa.auth; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; -import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer; -import org.springframework.security.web.SecurityFilterChain; - -@Configuration -@EnableWebSecurity -public class SecurityConfig { - - @Bean - public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - http - .csrf(AbstractHttpConfigurer::disable) - .headers(headerConfig -> headerConfig.frameOptions( - HeadersConfigurer.FrameOptionsConfig::sameOrigin)) - .authorizeHttpRequests(authorizeRequest -> { - authorizeRequest - .anyRequest().permitAll(); - }); - return http.build(); - } -} diff --git a/src/main/java/com/gamsa/avatar/constant/Experienced.java b/src/main/java/com/gamsa/avatar/constant/Experienced.java index 392c006..3e6133d 100644 --- a/src/main/java/com/gamsa/avatar/constant/Experienced.java +++ b/src/main/java/com/gamsa/avatar/constant/Experienced.java @@ -9,7 +9,7 @@ @RequiredArgsConstructor public enum Experienced { NOVICE("초심자"), - INTERMIDIATE("중급자"), + INTERMEDIATE("중급자"), EXPERT("상급자"); private final String name; diff --git a/src/main/java/com/gamsa/avatar/controller/AvatarController.java b/src/main/java/com/gamsa/avatar/controller/AvatarController.java index ee6c805..d95105c 100644 --- a/src/main/java/com/gamsa/avatar/controller/AvatarController.java +++ b/src/main/java/com/gamsa/avatar/controller/AvatarController.java @@ -4,38 +4,54 @@ import com.gamsa.avatar.dto.AvatarFindResponse; import com.gamsa.avatar.dto.AvatarSaveRequest; import com.gamsa.avatar.service.AvatarService; +import com.gamsa.common.utils.ExtractUserIdFromJwt; +import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; @RequiredArgsConstructor @RestController -@RequestMapping("/api/v1/avatar") +@RequestMapping("/api/v1/avatars") public class AvatarController { private final AvatarService avatarService; @PostMapping - public ResponseEntity saveAvatar(@RequestBody AvatarSaveRequest saveRequest) { - avatarService.save(saveRequest); + public ResponseEntity saveAvatar(@RequestBody AvatarSaveRequest saveRequest, + HttpServletRequest request) { + + Long userId = ExtractUserIdFromJwt.extract(request); + avatarService.save(saveRequest, userId); return new ResponseEntity<>(HttpStatus.CREATED); } - @GetMapping("{avatar-id}") - public AvatarFindResponse getAvatar(@PathVariable("avatar-id") Long avatarId) { - return avatarService.findById(avatarId); + @GetMapping + public AvatarFindResponse getAvatar(HttpServletRequest request) { + + Long userId = ExtractUserIdFromJwt.extract(request); + return avatarService.findByUserId(userId); } - @PutMapping("{avatar-id}") - public ResponseEntity updateAvatar(@PathVariable("avatar-id") Long avatarId, @RequestBody AvatarSaveRequest saveRequest) { - avatarService.update(avatarId, saveRequest); + @PutMapping + public ResponseEntity updateAvatar(@RequestBody AvatarSaveRequest saveRequest, + HttpServletRequest request) { + Long userId = ExtractUserIdFromJwt.extract(request); + avatarService.update(saveRequest, userId); return new ResponseEntity<>(HttpStatus.OK); } - @DeleteMapping("{avatar-Id}") - public ResponseEntity deleteAvatar(@PathVariable("avatar-id") Long avatarId) { - avatarService.delete(avatarId); + @DeleteMapping + public ResponseEntity deleteAvatar(HttpServletRequest request) { + Long userId = ExtractUserIdFromJwt.extract(request); + avatarService.delete(userId); return new ResponseEntity<>(HttpStatus.NO_CONTENT); } } diff --git a/src/main/java/com/gamsa/avatar/domain/Avatar.java b/src/main/java/com/gamsa/avatar/domain/Avatar.java index 9242853..faebb7b 100644 --- a/src/main/java/com/gamsa/avatar/domain/Avatar.java +++ b/src/main/java/com/gamsa/avatar/domain/Avatar.java @@ -2,6 +2,7 @@ import com.gamsa.avatar.constant.AgeRange; import com.gamsa.avatar.constant.Experienced; +import com.gamsa.user.domain.User; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -11,6 +12,7 @@ @AllArgsConstructor public class Avatar { private Long avatarId; + private User user; private Long avatarExp; private Long avatarLevel; private String nickname; diff --git a/src/main/java/com/gamsa/avatar/dto/AvatarFindResponse.java b/src/main/java/com/gamsa/avatar/dto/AvatarFindResponse.java index f016524..5717ad1 100644 --- a/src/main/java/com/gamsa/avatar/dto/AvatarFindResponse.java +++ b/src/main/java/com/gamsa/avatar/dto/AvatarFindResponse.java @@ -7,8 +7,6 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; -import java.time.LocalDateTime; - @Getter @Builder @RequiredArgsConstructor @@ -18,16 +16,16 @@ public class AvatarFindResponse { private final Long avatarLevel; private final String nickName; private final AgeRange ageRange; - private final Experienced exprienced; + private final Experienced experienced; public static AvatarFindResponse from(Avatar avatar) { return AvatarFindResponse.builder() - .avatarId(avatar.getAvatarId()) - .avatarExp(avatar.getAvatarExp()) - .avatarLevel(avatar.getAvatarLevel()) - .nickName(avatar.getNickname()) - .ageRange(avatar.getAgeRange()) - .exprienced(avatar.getExperienced()) - .build(); + .avatarId(avatar.getAvatarId()) + .avatarExp(avatar.getAvatarExp()) + .avatarLevel(avatar.getAvatarLevel()) + .nickName(avatar.getNickname()) + .ageRange(avatar.getAgeRange()) + .experienced(avatar.getExperienced()) + .build(); } } diff --git a/src/main/java/com/gamsa/avatar/dto/AvatarSaveRequest.java b/src/main/java/com/gamsa/avatar/dto/AvatarSaveRequest.java index 28af1b5..c5e95ef 100644 --- a/src/main/java/com/gamsa/avatar/dto/AvatarSaveRequest.java +++ b/src/main/java/com/gamsa/avatar/dto/AvatarSaveRequest.java @@ -3,6 +3,7 @@ import com.gamsa.avatar.constant.AgeRange; import com.gamsa.avatar.constant.Experienced; import com.gamsa.avatar.domain.Avatar; +import com.gamsa.user.domain.User; import jakarta.validation.constraints.Max; import jakarta.validation.constraints.NotNull; import lombok.Builder; @@ -23,13 +24,14 @@ public class AvatarSaveRequest { @NotNull(message = "봉사 활동 경험을 선택해야 합니다.") private final Experienced experienced; - public Avatar toModel() { + public Avatar toModel(User user) { return Avatar.builder() - .nickname(nickname) - .avatarExp(0L) - .avatarLevel(0L) - .ageRange(ageRange) - .experienced(experienced) - .build(); + .user(user) + .nickname(nickname) + .avatarExp(0L) + .avatarLevel(0L) + .ageRange(ageRange) + .experienced(experienced) + .build(); } } diff --git a/src/main/java/com/gamsa/avatar/entity/AvatarJpaEntity.java b/src/main/java/com/gamsa/avatar/entity/AvatarJpaEntity.java index ca9d4ec..209840f 100644 --- a/src/main/java/com/gamsa/avatar/entity/AvatarJpaEntity.java +++ b/src/main/java/com/gamsa/avatar/entity/AvatarJpaEntity.java @@ -6,8 +6,21 @@ import com.gamsa.avatar.constant.ExperiencedConverter; import com.gamsa.avatar.domain.Avatar; import com.gamsa.common.entity.BaseEntity; -import jakarta.persistence.*; -import lombok.*; +import com.gamsa.user.entity.UserJpaEntity; +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToOne; +import jakarta.persistence.Table; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; @Getter @Setter @@ -18,10 +31,14 @@ @NoArgsConstructor public class AvatarJpaEntity extends BaseEntity { @Id - @GeneratedValue - @Column(name = "id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "avatar_id") private Long avatarId; + @OneToOne + @JoinColumn(name = "user_id", unique = true) + private UserJpaEntity user; + @Column(name = "avatar_exp") private Long avatarExp; @@ -41,23 +58,25 @@ public class AvatarJpaEntity extends BaseEntity { public static AvatarJpaEntity from(Avatar avatar) { return AvatarJpaEntity.builder() - .avatarId(avatar.getAvatarId()) - .avatarExp(avatar.getAvatarExp()) - .avatarLevel(avatar.getAvatarLevel()) - .nickname(avatar.getNickname()) - .ageRange(avatar.getAgeRange()) - .experienced(avatar.getExperienced()) - .build(); + .avatarId(avatar.getAvatarId()) + .user(UserJpaEntity.from(avatar.getUser())) + .avatarExp(avatar.getAvatarExp()) + .avatarLevel(avatar.getAvatarLevel()) + .nickname(avatar.getNickname()) + .ageRange(avatar.getAgeRange()) + .experienced(avatar.getExperienced()) + .build(); } public Avatar toModel() { return Avatar.builder() - .avatarId(avatarId) - .avatarExp(avatarExp) - .avatarLevel(avatarLevel) - .nickname(nickname) - .ageRange(ageRange) - .experienced(experienced) - .build(); + .avatarId(avatarId) + .user(user.toModel()) + .avatarExp(avatarExp) + .avatarLevel(avatarLevel) + .nickname(nickname) + .ageRange(ageRange) + .experienced(experienced) + .build(); } } diff --git a/src/main/java/com/gamsa/avatar/repository/AvatarJpaRepository.java b/src/main/java/com/gamsa/avatar/repository/AvatarJpaRepository.java index 08a8155..365a29c 100644 --- a/src/main/java/com/gamsa/avatar/repository/AvatarJpaRepository.java +++ b/src/main/java/com/gamsa/avatar/repository/AvatarJpaRepository.java @@ -1,12 +1,14 @@ package com.gamsa.avatar.repository; -import com.gamsa.avatar.domain.Avatar; import com.gamsa.avatar.entity.AvatarJpaEntity; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import java.util.Optional; - @Repository public interface AvatarJpaRepository extends JpaRepository { + + Optional findByUserId(Long userId); + + Optional findByNickname(String nickname); } diff --git a/src/main/java/com/gamsa/avatar/repository/AvatarRepository.java b/src/main/java/com/gamsa/avatar/repository/AvatarRepository.java index fc6a8eb..aa8cf73 100644 --- a/src/main/java/com/gamsa/avatar/repository/AvatarRepository.java +++ b/src/main/java/com/gamsa/avatar/repository/AvatarRepository.java @@ -1,7 +1,6 @@ package com.gamsa.avatar.repository; import com.gamsa.avatar.domain.Avatar; - import java.util.Optional; public interface AvatarRepository { @@ -9,5 +8,9 @@ public interface AvatarRepository { Optional findById(Long id); + Optional findByUserId(Long userId); + + Optional findByNickname(String nickname); + void deleteById(Long id); } diff --git a/src/main/java/com/gamsa/avatar/repository/AvatarRepositoryImpl.java b/src/main/java/com/gamsa/avatar/repository/AvatarRepositoryImpl.java index bb75b85..4031d03 100644 --- a/src/main/java/com/gamsa/avatar/repository/AvatarRepositoryImpl.java +++ b/src/main/java/com/gamsa/avatar/repository/AvatarRepositoryImpl.java @@ -2,11 +2,10 @@ import com.gamsa.avatar.domain.Avatar; import com.gamsa.avatar.entity.AvatarJpaEntity; +import java.util.Optional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; -import java.util.Optional; - @RequiredArgsConstructor @Repository public class AvatarRepositoryImpl implements AvatarRepository { @@ -22,6 +21,18 @@ public Optional findById(Long id) { return avatarJpaRepository.findById(id).map(AvatarJpaEntity::toModel); } + @Override + public Optional findByUserId(Long userId) { + return avatarJpaRepository.findByUserId(userId) + .map(AvatarJpaEntity::toModel); + } + + @Override + public Optional findByNickname(String nickname) { + return avatarJpaRepository.findByNickname(nickname) + .map(AvatarJpaEntity::toModel); + } + @Override public void deleteById(Long id) { avatarJpaRepository.deleteById(id); diff --git a/src/main/java/com/gamsa/avatar/service/AvatarService.java b/src/main/java/com/gamsa/avatar/service/AvatarService.java index 3eb0193..503ca7c 100644 --- a/src/main/java/com/gamsa/avatar/service/AvatarService.java +++ b/src/main/java/com/gamsa/avatar/service/AvatarService.java @@ -4,40 +4,53 @@ import com.gamsa.avatar.dto.AvatarFindResponse; import com.gamsa.avatar.dto.AvatarSaveRequest; import com.gamsa.avatar.repository.AvatarRepository; +import com.gamsa.user.domain.User; +import com.gamsa.user.repository.UserRepository; +import java.util.NoSuchElementException; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import java.util.NoSuchElementException; - @RequiredArgsConstructor @Service public class AvatarService { private final AvatarRepository avatarRepository; + private final UserRepository userRepository; - public AvatarFindResponse findById(Long id) { - Avatar avatar = avatarRepository.findById(id).orElseThrow(NoSuchElementException::new); + public AvatarFindResponse findByUserId(Long userId) { + Avatar avatar = avatarRepository.findByUserId(userId) + .orElseThrow(NoSuchElementException::new); return AvatarFindResponse.from(avatar); } - public void save(AvatarSaveRequest saveRequest) { - Avatar avatar = saveRequest.toModel(); - avatarRepository.save(avatar); + public void save(AvatarSaveRequest saveRequest, Long userId) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new NoSuchElementException("존재하지 않는 유저.")); + + Avatar newAvatar = saveRequest.toModel(user); + avatarRepository.findByNickname(newAvatar.getNickname()) + .ifPresent(avatar -> { + throw new IllegalArgumentException("이미 존재하는 닉네임"); + }); + avatarRepository.save(newAvatar); } - public void delete(Long id) { - avatarRepository.findById(id).orElseThrow(NoSuchElementException::new); - avatarRepository.deleteById(id); + public void delete(Long userId) { + Avatar avatar = avatarRepository.findByUserId(userId) + .orElseThrow(NoSuchElementException::new); + avatarRepository.deleteById(avatar.getAvatarId()); } - public AvatarFindResponse expUp(Long avatarId, int amount) { - Avatar avatar = avatarRepository.findById(avatarId).orElseThrow(NoSuchElementException::new); + public AvatarFindResponse expUp(Long userId, int amount) { + Avatar avatar = avatarRepository.findByUserId(userId) + .orElseThrow(NoSuchElementException::new); avatar.expUp(amount); avatarRepository.save(avatar); return AvatarFindResponse.from(avatar); } - public AvatarFindResponse update(Long avatarId, AvatarSaveRequest saveRequest) { - Avatar avatar = avatarRepository.findById(avatarId).orElseThrow(NoSuchElementException::new); + public AvatarFindResponse update(AvatarSaveRequest saveRequest, Long userId) { + Avatar avatar = avatarRepository.findByUserId(userId) + .orElseThrow(NoSuchElementException::new); avatar.changeAgeRange(saveRequest.getAgeRange()); avatar.changeExperience(saveRequest.getExperienced()); avatar.changeNickname(saveRequest.getNickname()); diff --git a/src/main/java/com/gamsa/common/config/WebConfig.java b/src/main/java/com/gamsa/common/config/WebConfig.java new file mode 100644 index 0000000..5b87dee --- /dev/null +++ b/src/main/java/com/gamsa/common/config/WebConfig.java @@ -0,0 +1,27 @@ +package com.gamsa.common.config; + +import com.gamsa.common.interceptor.JwtInterceptor; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@RequiredArgsConstructor +@Configuration +public class WebConfig implements WebMvcConfigurer { + + private final JwtInterceptor jwtInterceptor; + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(jwtInterceptor) + .addPathPatterns("/api/v1/avatars/**") + .addPathPatterns("/api/v1/histories/**"); + } + + @Override + public void addCorsMappings(CorsRegistry registry) { + WebMvcConfigurer.super.addCorsMappings(registry); + } +} diff --git a/src/main/java/com/gamsa/common/exception/GlobalExceptionHandler.java b/src/main/java/com/gamsa/common/exception/GlobalExceptionHandler.java index 8c71a3d..845f381 100644 --- a/src/main/java/com/gamsa/common/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/gamsa/common/exception/GlobalExceptionHandler.java @@ -1,6 +1,8 @@ package com.gamsa.common.exception; import com.gamsa.activity.exception.ActivityException; +import com.gamsa.user.exception.KakaoApiException; +import java.util.NoSuchElementException; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; @@ -17,4 +19,20 @@ private ResponseEntity ActivityCustomExceptionHandler(ActivityException e) { .status(e.getActivityErrorCode().getStatus()) .body(e.getActivityErrorCode().getMsg()); } + + @ExceptionHandler(KakaoApiException.class) + private ResponseEntity kakaoApiExceptionHandler(KakaoApiException e) { + log.error(String.valueOf(e.getStackTrace()[0])); + return ResponseEntity + .status(e.getKakaoAPIErrorCode().getStatus()) + .body(e.getKakaoAPIErrorCode().getMsg()); + } + + @ExceptionHandler(value = {NoSuchElementException.class, IllegalArgumentException.class}) + private ResponseEntity noSuchElementExceptionHandler(Exception e) { + log.error(String.valueOf(e.getStackTrace()[0])); + return ResponseEntity + .badRequest() + .body(e.getMessage()); + } } diff --git a/src/main/java/com/gamsa/common/exception/RestClientErrorHandler.java b/src/main/java/com/gamsa/common/exception/RestClientErrorHandler.java new file mode 100644 index 0000000..00675e2 --- /dev/null +++ b/src/main/java/com/gamsa/common/exception/RestClientErrorHandler.java @@ -0,0 +1,27 @@ +package com.gamsa.common.exception; + +import com.gamsa.user.exception.KakaoApiErrorCode; +import com.gamsa.user.exception.KakaoApiException; +import org.springframework.web.client.RestClient.ResponseSpec.ErrorHandler; + +public class RestClientErrorHandler { + + public static ErrorHandler http4xxErrorHandler = (request, response) -> { + switch (response.getStatusCode().value()) { + case 400: + throw new KakaoApiException(KakaoApiErrorCode.KAKAO_API_BAD_REQUEST); + case 401: + throw new KakaoApiException(KakaoApiErrorCode.KAKAO_API_UNAUTHORIZED); + case 403: + throw new KakaoApiException(KakaoApiErrorCode.KAKAO_API_FORBIDDEN); + } + }; + + public static ErrorHandler http5xxErrorHandler = (request, response) -> { + switch (response.getStatusCode().value()) { + case 500: + throw new KakaoApiException(KakaoApiErrorCode.KAKAO_API_INTERNAL_SERVER_ERROR); + } + }; + +} diff --git a/src/main/java/com/gamsa/common/interceptor/JwtInterceptor.java b/src/main/java/com/gamsa/common/interceptor/JwtInterceptor.java new file mode 100644 index 0000000..f0e6489 --- /dev/null +++ b/src/main/java/com/gamsa/common/interceptor/JwtInterceptor.java @@ -0,0 +1,51 @@ +package com.gamsa.common.interceptor; + +import com.gamsa.common.jwt.JwtUtil; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; + +@Slf4j +@RequiredArgsConstructor +@Component +public class JwtInterceptor implements HandlerInterceptor { + + private final JwtUtil jwtUtil; + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, + Object handler) throws Exception { + + String authorizationHeader = request.getHeader(HttpHeaders.AUTHORIZATION); + String token = null; + + if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) { + token = authorizationHeader.substring(7); + } else { + unauthorizedResponse(response); + return false; + } + + try { + Long userId = jwtUtil.getUserId(token); + request.setAttribute("userId", userId); + } catch (Exception e) { + log.warn(e.getMessage()); + unauthorizedResponse(response); + return false; + } + + return true; + } + + private void unauthorizedResponse(HttpServletResponse response) throws IOException { + response.setCharacterEncoding("UTF-8"); + response.setStatus(HttpStatus.UNAUTHORIZED.value()); + } +} diff --git a/src/main/java/com/gamsa/common/jwt/JwtUtil.java b/src/main/java/com/gamsa/common/jwt/JwtUtil.java new file mode 100644 index 0000000..7f1646b --- /dev/null +++ b/src/main/java/com/gamsa/common/jwt/JwtUtil.java @@ -0,0 +1,63 @@ +package com.gamsa.common.jwt; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jws; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.Jwts.SIG; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +public class JwtUtil { + + private final SecretKey secretKey; + + /** + * secret과 알고리즘을 저장하여 Secretkey에 저장 + */ + public JwtUtil(@Value("${spring.jwt.secret}") String secret) { + this.secretKey = new SecretKeySpec( + secret.getBytes(StandardCharsets.UTF_8), + SIG.HS256.key().build().getAlgorithm() + ); + } + + /** + * 토큰을 통해 payload 정보 반환 + */ + public Long getUserId(String token) { + return validateJwt(token) + .getPayload() + .get("userId", Long.class); + } + + /** + * 토큰을 통해 만료가 되었는지 확인 + */ + public boolean isExpired(String token) { + return validateJwt(token) + .getPayload() + .getExpiration() + .before(new Date()); + } + + public String createJwt(Long userId, long expirationMs) { + return Jwts.builder() + .claim("userId", userId) + .issuedAt(new Date()) + .expiration(new Date(System.currentTimeMillis() + expirationMs)) + .signWith(secretKey) + .compact(); + } + + private Jws validateJwt(String token) { + return Jwts.parser() + .verifyWith(secretKey) + .build() + .parseSignedClaims(token); + } +} diff --git a/src/main/java/com/gamsa/common/utils/ExtractUserIdFromJwt.java b/src/main/java/com/gamsa/common/utils/ExtractUserIdFromJwt.java new file mode 100644 index 0000000..5d470e5 --- /dev/null +++ b/src/main/java/com/gamsa/common/utils/ExtractUserIdFromJwt.java @@ -0,0 +1,11 @@ +package com.gamsa.common.utils; + +import jakarta.servlet.http.HttpServletRequest; + +public class ExtractUserIdFromJwt { + + public static Long extract(HttpServletRequest request) { + return (Long) request.getAttribute("userId"); + } + +} diff --git a/src/main/java/com/gamsa/history/controller/HistoryController.java b/src/main/java/com/gamsa/history/controller/HistoryController.java index 3d3ace9..f5ba140 100644 --- a/src/main/java/com/gamsa/history/controller/HistoryController.java +++ b/src/main/java/com/gamsa/history/controller/HistoryController.java @@ -1,8 +1,10 @@ package com.gamsa.history.controller; +import com.gamsa.common.utils.ExtractUserIdFromJwt; import com.gamsa.history.dto.HistoryFindSliceResponse; import com.gamsa.history.dto.HistorySaveRequest; import com.gamsa.history.service.HistoryService; +import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; @@ -10,26 +12,41 @@ import org.springframework.data.domain.Sort; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; @RequiredArgsConstructor @RestController -@RequestMapping("/api/v1/history") +@RequestMapping("/api/v1/histories") public class HistoryController { - private HistoryService historyService; - private static final int MAX_SIZE = Integer.MAX_VALUE; + private final HistoryService historyService; + + private static final int MAX_SIZE = Integer.MAX_VALUE - 1; @PostMapping - public ResponseEntity addHistory(@RequestBody HistorySaveRequest saveRequest) { - historyService.save(saveRequest); + public ResponseEntity addHistory(@RequestBody HistorySaveRequest saveRequest, + HttpServletRequest request) { + + Long userId = ExtractUserIdFromJwt.extract(request); + historyService.save(saveRequest, userId); return new ResponseEntity<>(HttpStatus.CREATED); } - @GetMapping("{avatar-id}") - public Slice findSliceByUserId(@PathVariable("avatar-id") long avatarId, - @RequestParam(value = "page", required = false) Integer page, - @RequestParam(value = "size", required = false) Integer size) { + @GetMapping + public Slice findSliceByUserId( + @RequestParam(value = "page", required = false) Integer page, + @RequestParam(value = "size", required = false) Integer size, + HttpServletRequest request) { + + Long userId = ExtractUserIdFromJwt.extract(request); + Pageable pageable; if (page == null || size == null) { @@ -37,7 +54,7 @@ public Slice findSliceByUserId(@PathVariable("avatar-i } else { pageable = PageRequest.of(page, size, Sort.unsorted()); } - return historyService.findSliceByAvatarId(avatarId, pageable); + return historyService.findSliceByAvatarId(userId, pageable); } @DeleteMapping("{history-id}") diff --git a/src/main/java/com/gamsa/history/dto/HistorySaveRequest.java b/src/main/java/com/gamsa/history/dto/HistorySaveRequest.java index b1f5964..5c83bd2 100644 --- a/src/main/java/com/gamsa/history/dto/HistorySaveRequest.java +++ b/src/main/java/com/gamsa/history/dto/HistorySaveRequest.java @@ -12,7 +12,6 @@ @Builder @RequiredArgsConstructor public class HistorySaveRequest { - private final long avatarId; private final long actId; public History toModel(Avatar avatar, Activity activity) { diff --git a/src/main/java/com/gamsa/history/repository/HistoryCustomRepositoryImpl.java b/src/main/java/com/gamsa/history/repository/HistoryCustomRepositoryImpl.java index 012feed..9ee3a9e 100644 --- a/src/main/java/com/gamsa/history/repository/HistoryCustomRepositoryImpl.java +++ b/src/main/java/com/gamsa/history/repository/HistoryCustomRepositoryImpl.java @@ -1,20 +1,19 @@ package com.gamsa.history.repository; +import static com.gamsa.history.entity.QHistoryJpaEntity.historyJpaEntity; + import com.gamsa.history.entity.HistoryJpaEntity; import com.querydsl.core.types.Order; import com.querydsl.core.types.OrderSpecifier; import com.querydsl.core.types.dsl.PathBuilder; import com.querydsl.jpa.impl.JPAQueryFactory; +import java.util.ArrayList; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.data.domain.SliceImpl; -import java.util.ArrayList; -import java.util.List; - -import static com.gamsa.history.entity.QHistoryJpaEntity.historyJpaEntity; - @RequiredArgsConstructor public class HistoryCustomRepositoryImpl implements HistoryCustomRepository { private final JPAQueryFactory jpaQueryFactory; @@ -28,7 +27,7 @@ public Slice findSliceByAvatarId(long avatarId, Pageable pagea .where(historyJpaEntity.avatar.avatarId.eq(avatarId)) .orderBy(orders.toArray(OrderSpecifier[]::new)) .offset(pageable.getOffset()) - .limit(pageable.getOffset() + 1) + .limit(pageable.getPageSize() + 1) .fetch(); return checkLastPage(pageable, results); @@ -50,7 +49,8 @@ private List getAllOrderSpecifiers(Pageable pageable) { .forEach(order -> { Order direction = order.getDirection().isAscending() ? Order.ASC : Order.DESC; String property = order.getProperty(); - PathBuilder orderPath = new PathBuilder(HistoryJpaEntity.class, "activityJpaEntity"); + PathBuilder orderPath = new PathBuilder(HistoryJpaEntity.class, + "historyJpaEntity"); orders.add(new OrderSpecifier(direction, orderPath.get(property))); } ); diff --git a/src/main/java/com/gamsa/history/service/HistoryService.java b/src/main/java/com/gamsa/history/service/HistoryService.java index 8bb326c..6807550 100644 --- a/src/main/java/com/gamsa/history/service/HistoryService.java +++ b/src/main/java/com/gamsa/history/service/HistoryService.java @@ -8,14 +8,13 @@ import com.gamsa.history.dto.HistoryFindSliceResponse; import com.gamsa.history.dto.HistorySaveRequest; import com.gamsa.history.repository.HistoryRepository; +import java.time.LocalDateTime; +import java.util.NoSuchElementException; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.stereotype.Service; -import java.time.LocalDateTime; -import java.util.NoSuchElementException; - @RequiredArgsConstructor @Service public class HistoryService { @@ -23,16 +22,24 @@ public class HistoryService { private final AvatarRepository avatarRepository; private final ActivityRepository activityRepository; - public void save(HistorySaveRequest saveRequest) { - Avatar avatar = avatarRepository.findById(saveRequest.getAvatarId()).orElseThrow(NoSuchElementException::new); - Activity activity = activityRepository.findById(saveRequest.getActId()).orElseThrow(NoSuchElementException::new); + public void save(HistorySaveRequest saveRequest, Long userId) { + + Avatar avatar = avatarRepository.findByUserId(userId) + .orElseThrow(NoSuchElementException::new); + Activity activity = activityRepository.findById(saveRequest.getActId()) + .orElseThrow(NoSuchElementException::new); History history = saveRequest.toModel(avatar, activity); historyRepository.save(history); } - public Slice findSliceByAvatarId(long avatarId, Pageable pageable) { - Slice histories = historyRepository.findSliceByAvatarId(avatarId, pageable); + public Slice findSliceByAvatarId(Long userId, Pageable pageable) { + Avatar avatar = avatarRepository.findByUserId(userId) + .orElseThrow(NoSuchElementException::new); + + Slice histories = historyRepository + .findSliceByAvatarId(avatar.getAvatarId(), pageable); histories.forEach(this::checkDate); + return histories.map(HistoryFindSliceResponse::from); } diff --git a/src/main/java/com/gamsa/user/controller/UserController.java b/src/main/java/com/gamsa/user/controller/UserController.java new file mode 100644 index 0000000..e82c2d5 --- /dev/null +++ b/src/main/java/com/gamsa/user/controller/UserController.java @@ -0,0 +1,30 @@ +package com.gamsa.user.controller; + +import com.gamsa.user.dto.KakaoLoginResponse; +import com.gamsa.user.service.UserService; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RequiredArgsConstructor +@RestController +@RequestMapping("/api/v1/users") +public class UserController { + + private final UserService userService; + + @GetMapping("/login/kakao") + public ResponseEntity kakaoLogin( + @RequestHeader Map headers) { + + Map response = userService.userKakaoLogin(headers.get("token")); + + return ResponseEntity.ok() + .header("token", (String) response.get("token")) + .body((KakaoLoginResponse) response.get("body")); + } +} diff --git a/src/main/java/com/gamsa/user/domain/KakaoLogin.java b/src/main/java/com/gamsa/user/domain/KakaoLogin.java new file mode 100644 index 0000000..0fa42de --- /dev/null +++ b/src/main/java/com/gamsa/user/domain/KakaoLogin.java @@ -0,0 +1,29 @@ +package com.gamsa.user.domain; + +import com.gamsa.common.exception.RestClientErrorHandler; +import com.gamsa.user.dto.KakaoProperties; +import com.gamsa.user.dto.KakaoUserInfoResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatusCode; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestClient; + +@RequiredArgsConstructor +@Component +public class KakaoLogin { + + private final KakaoProperties kakaoProperties; + private final RestClient restClient = RestClient.create(); + + public KakaoUserInfoResponse getUserInfo(String token) { + return restClient.post() + .uri(kakaoProperties.getUserInfoUrl()) + .contentType(MediaType.APPLICATION_FORM_URLENCODED) + .header("Authorization", "Bearer " + token) + .retrieve() + .onStatus(HttpStatusCode::is4xxClientError, RestClientErrorHandler.http4xxErrorHandler) + .onStatus(HttpStatusCode::is5xxServerError, RestClientErrorHandler.http5xxErrorHandler) + .body(KakaoUserInfoResponse.class); + } +} diff --git a/src/main/java/com/gamsa/user/domain/User.java b/src/main/java/com/gamsa/user/domain/User.java new file mode 100644 index 0000000..4efeae4 --- /dev/null +++ b/src/main/java/com/gamsa/user/domain/User.java @@ -0,0 +1,12 @@ +package com.gamsa.user.domain; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class User { + + private Long id; + private String nickname; +} diff --git a/src/main/java/com/gamsa/user/dto/KakaoLoginResponse.java b/src/main/java/com/gamsa/user/dto/KakaoLoginResponse.java new file mode 100644 index 0000000..7148934 --- /dev/null +++ b/src/main/java/com/gamsa/user/dto/KakaoLoginResponse.java @@ -0,0 +1,12 @@ +package com.gamsa.user.dto; + +import com.gamsa.avatar.dto.AvatarFindResponse; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class KakaoLoginResponse { + + private final AvatarFindResponse avatar; +} diff --git a/src/main/java/com/gamsa/user/dto/KakaoProperties.java b/src/main/java/com/gamsa/user/dto/KakaoProperties.java new file mode 100644 index 0000000..ab85284 --- /dev/null +++ b/src/main/java/com/gamsa/user/dto/KakaoProperties.java @@ -0,0 +1,13 @@ +package com.gamsa.user.dto; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@Getter +@RequiredArgsConstructor +@ConfigurationProperties(prefix = "kakao") +public class KakaoProperties { + + private final String userInfoUrl; +} diff --git a/src/main/java/com/gamsa/user/dto/KakaoUserInfoResponse.java b/src/main/java/com/gamsa/user/dto/KakaoUserInfoResponse.java new file mode 100644 index 0000000..e51650e --- /dev/null +++ b/src/main/java/com/gamsa/user/dto/KakaoUserInfoResponse.java @@ -0,0 +1,34 @@ +package com.gamsa.user.dto; + +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@JsonNaming(value = PropertyNamingStrategies.SnakeCaseStrategy.class) +public class KakaoUserInfoResponse { + + private Long id; + private KakaoAccount kakaoAccount; + + public String getNickname() { + return kakaoAccount.getProfile().getNickname(); + } + + @Getter + @NoArgsConstructor + private static class KakaoAccount { + + private Profile profile; + } + + @Getter + @NoArgsConstructor + private static class Profile { + + private String nickname; + } +} + diff --git a/src/main/java/com/gamsa/user/entity/UserJpaEntity.java b/src/main/java/com/gamsa/user/entity/UserJpaEntity.java new file mode 100644 index 0000000..9db4086 --- /dev/null +++ b/src/main/java/com/gamsa/user/entity/UserJpaEntity.java @@ -0,0 +1,42 @@ +package com.gamsa.user.entity; + +import com.gamsa.user.domain.User; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(name = "users") +@Entity +public class UserJpaEntity { + + @Id + @Column(name = "user_id") + private Long id; + + @Column(name = "nickname", nullable = false) + private String nickname; + + public static UserJpaEntity from(User user) { + return UserJpaEntity.builder() + .id(user.getId()) + .nickname(user.getNickname()) + .build(); + } + + public User toModel() { + return User.builder() + .id(id) + .nickname(nickname) + .build(); + } +} diff --git a/src/main/java/com/gamsa/user/exception/KakaoApiErrorCode.java b/src/main/java/com/gamsa/user/exception/KakaoApiErrorCode.java new file mode 100644 index 0000000..c6cd8ec --- /dev/null +++ b/src/main/java/com/gamsa/user/exception/KakaoApiErrorCode.java @@ -0,0 +1,20 @@ +package com.gamsa.user.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum KakaoApiErrorCode { + + // 4xx error + KAKAO_API_BAD_REQUEST(400, "잘못된 요청"), + KAKAO_API_UNAUTHORIZED(401, "인증 오류"), + KAKAO_API_FORBIDDEN(403, "허가되지 않은 접근"), + + // 5xx error + KAKAO_API_INTERNAL_SERVER_ERROR(500, "서버 내부 오류"); + + private final int status; + private final String msg; +} diff --git a/src/main/java/com/gamsa/user/exception/KakaoApiException.java b/src/main/java/com/gamsa/user/exception/KakaoApiException.java new file mode 100644 index 0000000..5f21f7b --- /dev/null +++ b/src/main/java/com/gamsa/user/exception/KakaoApiException.java @@ -0,0 +1,11 @@ +package com.gamsa.user.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class KakaoApiException extends RuntimeException { + + private final KakaoApiErrorCode kakaoAPIErrorCode; +} diff --git a/src/main/java/com/gamsa/user/repository/KakaoAccessTokenRepository.java b/src/main/java/com/gamsa/user/repository/KakaoAccessTokenRepository.java new file mode 100644 index 0000000..5cf6dab --- /dev/null +++ b/src/main/java/com/gamsa/user/repository/KakaoAccessTokenRepository.java @@ -0,0 +1,20 @@ +package com.gamsa.user.repository; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import org.springframework.stereotype.Repository; + +@Repository +public class KakaoAccessTokenRepository { + + private final Map tokenRepository = new HashMap<>(); + + public Optional findById(Long id) { + return Optional.of(tokenRepository.getOrDefault(id, null)); + } + + public void save(Long id, String token) { + tokenRepository.put(id, token); + } +} diff --git a/src/main/java/com/gamsa/user/repository/UserJpaRepository.java b/src/main/java/com/gamsa/user/repository/UserJpaRepository.java new file mode 100644 index 0000000..0f2b495 --- /dev/null +++ b/src/main/java/com/gamsa/user/repository/UserJpaRepository.java @@ -0,0 +1,8 @@ +package com.gamsa.user.repository; + +import com.gamsa.user.entity.UserJpaEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface UserJpaRepository extends JpaRepository { + +} diff --git a/src/main/java/com/gamsa/user/repository/UserRepository.java b/src/main/java/com/gamsa/user/repository/UserRepository.java new file mode 100644 index 0000000..86d949a --- /dev/null +++ b/src/main/java/com/gamsa/user/repository/UserRepository.java @@ -0,0 +1,12 @@ +package com.gamsa.user.repository; + +import com.gamsa.user.domain.User; +import java.util.Optional; + +public interface UserRepository { + + void save(User user); + + Optional findById(Long userId); + +} diff --git a/src/main/java/com/gamsa/user/repository/UserRepositoryImpl.java b/src/main/java/com/gamsa/user/repository/UserRepositoryImpl.java new file mode 100644 index 0000000..4182b57 --- /dev/null +++ b/src/main/java/com/gamsa/user/repository/UserRepositoryImpl.java @@ -0,0 +1,25 @@ +package com.gamsa.user.repository; + +import com.gamsa.user.domain.User; +import com.gamsa.user.entity.UserJpaEntity; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +@RequiredArgsConstructor +@Repository +public class UserRepositoryImpl implements UserRepository { + + private final UserJpaRepository userJpaRepository; + + @Override + public void save(User user) { + userJpaRepository.save(UserJpaEntity.from(user)); + } + + @Override + public Optional findById(Long userId) { + return userJpaRepository.findById(userId) + .map(UserJpaEntity::toModel); + } +} diff --git a/src/main/java/com/gamsa/user/service/UserService.java b/src/main/java/com/gamsa/user/service/UserService.java new file mode 100644 index 0000000..b823d20 --- /dev/null +++ b/src/main/java/com/gamsa/user/service/UserService.java @@ -0,0 +1,57 @@ +package com.gamsa.user.service; + +import com.gamsa.avatar.domain.Avatar; +import com.gamsa.avatar.dto.AvatarFindResponse; +import com.gamsa.avatar.repository.AvatarRepository; +import com.gamsa.common.jwt.JwtUtil; +import com.gamsa.user.domain.KakaoLogin; +import com.gamsa.user.domain.User; +import com.gamsa.user.dto.KakaoLoginResponse; +import com.gamsa.user.dto.KakaoUserInfoResponse; +import com.gamsa.user.repository.KakaoAccessTokenRepository; +import com.gamsa.user.repository.UserRepository; +import java.util.Map; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +@RequiredArgsConstructor +@Service +public class UserService { + + @Value("${spring.jwt.expiration-time}") + private long TOKEN_EXPIRED_TIME; + private final JwtUtil jwtUtil; + private final KakaoLogin kakaoLogin; + private final UserRepository userRepository; + private final AvatarRepository avatarRepository; + private final KakaoAccessTokenRepository kakaoAccessTokenRepository; + + public Map userKakaoLogin(String kakaoToken) { + KakaoUserInfoResponse userInfo = kakaoLogin.getUserInfo(kakaoToken); + Optional user = userRepository.findById(userInfo.getId()); + + if (user.isEmpty()) { + userRepository.save(generateNewUser(userInfo)); + } + kakaoAccessTokenRepository.save(userInfo.getId(), kakaoToken); + + Optional avatar = avatarRepository.findByUserId(userInfo.getId()); + KakaoLoginResponse body = KakaoLoginResponse.builder() + .avatar(avatar.map(AvatarFindResponse::from).orElse(null)) + .build(); + + return Map.of( + "token", (Object) jwtUtil.createJwt(userInfo.getId(), TOKEN_EXPIRED_TIME), + "body", (Object) body + ); + } + + private User generateNewUser(KakaoUserInfoResponse userInfo) { + return User.builder() + .id(userInfo.getId()) + .nickname(userInfo.getNickname()) + .build(); + } +} diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index f46c632..4b7f737 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -13,6 +13,9 @@ spring: hibernate.ddl-auto: create properties.hibernate.format_sql: true show-sql: true + jwt: + secret: kghobnakghwoigabsdlkbghaoigqhegowebnhlkwehgaoiwehtoaweqnbzoiwnyzbvwow + expiration-time: 86400000 logging: level: diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 202f78a..e75c3d5 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -3,3 +3,6 @@ spring: name: gamja-bongsa profiles: active: dev + +kakao: + user-info-url: https://kapi.kakao.com/v2/user/me diff --git a/src/test/java/com/gamsa/ApplicationTests.java b/src/test/java/com/gamsa/ApplicationTests.java deleted file mode 100644 index 411875a..0000000 --- a/src/test/java/com/gamsa/ApplicationTests.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.gamsa; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class ApplicationTests { - - @Test - void contextLoads() { - } - -} diff --git a/src/test/java/com/gamsa/avatar/entity/AvatarJpaEntityTest.java b/src/test/java/com/gamsa/avatar/entity/AvatarJpaEntityTest.java index f3e14fc..38f5e78 100644 --- a/src/test/java/com/gamsa/avatar/entity/AvatarJpaEntityTest.java +++ b/src/test/java/com/gamsa/avatar/entity/AvatarJpaEntityTest.java @@ -5,20 +5,27 @@ import com.gamsa.avatar.constant.AgeRange; import com.gamsa.avatar.constant.Experienced; import com.gamsa.avatar.domain.Avatar; +import com.gamsa.user.domain.User; +import com.gamsa.user.entity.UserJpaEntity; import org.junit.jupiter.api.Test; public class AvatarJpaEntityTest { @Test void 도메인에서_JPA엔티티로() { //given + User user = User.builder() + .id(1L) + .nickname("nickname") + .build(); Avatar avatar = Avatar.builder() - .avatarId(1L) - .avatarLevel(1L) - .avatarExp(1L) - .nickname("닉네임") - .ageRange(AgeRange.ADULT) - .experienced(Experienced.NOVICE) - .build(); + .avatarId(1L) + .user(user) + .avatarLevel(1L) + .avatarExp(1L) + .nickname("닉네임") + .ageRange(AgeRange.ADULT) + .experienced(Experienced.NOVICE) + .build(); //when AvatarJpaEntity jpaEntity = AvatarJpaEntity.from(avatar); @@ -30,14 +37,19 @@ public class AvatarJpaEntityTest { @Test void JPA엔티티에서_도메인으로() { //given + UserJpaEntity userJpaEntity = UserJpaEntity.builder() + .id(1L) + .nickname("nickname") + .build(); AvatarJpaEntity avatarJpaEntity = AvatarJpaEntity.builder() - .avatarId(1L) - .avatarLevel(1L) - .avatarExp(1L) - .nickname("닉네임") - .ageRange(AgeRange.ADULT) - .experienced(Experienced.NOVICE) - .build(); + .avatarId(1L) + .user(userJpaEntity) + .avatarLevel(1L) + .avatarExp(1L) + .nickname("닉네임") + .ageRange(AgeRange.ADULT) + .experienced(Experienced.NOVICE) + .build(); //when Avatar avatar = avatarJpaEntity.toModel(); diff --git a/src/test/java/com/gamsa/avatar/service/AvatarServiceTest.java b/src/test/java/com/gamsa/avatar/service/AvatarServiceTest.java index 3a16636..80fd197 100644 --- a/src/test/java/com/gamsa/avatar/service/AvatarServiceTest.java +++ b/src/test/java/com/gamsa/avatar/service/AvatarServiceTest.java @@ -6,38 +6,53 @@ import com.gamsa.avatar.constant.AgeRange; import com.gamsa.avatar.constant.Experienced; import com.gamsa.avatar.dto.AvatarSaveRequest; -import com.gamsa.avatar.stub.StubAvatarRepository; +import com.gamsa.avatar.stub.StubEmptyAvatarRepository; +import com.gamsa.avatar.stub.StubExistsAvatarRepository; +import com.gamsa.user.domain.User; +import com.gamsa.user.stub.StubExistsUserRepository; import org.junit.jupiter.api.Test; public class AvatarServiceTest { - AvatarSaveRequest saveRequest = AvatarSaveRequest.builder() + + private final AvatarSaveRequest saveRequest = AvatarSaveRequest.builder() .nickname("닉네임") .ageRange(AgeRange.ADULT) .experienced(Experienced.EXPERT) .build(); + private final User user = User.builder() + .id(1L) + .nickname("nickname") + .build(); + @Test - void 새로운_유저_저장() { + void 새로운_아바타_저장() { //given - AvatarService avatarService = new AvatarService(new StubAvatarRepository()); + AvatarService avatarService = new AvatarService( + new StubEmptyAvatarRepository(), + new StubExistsUserRepository()); //then - assertDoesNotThrow(() -> avatarService.save(saveRequest)); + assertDoesNotThrow(() -> avatarService.save(saveRequest, user.getId())); } @Test - void 기존_유저_검색() { + void 기존_아바타_검색_성공() { //given - AvatarService avatarService = new AvatarService(new StubAvatarRepository()); + AvatarService avatarService = new AvatarService( + new StubExistsAvatarRepository(), + new StubExistsUserRepository()); //then - assertThat(avatarService.findById(1L)).isNotNull(); + assertThat(avatarService.findByUserId(1L)).isNotNull(); } @Test void 기존_유저_삭제() { //given - AvatarService avatarService = new AvatarService(new StubAvatarRepository()); + AvatarService avatarService = new AvatarService( + new StubExistsAvatarRepository(), + new StubExistsUserRepository()); //then assertDoesNotThrow(() -> avatarService.delete(1L)); @@ -48,9 +63,17 @@ public class AvatarServiceTest { @Test void 기존_유저_업데이트() { //given - AvatarService avatarService = new AvatarService(new StubAvatarRepository()); + AvatarService avatarService = new AvatarService( + new StubEmptyAvatarRepository(), + new StubExistsUserRepository()); + + AvatarSaveRequest updateRequest = AvatarSaveRequest.builder() + .nickname("새 닉네임") + .ageRange(AgeRange.ADULT) + .experienced(Experienced.EXPERT) + .build(); //then - assertDoesNotThrow(() -> avatarService.save(saveRequest)); + assertDoesNotThrow(() -> avatarService.save(updateRequest, user.getId())); } } diff --git a/src/test/java/com/gamsa/avatar/stub/StubAvatarRepository.java b/src/test/java/com/gamsa/avatar/stub/StubAvatarRepository.java deleted file mode 100644 index a24b653..0000000 --- a/src/test/java/com/gamsa/avatar/stub/StubAvatarRepository.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.gamsa.avatar.stub; - -import com.gamsa.avatar.constant.AgeRange; -import com.gamsa.avatar.constant.Experienced; -import com.gamsa.avatar.domain.Avatar; -import com.gamsa.avatar.repository.AvatarRepository; - -import java.time.LocalDateTime; -import java.util.Optional; - -public class StubAvatarRepository implements AvatarRepository { - private final Avatar avatar = Avatar.builder() - .avatarId(1L) - .avatarLevel(1L) - .avatarExp(1L) - .nickname("닉네임") - .ageRange(AgeRange.ADULT) - .experienced(Experienced.NOVICE) - .build(); - - @Override - public void save(Avatar avatar) {} - - @Override - public Optional findById(Long id) { - return Optional.of(avatar); - } - - @Override - public void deleteById(Long id) {} -} diff --git a/src/test/java/com/gamsa/avatar/stub/StubEmptyAvatarRepository.java b/src/test/java/com/gamsa/avatar/stub/StubEmptyAvatarRepository.java new file mode 100644 index 0000000..c60fc46 --- /dev/null +++ b/src/test/java/com/gamsa/avatar/stub/StubEmptyAvatarRepository.java @@ -0,0 +1,33 @@ +package com.gamsa.avatar.stub; + +import com.gamsa.avatar.domain.Avatar; +import com.gamsa.avatar.repository.AvatarRepository; +import java.util.Optional; + +public class StubEmptyAvatarRepository implements AvatarRepository { + + @Override + public void save(Avatar avatar) { + // do nothing + } + + @Override + public Optional findById(Long id) { + return Optional.empty(); + } + + @Override + public Optional findByUserId(Long userId) { + return Optional.empty(); + } + + @Override + public Optional findByNickname(String nickname) { + return Optional.empty(); + } + + @Override + public void deleteById(Long id) { + // do nothing + } +} diff --git a/src/test/java/com/gamsa/avatar/stub/StubExistsAvatarRepository.java b/src/test/java/com/gamsa/avatar/stub/StubExistsAvatarRepository.java new file mode 100644 index 0000000..1a0051e --- /dev/null +++ b/src/test/java/com/gamsa/avatar/stub/StubExistsAvatarRepository.java @@ -0,0 +1,47 @@ +package com.gamsa.avatar.stub; + +import com.gamsa.avatar.constant.AgeRange; +import com.gamsa.avatar.constant.Experienced; +import com.gamsa.avatar.domain.Avatar; +import com.gamsa.avatar.repository.AvatarRepository; +import com.gamsa.user.entity.UserJpaEntity; +import java.util.Optional; + +public class StubExistsAvatarRepository implements AvatarRepository { + + private final UserJpaEntity user = UserJpaEntity.builder() + .id(1L) + .nickname("nickname") + .build(); + + private final Avatar avatar = Avatar.builder() + .avatarId(1L) + .user(user.toModel()) + .avatarLevel(1L) + .avatarExp(1L) + .nickname("닉네임") + .ageRange(AgeRange.ADULT) + .experienced(Experienced.NOVICE) + .build(); + + @Override + public void save(Avatar avatar) {} + + @Override + public Optional findById(Long id) { + return Optional.of(avatar); + } + + @Override + public Optional findByUserId(Long userId) { + return Optional.of(avatar); + } + + @Override + public Optional findByNickname(String nickname) { + return Optional.of(avatar); + } + + @Override + public void deleteById(Long id) {} +} diff --git a/src/test/java/com/gamsa/history/entity/HistoryJpaEntityTest.java b/src/test/java/com/gamsa/history/entity/HistoryJpaEntityTest.java index 26f0594..6b8b95b 100644 --- a/src/test/java/com/gamsa/history/entity/HistoryJpaEntityTest.java +++ b/src/test/java/com/gamsa/history/entity/HistoryJpaEntityTest.java @@ -1,5 +1,7 @@ package com.gamsa.history.entity; +import static org.assertj.core.api.Assertions.assertThat; + import com.gamsa.activity.constant.Category; import com.gamsa.activity.domain.Activity; import com.gamsa.activity.domain.District; @@ -12,78 +14,82 @@ import com.gamsa.common.config.TestConfig; import com.gamsa.history.constant.ActivityStatus; import com.gamsa.history.domain.History; -import org.junit.jupiter.api.Test; -import org.springframework.context.annotation.Import; - +import com.gamsa.user.domain.User; import java.math.BigDecimal; import java.time.LocalDateTime; - -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Import; @Import(TestConfig.class) public class HistoryJpaEntityTest { // given - District district = District.builder() - .sidoCode(1234) - .sidoGunguCode(8888) - .sidoName("서울특별시") - .gunguName("강남구") - .sido(false) - .build(); + private final District district = District.builder() + .sidoCode(1234) + .sidoGunguCode(8888) + .sidoName("서울특별시") + .gunguName("강남구") + .sido(false) + .build(); - Institute institute = Institute.builder() - .instituteId(1L) - .name("도서관") - .location("서울시") - .latitude(new BigDecimal("123456789.12341234")) - .longitude(new BigDecimal("987654321.43214321")) - .sidoGungu(district) - .phone("010xxxxxxxx") - .build(); - - Activity activity = Activity.builder() - .actId(1L) - .actTitle("어린이놀이안전관리 및 놀잇감 청결유지 및 정리") - .actLocation("아이사랑꿈터 서구 5호점") - .description("봉사 내용") - .noticeStartDate(LocalDateTime.of(2024, 9, 10, 0, 0)) - .noticeEndDate(LocalDateTime.of(2024, 12, 7, 0, 0)) - .actStartDate(LocalDateTime.of(2024, 9, 10, 0, 0)) - .actEndDate(LocalDateTime.of(2024, 12, 7, 0, 0)) - .actStartTime(13) - .actEndTime(18) - .recruitTotalNum(1) - .adultPossible(true) - .teenPossible(false) - .groupPossible(false) - .actWeek(0111110) - .actManager("윤순영") - .actPhone("032-577-3026") - .url("https://...") - .category(Category.OTHER_ACTIVITIES) - .institute(institute) - .sidoGungu(district) - .build(); + private final Institute institute = Institute.builder() + .instituteId(1L) + .name("도서관") + .location("서울시") + .latitude(new BigDecimal("123456789.12341234")) + .longitude(new BigDecimal("987654321.43214321")) + .sidoGungu(district) + .phone("010xxxxxxxx") + .build(); - Avatar avatar = Avatar.builder() - .avatarId(1L) - .avatarLevel(1L) - .avatarExp(1L) - .nickname("닉네임") - .ageRange(AgeRange.ADULT) - .experienced(Experienced.NOVICE) - .build(); + private final Activity activity = Activity.builder() + .actId(1L) + .actTitle("어린이놀이안전관리 및 놀잇감 청결유지 및 정리") + .actLocation("아이사랑꿈터 서구 5호점") + .description("봉사 내용") + .noticeStartDate(LocalDateTime.of(2024, 9, 10, 0, 0)) + .noticeEndDate(LocalDateTime.of(2024, 12, 7, 0, 0)) + .actStartDate(LocalDateTime.of(2024, 9, 10, 0, 0)) + .actEndDate(LocalDateTime.of(2024, 12, 7, 0, 0)) + .actStartTime(13) + .actEndTime(18) + .recruitTotalNum(1) + .adultPossible(true) + .teenPossible(false) + .groupPossible(false) + .actWeek(0111110) + .actManager("윤순영") + .actPhone("032-577-3026") + .url("https://...") + .category(Category.OTHER_ACTIVITIES) + .institute(institute) + .sidoGungu(district) + .build(); + + private final User user = User.builder() + .id(1L) + .nickname("nickname") + .build(); + + private final Avatar avatar = Avatar.builder() + .avatarId(1L) + .user(user) + .avatarLevel(1L) + .avatarExp(1L) + .nickname("닉네임") + .ageRange(AgeRange.ADULT) + .experienced(Experienced.NOVICE) + .build(); @Test void 도메인에서_엔티티로() { // given History history = History.builder() - .historyId(1L) - .activity(activity) - .avatar(avatar) - .activityStatus(ActivityStatus.APPLIED) - .reviewed(false) - .build(); + .historyId(1L) + .activity(activity) + .avatar(avatar) + .activityStatus(ActivityStatus.APPLIED) + .reviewed(false) + .build(); //when HistoryJpaEntity historyJpaEntity = HistoryJpaEntity.from(history); diff --git a/src/test/java/com/gamsa/history/repository/HistoryJpaRepositoryTest.java b/src/test/java/com/gamsa/history/repository/HistoryJpaRepositoryTest.java index 9e3c050..23a5655 100644 --- a/src/test/java/com/gamsa/history/repository/HistoryJpaRepositoryTest.java +++ b/src/test/java/com/gamsa/history/repository/HistoryJpaRepositoryTest.java @@ -1,5 +1,7 @@ package com.gamsa.history.repository; +import static org.assertj.core.api.Assertions.assertThat; + import com.gamsa.activity.constant.Category; import com.gamsa.activity.domain.Activity; import com.gamsa.activity.domain.District; @@ -12,16 +14,14 @@ import com.gamsa.common.config.TestConfig; import com.gamsa.history.constant.ActivityStatus; import com.gamsa.history.entity.HistoryJpaEntity; +import com.gamsa.user.domain.User; +import java.math.BigDecimal; +import java.time.LocalDateTime; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.context.annotation.Import; -import java.math.BigDecimal; -import java.time.LocalDateTime; - -import static org.assertj.core.api.Assertions.assertThat; - @DataJpaTest @Import(TestConfig.class) public class HistoryJpaRepositoryTest { @@ -29,63 +29,69 @@ public class HistoryJpaRepositoryTest { private HistoryJpaRepository historyJpaRepository; District district = District.builder() - .sidoCode(1234) - .sidoGunguCode(8888) - .sidoName("서울특별시") - .gunguName("강남구") - .sido(false) - .build(); + .sidoCode(1234) + .sidoGunguCode(8888) + .sidoName("서울특별시") + .gunguName("강남구") + .sido(false) + .build(); Institute institute = Institute.builder() - .instituteId(1L) - .name("도서관") - .location("서울시") - .latitude(new BigDecimal("123456789.12341234")) - .longitude(new BigDecimal("987654321.43214321")) - .sidoGungu(district) - .phone("010xxxxxxxx") - .build(); + .instituteId(1L) + .name("도서관") + .location("서울시") + .latitude(new BigDecimal("123456789.12341234")) + .longitude(new BigDecimal("987654321.43214321")) + .sidoGungu(district) + .phone("010xxxxxxxx") + .build(); Activity activity = Activity.builder() - .actId(1L) - .actTitle("어린이놀이안전관리 및 놀잇감 청결유지 및 정리") - .actLocation("아이사랑꿈터 서구 5호점") - .description("봉사 내용") - .noticeStartDate(LocalDateTime.of(2024, 9, 10, 0, 0)) - .noticeEndDate(LocalDateTime.of(2024, 12, 7, 0, 0)) - .actStartDate(LocalDateTime.of(2024, 9, 10, 0, 0)) - .actEndDate(LocalDateTime.of(2024, 12, 7, 0, 0)) - .actStartTime(13) - .actEndTime(18) - .recruitTotalNum(1) - .adultPossible(true) - .teenPossible(false) - .groupPossible(false) - .actWeek(0111110) - .actManager("윤순영") - .actPhone("032-577-3026") - .url("https://...") - .category(Category.OTHER_ACTIVITIES) - .institute(institute) - .sidoGungu(district) - .build(); + .actId(1L) + .actTitle("어린이놀이안전관리 및 놀잇감 청결유지 및 정리") + .actLocation("아이사랑꿈터 서구 5호점") + .description("봉사 내용") + .noticeStartDate(LocalDateTime.of(2024, 9, 10, 0, 0)) + .noticeEndDate(LocalDateTime.of(2024, 12, 7, 0, 0)) + .actStartDate(LocalDateTime.of(2024, 9, 10, 0, 0)) + .actEndDate(LocalDateTime.of(2024, 12, 7, 0, 0)) + .actStartTime(13) + .actEndTime(18) + .recruitTotalNum(1) + .adultPossible(true) + .teenPossible(false) + .groupPossible(false) + .actWeek(0111110) + .actManager("윤순영") + .actPhone("032-577-3026") + .url("https://...") + .category(Category.OTHER_ACTIVITIES) + .institute(institute) + .sidoGungu(district) + .build(); + + private final User user = User.builder() + .id(1L) + .nickname("nickname") + .build(); private final Avatar avatar = Avatar.builder() - .avatarId(1L) - .avatarLevel(1L) - .avatarExp(1L) - .nickname("닉네임") - .ageRange(AgeRange.ADULT) - .experienced(Experienced.NOVICE) - .build(); + .avatarId(1L) + .user(user) + .avatarLevel(1L) + .avatarExp(1L) + .nickname("닉네임") + .ageRange(AgeRange.ADULT) + .experienced(Experienced.NOVICE) + .build(); private final HistoryJpaEntity historyJpaEntity = HistoryJpaEntity.builder() - .historyId(1L) - .activity(ActivityJpaEntity.from(activity)) - .avatar(AvatarJpaEntity.from(avatar)) - .activityStatus(ActivityStatus.APPLIED) - .reviewed(false) - .build(); + .historyId(1L) + .activity(ActivityJpaEntity.from(activity)) + .avatar(AvatarJpaEntity.from(avatar)) + .activityStatus(ActivityStatus.APPLIED) + .reviewed(false) + .build(); @Test diff --git a/src/test/java/com/gamsa/history/service/HitstoryServiceTest.java b/src/test/java/com/gamsa/history/service/HitstoryServiceTest.java index 9c63fb3..9344843 100644 --- a/src/test/java/com/gamsa/history/service/HitstoryServiceTest.java +++ b/src/test/java/com/gamsa/history/service/HitstoryServiceTest.java @@ -1,36 +1,37 @@ package com.gamsa.history.service; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + import com.gamsa.activity.stub.StubExistsActivityRepository; -import com.gamsa.avatar.stub.StubAvatarRepository; +import com.gamsa.avatar.stub.StubExistsAvatarRepository; import com.gamsa.history.dto.HistorySaveRequest; import com.gamsa.history.stub.StubHistoryRepository; import org.junit.jupiter.api.Test; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; - public class HitstoryServiceTest { HistorySaveRequest historySaveRequest = HistorySaveRequest.builder() .actId(1L) - .avatarId(1L) .build(); @Test void 새로운_기록_저장() { //given - HistoryService historyService = new HistoryService(new StubHistoryRepository(), new StubAvatarRepository(), new StubExistsActivityRepository()); + HistoryService historyService = new HistoryService(new StubHistoryRepository(), + new StubExistsAvatarRepository(), new StubExistsActivityRepository()); //when & then - assertDoesNotThrow(() -> historyService.save(historySaveRequest)); + assertDoesNotThrow(() -> historyService.save(historySaveRequest, 1L)); } @Test void 유저_기록_찾기() { //given - HistoryService historyService = new HistoryService(new StubHistoryRepository(), new StubAvatarRepository(), new StubExistsActivityRepository()); + HistoryService historyService = new HistoryService(new StubHistoryRepository(), + new StubExistsAvatarRepository(), new StubExistsActivityRepository()); //when & then Pageable pageable = PageRequest.of(0, 10); @@ -40,7 +41,8 @@ public class HitstoryServiceTest { @Test void 기록_삭제() { //given - HistoryService historyService = new HistoryService(new StubHistoryRepository(), new StubAvatarRepository(), new StubExistsActivityRepository()); + HistoryService historyService = new HistoryService(new StubHistoryRepository(), + new StubExistsAvatarRepository(), new StubExistsActivityRepository()); //when & then assertDoesNotThrow(() -> historyService.delete(1L)); @@ -49,7 +51,8 @@ public class HitstoryServiceTest { @Test void 리뷰_상태_업데이트() { //given - HistoryService historyService = new HistoryService(new StubHistoryRepository(), new StubAvatarRepository(), new StubExistsActivityRepository()); + HistoryService historyService = new HistoryService(new StubHistoryRepository(), + new StubExistsAvatarRepository(), new StubExistsActivityRepository()); //when & then assertDoesNotThrow(() -> historyService.updateReviewed(1L, true)); diff --git a/src/test/java/com/gamsa/user/service/UserServiceTest.java b/src/test/java/com/gamsa/user/service/UserServiceTest.java new file mode 100644 index 0000000..7ca6cae --- /dev/null +++ b/src/test/java/com/gamsa/user/service/UserServiceTest.java @@ -0,0 +1,58 @@ +package com.gamsa.user.service; + +import com.gamsa.avatar.stub.StubEmptyAvatarRepository; +import com.gamsa.avatar.stub.StubExistsAvatarRepository; +import com.gamsa.common.jwt.JwtUtil; +import com.gamsa.user.dto.KakaoLoginResponse; +import com.gamsa.user.dto.KakaoProperties; +import com.gamsa.user.stub.DummyKakaoAccessTokenRepository; +import com.gamsa.user.stub.DummyKakaoLogin; +import com.gamsa.user.stub.StubExistsUserRepository; +import java.util.Map; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class UserServiceTest { + + private final String dummySecretKey = "sghdfdfsfskwpqdnblkdjofknvboiwrbnowagibsdhgalkshgowaweqnbzoiwnyzbvwow"; + + @Test + @DisplayName("아직 아바타가 없는 유저 카카오 로그인 성공") + void avatarEmptyUserKakaoLogin() { + // given + final UserService userService = new UserService( + new JwtUtil(dummySecretKey), + new DummyKakaoLogin(new KakaoProperties("dummyUrl")), + new StubExistsUserRepository(), + new StubEmptyAvatarRepository(), // 아바타 X + new DummyKakaoAccessTokenRepository()); + // when + Map result = userService.userKakaoLogin("dummyToken"); + + String token = (String) result.get("token"); + KakaoLoginResponse response = (KakaoLoginResponse) result.get("body"); + // then + Assertions.assertThat(token).isNotNull(); + Assertions.assertThat(response.getAvatar()).isNull(); + } + + @Test + @DisplayName("아바타가 존재하는 유저 카카오 로그인 성공") + void avatarExistsUserKakaoLogin() { + // given + final UserService userService = new UserService( + new JwtUtil(dummySecretKey), + new DummyKakaoLogin(new KakaoProperties("dummyUrl")), + new StubExistsUserRepository(), + new StubExistsAvatarRepository(), // 아바타 O + new DummyKakaoAccessTokenRepository()); + // when + Map result = userService.userKakaoLogin("dummyToken"); + String token = (String) result.get("token"); + KakaoLoginResponse response = (KakaoLoginResponse) result.get("body"); + // then + Assertions.assertThat(token).isNotNull(); + Assertions.assertThat(response.getAvatar()).isNotNull(); + } +} \ No newline at end of file diff --git a/src/test/java/com/gamsa/user/stub/DummyKakaoAccessTokenRepository.java b/src/test/java/com/gamsa/user/stub/DummyKakaoAccessTokenRepository.java new file mode 100644 index 0000000..a521ebc --- /dev/null +++ b/src/test/java/com/gamsa/user/stub/DummyKakaoAccessTokenRepository.java @@ -0,0 +1,17 @@ +package com.gamsa.user.stub; + +import com.gamsa.user.repository.KakaoAccessTokenRepository; +import java.util.Optional; + +public class DummyKakaoAccessTokenRepository extends KakaoAccessTokenRepository { + + @Override + public Optional findById(Long id) { + return Optional.empty(); + } + + @Override + public void save(Long id, String token) { + // do nothing + } +} diff --git a/src/test/java/com/gamsa/user/stub/DummyKakaoLogin.java b/src/test/java/com/gamsa/user/stub/DummyKakaoLogin.java new file mode 100644 index 0000000..8d5b1c9 --- /dev/null +++ b/src/test/java/com/gamsa/user/stub/DummyKakaoLogin.java @@ -0,0 +1,17 @@ +package com.gamsa.user.stub; + +import com.gamsa.user.domain.KakaoLogin; +import com.gamsa.user.dto.KakaoProperties; +import com.gamsa.user.dto.KakaoUserInfoResponse; + +public class DummyKakaoLogin extends KakaoLogin { + + public DummyKakaoLogin(KakaoProperties kakaoProperties) { + super(kakaoProperties); + } + + @Override + public KakaoUserInfoResponse getUserInfo(String token) { + return new KakaoUserInfoResponse(); + } +} diff --git a/src/test/java/com/gamsa/user/stub/StubExistsUserRepository.java b/src/test/java/com/gamsa/user/stub/StubExistsUserRepository.java new file mode 100644 index 0000000..2745ade --- /dev/null +++ b/src/test/java/com/gamsa/user/stub/StubExistsUserRepository.java @@ -0,0 +1,23 @@ +package com.gamsa.user.stub; + +import com.gamsa.user.domain.User; +import com.gamsa.user.repository.UserRepository; +import java.util.Optional; + +public class StubExistsUserRepository implements UserRepository { + + private final User user = User.builder() + .id(1L) + .nickname("nickname") + .build(); + + @Override + public void save(User user) { + // do nothing + } + + @Override + public Optional findById(Long userId) { + return Optional.of(user); + } +} From 674ff15d8ed3f6f3986f106743cc6c348317e5c1 Mon Sep 17 00:00:00 2001 From: 5win <94297900+5win@users.noreply.github.com> Date: Wed, 30 Oct 2024 20:09:59 +0900 Subject: [PATCH 5/5] =?UTF-8?q?feat:=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=20=EB=AA=A9=EB=A1=9D=20=EB=B0=98=ED=99=98=20API=20(#4?= =?UTF-8?q?9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 카테고리 목록 반환 API 생성 - 클라이언트와 주고받는 카테고리명 한글로 수정 --- .../com/gamsa/activity/constant/Category.java | 26 +++++++++++++++++++ .../controller/ActivityController.java | 4 +-- .../controller/CategoryController.java | 17 ++++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/gamsa/activity/controller/CategoryController.java diff --git a/src/main/java/com/gamsa/activity/constant/Category.java b/src/main/java/com/gamsa/activity/constant/Category.java index 561983f..848c0cf 100644 --- a/src/main/java/com/gamsa/activity/constant/Category.java +++ b/src/main/java/com/gamsa/activity/constant/Category.java @@ -1,5 +1,7 @@ package com.gamsa.activity.constant; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -15,4 +17,28 @@ public enum Category { OTHER_ACTIVITIES("기타 활동"); private final String name; + + @JsonCreator + public static Category fromValues(String value) { + for (Category category : Category.values()) { + if (category.getName().equals(value)) { + return category; + } + } + throw new IllegalArgumentException("Unknown value: " + value); + } + + public static Category fromValuesForSlice(String value) { + for (Category category : Category.values()) { + if (category.getName().equals(value)) { + return category; + } + } + return null; // QueryDSL 에서는 null일 경우 필터링에서 제외하므로 null 반환 허용 + } + + @JsonValue + public String toValue() { + return this.name; + } } diff --git a/src/main/java/com/gamsa/activity/controller/ActivityController.java b/src/main/java/com/gamsa/activity/controller/ActivityController.java index 6da07fc..13e3fa5 100644 --- a/src/main/java/com/gamsa/activity/controller/ActivityController.java +++ b/src/main/java/com/gamsa/activity/controller/ActivityController.java @@ -28,7 +28,7 @@ public class ActivityController { @GetMapping public Slice findSlice( - @RequestParam(required = false) Category category, + @RequestParam(required = false) String category, @RequestParam(required = false) Integer sidoGunguCode, @RequestParam(required = false) Integer sidoCode, @RequestParam(defaultValue = "false") boolean teenPossibleOnly, @@ -36,7 +36,7 @@ public Slice findSlice( Pageable pageable) { ActivityFilterRequest request = ActivityFilterRequest.builder() - .category(category) + .category(Category.fromValuesForSlice(category)) .sidoGunguCode(sidoGunguCode) .sidoCode(sidoCode) .teenPossibleOnly(teenPossibleOnly) diff --git a/src/main/java/com/gamsa/activity/controller/CategoryController.java b/src/main/java/com/gamsa/activity/controller/CategoryController.java new file mode 100644 index 0000000..0a36aa5 --- /dev/null +++ b/src/main/java/com/gamsa/activity/controller/CategoryController.java @@ -0,0 +1,17 @@ +package com.gamsa.activity.controller; + +import com.gamsa.activity.constant.Category; +import java.util.List; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/v1/activities/categories") +public class CategoryController { + + @GetMapping + public List findAllCategories() { + return List.of(Category.values()); + } +}