From 5e4366e8f7fb82678264d22a2f45769f21f4c549 Mon Sep 17 00:00:00 2001 From: 5win <94297900+5win@users.noreply.github.com> Date: Mon, 14 Oct 2024 09:46:02 +0900 Subject: [PATCH 1/2] =?UTF-8?q?review:=20=EC=84=B8=20=EB=B2=88=EC=A7=B8=20?= =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20PR=20(#34)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 활동 조회 기능 완성 및 단위테스트 추가 (#15) * [#6] feat: JPA 레포지토리 연결 - save, findAll, findById 구현 * [#6] feat: 활동 리스트, 상세정보 응답 DTO 구현 * [#6] feat: JPA 엔티티 애노테이션 수정 - GeneratedValue 삭제 : 받아온 데이터의 id를 그대로 사용하기 위함. - String 컬럼에 length 제한 적용 - LocalDateTime 컬럼에 @Temporal 적용 * [#6] feat: 예외 메시지 enum 생성 및 적용 - 기존 string 하드코딩에서 enum으로 변경 * [#6] feat: Spring security 우회 설정 - 컨트롤러 테스트를 위해 모든 경로 permitAll로 지정하여 우회함. * [#6] fix: 컨트롤러 URI 변경 - program -> activity로 명명 변경에서 놓친 부분 수정 * [#6] fix: 활동 저장 오류 수정 - 서비스의 잘못된 로직 수정 - JpaEntity에 기본 생성자 추가 * [#6] refact: DTO 변수를 final로 변경 - 응답 dto의 변수를 final로 변경 - 저장 요청 dto에서 @Data 에서 @Getter로 변경 * [#6] feat: Activity 예외 수정 및 핸들러 추가 - common.constant.ErrorCode 추가 - common.exception.GlobalExceptionHandler 추가 - activity.exception.ActivityCustomException 수정 * [#6] feat: Activity 관련 클래스 변수 추가 및 삭제 - onlinePossible 변수 삭제 - actStartTime, actEndTime 변수 추가 * [#6] feat: 활동 제목 변수 추가 - actTitle 추가 * [#6] test: Activity 단위 테스트 추가 - 아래 클래스에 대한 단위 테스트를 추가함. - JpaEntity - Service - JpaRepository * weekly: 4주차 작업 병합 (#24) * docs: Update issue templates 이슈 템플릿 생성 * docs: Create pull_request_template.md - PR 템플릿 작성 * init: 프로젝트 생성 [#2] init: 프로젝트 생성 및 의존성 추가 (#3) (#4) * weekly: 3주차 작업 내용 머지 (#8) * [#2] init: 프로젝트 생성 및 의존성 추가 (#3) - 프로젝트 생성 - 의존성 추가 - H2 연결 및 테스트 * [#6] feat: 봉사활동 조회 기능 추가 (#7) - 아직 모두 구현하지 못했음 * fix: PR 템플릿 위치 수정 (#14) * [#5] init: PR 템플릿 위치 변경 - 템플릿 파일의 위치를 변경함 * rename: 기본 테스트 클래스 이름 변경 * feat: 활동 조회 기능 완성 및 단위테스트 추가 (#15) * [#6] feat: JPA 레포지토리 연결 - save, findAll, findById 구현 * [#6] feat: 활동 리스트, 상세정보 응답 DTO 구현 * [#6] feat: JPA 엔티티 애노테이션 수정 - GeneratedValue 삭제 : 받아온 데이터의 id를 그대로 사용하기 위함. - String 컬럼에 length 제한 적용 - LocalDateTime 컬럼에 @Temporal 적용 * [#6] feat: 예외 메시지 enum 생성 및 적용 - 기존 string 하드코딩에서 enum으로 변경 * [#6] feat: Spring security 우회 설정 - 컨트롤러 테스트를 위해 모든 경로 permitAll로 지정하여 우회함. * [#6] fix: 컨트롤러 URI 변경 - program -> activity로 명명 변경에서 놓친 부분 수정 * [#6] fix: 활동 저장 오류 수정 - 서비스의 잘못된 로직 수정 - JpaEntity에 기본 생성자 추가 * [#6] refact: DTO 변수를 final로 변경 - 응답 dto의 변수를 final로 변경 - 저장 요청 dto에서 @Data 에서 @Getter로 변경 * [#6] feat: Activity 예외 수정 및 핸들러 추가 - common.constant.ErrorCode 추가 - common.exception.GlobalExceptionHandler 추가 - activity.exception.ActivityCustomException 수정 * [#6] feat: Activity 관련 클래스 변수 추가 및 삭제 - onlinePossible 변수 삭제 - actStartTime, actEndTime 변수 추가 * [#6] feat: 활동 제목 변수 추가 - actTitle 추가 * [#6] test: Activity 단위 테스트 추가 - 아래 클래스에 대한 단위 테스트를 추가함. - JpaEntity - Service - JpaRepository * feat: 봉사활동 조회 무한스크롤 기능 및 단위테스트 구현 (#20) * [#18] feat: 활동 조회 id 기준 QueryDSL 무한스크롤 구현 - QueryDSL 의존성 추가 및 common.config.QueryDslConfig 파일 추가 - Slice 객체 반환하도록 변경 - CustomRepository 만드는 패턴을 통해 QueryDSL과 Spring data JPA 결합 * [#18] rename: 활동 테이블 PK명 id에서 actId로 변경 * [#18] test: 무한스크롤 단위 테스트 추가 및 수정 * [#18] feat: 활동 정렬 기준 상수클래스 생성 - 정렬 기준 상수클래스 생성하여 하드코딩 제거 - common.constant를 activity 패키지로 이동 * [#18] fix: ActivityCustomRepositoryImpl에서의 도메인 엔티티 종속성 제거 - Activity로의 종속성을 제거하고 ActivityJpaEntity만 종속하도록 수정함. * [#18] rename: 바꾸지 않은 findAll 명명을 모두 findSlice로 변경 * [#18] rename: ActivityCustomException을 ActivityException으로 변경 * [#18] refact: OrderSpecifier 정렬 기준 생성 메서드 리팩토링 불필요한 클래스 삭제 - common.utils.QueryDslUtil - activity.constant.ActivitySortType * feat: 봉사 분야 기능, 봉사 조회 필터링 기능 추가 (#23) * [#22] refact: lombok 사용하여 생성자 제거 * [#22] feat: H2콘솔 위한 X-Frame-Options 비활성화 * [#22] feat: 봉사 분야 컬럼 추가 - enum 형태로 관리 - CategoryConverter를 통해 DB에는 한글로 저장. * [#22] feat: 봉사활동 조회 필터링 기능 추가 - filter DTO를 추가하여 필터링 정보 전달. - 파라미터 변경에 따른 클래스, 테스트코드 수정 - ActivityFilterBuilder: 필터링 정보 조합 BooleanBuilder 반환 * [#22] fix: 마감 전 활동만 필터링 하도록 조건 수정 - 마감된 활동만 필터링에서 마감 전 활동 필터링으로 변경 * [#22] test: 활동 조회 정렬, 필터링 기능 단위테스트 - 마감 날짜 오름차순 정렬 조회 - 마감된 공고 중, 마감 날짜 가까운 순 정렬 - 카테고리 필터링 - 청소년 가능 활동만 필터링 - 마감되지 않은 활동만 필터링 * [#22] refact: 기존 활동 조회 단위 테스트 리팩토링 * feat: 아바타 관련 기능 구현 * feat: 아바타 관련 기능 구현 신규 아바타 저장 기존 아바타 호출 기존 아바타 수정 * test: 아바타 관련 단위 테스트 * fix: 아바타 기능 수정 --------- Co-authored-by: Awhn <69659322+Awhn@users.noreply.github.com> * feat: 봉사 기관 관련 기능 및 단위테스트 추가 (#29) * [#21] fix: AvatarJpaRepositoryTest에 @Import 추가 * [#21] refact: Activity 도메인 lombok 수정 - AllArgsConstructor 제거 * [#21] feat: 봉사기관 테이블 생성 및 조회 - 봉사활동:봉사기관 = N:1 (ManyToOne) - ActivityDetail 응답에 기관 정보 포함 * [#21] test: 봉사기관 관련 단위테스트 * [#21] fix: 리뷰 후 1차 수정 사항 - InstituteDetailResponse로 명명 수정 - 위도, 경도 BigDecimal로 변경 * feat: 시도, 시군구 API 및 시도, 시군구 필터링 기능 추가 (#31) * [#30] feat: 시도, 군구 CR API 구현 - 시도, 군구 영속성 엔티티 생성 - create, read API 구현 - 시도 조회, 군구 조회 별도로 생성 * [#30] fix: 변수명, 제약조건 수정 - gunguCode 에서 sidoGunguCode로 변경 - nullable 제약조건 추가 * [#30] feat: 봉사활동과 시도군구 연관관계 매핑 * [#30] feat: 시도, 시군구 필터링 조회 기능 추가 - 봉사활동 조회 시, 시도, 시군구 필터링 기능 추가 - sidoCode, sidoGunguCode로 queryDsl BooleanBuilder 추가. --------- Co-authored-by: Awhn <69659322+Awhn@users.noreply.github.com> --- .../activity/constant/ActivityErrorCode.java | 6 +- .../controller/ActivityController.java | 12 ++- .../controller/DistrictController.java | 38 ++++++++++ .../controller/InstituteController.java | 25 ++++++ .../com/gamsa/activity/domain/Activity.java | 3 +- .../com/gamsa/activity/domain/District.java | 15 ++++ .../com/gamsa/activity/domain/Institute.java | 18 +++++ .../activity/dto/ActivityDetailResponse.java | 4 + .../activity/dto/ActivityFilterRequest.java | 8 +- .../activity/dto/ActivitySaveRequest.java | 8 +- .../activity/dto/DistrictDetailResponse.java | 28 +++++++ .../activity/dto/DistrictFindAllResponse.java | 28 +++++++ .../activity/dto/DistrictSaveRequest.java | 28 +++++++ .../activity/dto/InstituteDetailResponse.java | 31 ++++++++ .../activity/dto/InstituteSaveRequest.java | 31 ++++++++ .../activity/entity/ActivityJpaEntity.java | 15 ++++ .../activity/entity/DistrictJpaEntity.java | 58 ++++++++++++++ .../activity/entity/InstituteJpaEntity.java | 76 +++++++++++++++++++ .../repository/ActivityFilterBuilder.java | 14 ++++ .../repository/DistrictJpaRepository.java | 13 ++++ .../repository/DistrictRepository.java | 14 ++++ .../repository/DistrictRepositoryImpl.java | 33 ++++++++ .../repository/InstituteJpaRepository.java | 10 +++ .../repository/InstituteRepository.java | 13 ++++ .../repository/InstituteRepositoryImpl.java | 31 ++++++++ .../activity/service/ActivityService.java | 17 ++++- .../activity/service/DistrictService.java | 47 ++++++++++++ .../activity/service/InstituteService.java | 30 ++++++++ .../entity/ActivityJpaEntityTest.java | 45 ++++++++++- .../entity/InstituteJpaEntityTest.java | 68 +++++++++++++++++ .../repository/ActivityJpaRepositoryTest.java | 8 +- .../InstituteJpaRepositoryTest.java | 39 ++++++++++ .../activity/service/ActivityServiceTest.java | 41 ++++++++-- .../service/InstituteServiceTest.java | 56 ++++++++++++++ .../stub/StubEmptyInstituteRepository.java | 23 ++++++ .../stub/StubExistsActivityRepository.java | 22 ++++++ .../stub/StubExistsDistrictRepository.java | 32 ++++++++ .../stub/StubExistsInstituteRepository.java | 33 ++++++++ .../repository/AvatarJpaRepositoryTest.java | 3 +- 39 files changed, 1005 insertions(+), 19 deletions(-) create mode 100644 src/main/java/com/gamsa/activity/controller/DistrictController.java create mode 100644 src/main/java/com/gamsa/activity/controller/InstituteController.java create mode 100644 src/main/java/com/gamsa/activity/domain/District.java create mode 100644 src/main/java/com/gamsa/activity/domain/Institute.java create mode 100644 src/main/java/com/gamsa/activity/dto/DistrictDetailResponse.java create mode 100644 src/main/java/com/gamsa/activity/dto/DistrictFindAllResponse.java create mode 100644 src/main/java/com/gamsa/activity/dto/DistrictSaveRequest.java create mode 100644 src/main/java/com/gamsa/activity/dto/InstituteDetailResponse.java create mode 100644 src/main/java/com/gamsa/activity/dto/InstituteSaveRequest.java create mode 100644 src/main/java/com/gamsa/activity/entity/DistrictJpaEntity.java create mode 100644 src/main/java/com/gamsa/activity/entity/InstituteJpaEntity.java create mode 100644 src/main/java/com/gamsa/activity/repository/DistrictJpaRepository.java create mode 100644 src/main/java/com/gamsa/activity/repository/DistrictRepository.java create mode 100644 src/main/java/com/gamsa/activity/repository/DistrictRepositoryImpl.java create mode 100644 src/main/java/com/gamsa/activity/repository/InstituteJpaRepository.java create mode 100644 src/main/java/com/gamsa/activity/repository/InstituteRepository.java create mode 100644 src/main/java/com/gamsa/activity/repository/InstituteRepositoryImpl.java create mode 100644 src/main/java/com/gamsa/activity/service/DistrictService.java create mode 100644 src/main/java/com/gamsa/activity/service/InstituteService.java create mode 100644 src/test/java/com/gamsa/activity/entity/InstituteJpaEntityTest.java create mode 100644 src/test/java/com/gamsa/activity/repository/InstituteJpaRepositoryTest.java create mode 100644 src/test/java/com/gamsa/activity/service/InstituteServiceTest.java create mode 100644 src/test/java/com/gamsa/activity/stub/StubEmptyInstituteRepository.java create mode 100644 src/test/java/com/gamsa/activity/stub/StubExistsDistrictRepository.java create mode 100644 src/test/java/com/gamsa/activity/stub/StubExistsInstituteRepository.java diff --git a/src/main/java/com/gamsa/activity/constant/ActivityErrorCode.java b/src/main/java/com/gamsa/activity/constant/ActivityErrorCode.java index 163c36f..9930e68 100644 --- a/src/main/java/com/gamsa/activity/constant/ActivityErrorCode.java +++ b/src/main/java/com/gamsa/activity/constant/ActivityErrorCode.java @@ -9,9 +9,13 @@ public enum ActivityErrorCode { // 404 Not Found : 존재하지 않는 리소스 접근 ACTIVITY_NOT_EXISTS(404, "존재하지 않는 활동입니다."), + INSTITUTE_NOT_EXISTS(404, "존재하지 않는 기관입니다."), + DISTRICT_NOT_EXISTS(404, "존재하지 않는 지역입니다."), // 409 Conflict : 요청이 현재 서버의 상태와 충돌 - ACTIVITY_ALREADY_EXISTS(409, "이미 존재하는 활동입니다."); + ACTIVITY_ALREADY_EXISTS(409, "이미 존재하는 활동입니다."), + INSTITUTE_ALREADY_EXISTS(409, "이미 존재하는 기관입니다."), + DISTRICT_ALREADY_EXISTS(409, "이미 존재하는 지역입니다."); private final int status; private final String msg; diff --git a/src/main/java/com/gamsa/activity/controller/ActivityController.java b/src/main/java/com/gamsa/activity/controller/ActivityController.java index a231180..6da07fc 100644 --- a/src/main/java/com/gamsa/activity/controller/ActivityController.java +++ b/src/main/java/com/gamsa/activity/controller/ActivityController.java @@ -29,12 +29,20 @@ public class ActivityController { @GetMapping public Slice findSlice( @RequestParam(required = false) Category category, + @RequestParam(required = false) Integer sidoGunguCode, + @RequestParam(required = false) Integer sidoCode, @RequestParam(defaultValue = "false") boolean teenPossibleOnly, @RequestParam(defaultValue = "false") boolean beforeDeadlineOnly, Pageable pageable) { - ActivityFilterRequest request = new ActivityFilterRequest(category, teenPossibleOnly, - beforeDeadlineOnly); + ActivityFilterRequest request = ActivityFilterRequest.builder() + .category(category) + .sidoGunguCode(sidoGunguCode) + .sidoCode(sidoCode) + .teenPossibleOnly(teenPossibleOnly) + .beforeDeadlineOnly(beforeDeadlineOnly) + .build(); + return activityService.findSlice(request, pageable); } diff --git a/src/main/java/com/gamsa/activity/controller/DistrictController.java b/src/main/java/com/gamsa/activity/controller/DistrictController.java new file mode 100644 index 0000000..0be5c79 --- /dev/null +++ b/src/main/java/com/gamsa/activity/controller/DistrictController.java @@ -0,0 +1,38 @@ +package com.gamsa.activity.controller; + +import com.gamsa.activity.dto.DistrictFindAllResponse; +import com.gamsa.activity.dto.DistrictSaveRequest; +import com.gamsa.activity.service.DistrictService; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +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.RestController; + +@RequiredArgsConstructor +@RestController +@RequestMapping("/api/v1/districts") +public class DistrictController { + + private final DistrictService districtService; + + @PostMapping + public ResponseEntity save(@RequestBody DistrictSaveRequest saveRequest) { + districtService.save(saveRequest); + return new ResponseEntity<>(HttpStatus.CREATED); + } + + @GetMapping("/sido") + public List findAllSido() { + return districtService.findAllSido(); + } + + @GetMapping("/gungu") + public List findAllGungu() { + return districtService.findAllGungu(); + } +} diff --git a/src/main/java/com/gamsa/activity/controller/InstituteController.java b/src/main/java/com/gamsa/activity/controller/InstituteController.java new file mode 100644 index 0000000..e25a285 --- /dev/null +++ b/src/main/java/com/gamsa/activity/controller/InstituteController.java @@ -0,0 +1,25 @@ +package com.gamsa.activity.controller; + +import com.gamsa.activity.dto.InstituteSaveRequest; +import com.gamsa.activity.service.InstituteService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +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.RestController; + +@RequiredArgsConstructor +@RestController +@RequestMapping("/api/v1/institutes") +public class InstituteController { + + private final InstituteService instituteService; + + @PostMapping + public ResponseEntity save(@RequestBody InstituteSaveRequest saveRequest) { + instituteService.save(saveRequest); + return new ResponseEntity<>(HttpStatus.CREATED); + } +} diff --git a/src/main/java/com/gamsa/activity/domain/Activity.java b/src/main/java/com/gamsa/activity/domain/Activity.java index 662cd17..195b4b4 100644 --- a/src/main/java/com/gamsa/activity/domain/Activity.java +++ b/src/main/java/com/gamsa/activity/domain/Activity.java @@ -8,7 +8,6 @@ @Getter @Builder -@AllArgsConstructor public class Activity { private Long actId; @@ -30,4 +29,6 @@ public class Activity { private String actPhone; private String url; private Category category; + private Institute institute; + private District sidoGungu; } diff --git a/src/main/java/com/gamsa/activity/domain/District.java b/src/main/java/com/gamsa/activity/domain/District.java new file mode 100644 index 0000000..9379f12 --- /dev/null +++ b/src/main/java/com/gamsa/activity/domain/District.java @@ -0,0 +1,15 @@ +package com.gamsa.activity.domain; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class District { + + private int sidoGunguCode; + private int sidoCode; + private String sidoName; + private String gunguName; + private boolean sido; +} diff --git a/src/main/java/com/gamsa/activity/domain/Institute.java b/src/main/java/com/gamsa/activity/domain/Institute.java new file mode 100644 index 0000000..b3153e8 --- /dev/null +++ b/src/main/java/com/gamsa/activity/domain/Institute.java @@ -0,0 +1,18 @@ +package com.gamsa.activity.domain; + +import java.math.BigDecimal; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class Institute { + + private Long instituteId; + private String name; + private String location; + private BigDecimal latitude; + private BigDecimal longitude; + private District sidoGungu; + private String phone; +} diff --git a/src/main/java/com/gamsa/activity/dto/ActivityDetailResponse.java b/src/main/java/com/gamsa/activity/dto/ActivityDetailResponse.java index d45f6df..9e50ce7 100644 --- a/src/main/java/com/gamsa/activity/dto/ActivityDetailResponse.java +++ b/src/main/java/com/gamsa/activity/dto/ActivityDetailResponse.java @@ -32,6 +32,8 @@ public class ActivityDetailResponse { private final String actPhone; private final String url; private final Category category; + private final InstituteDetailResponse institute; + private final DistrictDetailResponse sidoGungu; public static ActivityDetailResponse from(Activity activity) { return ActivityDetailResponse.builder() @@ -54,6 +56,8 @@ public static ActivityDetailResponse from(Activity activity) { .actPhone(activity.getActPhone()) .url(activity.getUrl()) .category(activity.getCategory()) + .institute(InstituteDetailResponse.from(activity.getInstitute())) + .sidoGungu(DistrictDetailResponse.from(activity.getSidoGungu())) .build(); } } diff --git a/src/main/java/com/gamsa/activity/dto/ActivityFilterRequest.java b/src/main/java/com/gamsa/activity/dto/ActivityFilterRequest.java index e0aef5a..9be4f9d 100644 --- a/src/main/java/com/gamsa/activity/dto/ActivityFilterRequest.java +++ b/src/main/java/com/gamsa/activity/dto/ActivityFilterRequest.java @@ -1,17 +1,23 @@ package com.gamsa.activity.dto; import com.gamsa.activity.constant.Category; +import lombok.Builder; import lombok.Getter; import lombok.RequiredArgsConstructor; @Getter +@Builder @RequiredArgsConstructor public class ActivityFilterRequest { // 카테고리 private final Category category; - // Todo 지역 + // 시도군구 코드 + private final Integer sidoGunguCode; + + // 시도 코드 + private final Integer sidoCode; // 청소년 가능한 것만 private final boolean teenPossibleOnly; diff --git a/src/main/java/com/gamsa/activity/dto/ActivitySaveRequest.java b/src/main/java/com/gamsa/activity/dto/ActivitySaveRequest.java index 4dff7d0..cc0bc8d 100644 --- a/src/main/java/com/gamsa/activity/dto/ActivitySaveRequest.java +++ b/src/main/java/com/gamsa/activity/dto/ActivitySaveRequest.java @@ -2,6 +2,8 @@ import com.gamsa.activity.constant.Category; 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; @@ -31,8 +33,10 @@ public class ActivitySaveRequest { private final String actPhone; private final String url; private final Category category; + private final Long instituteId; + private final Integer sidoGunguCode; - public Activity toModel() { + public Activity toModel(Institute institute, District sidoGungu) { return Activity.builder() .actId(actId) .actTitle(actTitle) @@ -53,6 +57,8 @@ public Activity toModel() { .actPhone(actPhone) .url(url) .category(category) + .institute(institute) + .sidoGungu(sidoGungu) .build(); } } diff --git a/src/main/java/com/gamsa/activity/dto/DistrictDetailResponse.java b/src/main/java/com/gamsa/activity/dto/DistrictDetailResponse.java new file mode 100644 index 0000000..3db46c0 --- /dev/null +++ b/src/main/java/com/gamsa/activity/dto/DistrictDetailResponse.java @@ -0,0 +1,28 @@ +package com.gamsa.activity.dto; + +import com.gamsa.activity.domain.District; +import lombok.Builder; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@Builder +@RequiredArgsConstructor +public class DistrictDetailResponse { + + private final int sidoGunguCode; + private final int sidoCode; + private final String sidoName; + private final String gunguName; + private final boolean sido; + + public static DistrictDetailResponse from(District district) { + return DistrictDetailResponse.builder() + .sidoGunguCode(district.getSidoGunguCode()) + .sidoCode(district.getSidoCode()) + .sidoName(district.getSidoName()) + .gunguName(district.getGunguName()) + .sido(district.isSido()) + .build(); + } +} diff --git a/src/main/java/com/gamsa/activity/dto/DistrictFindAllResponse.java b/src/main/java/com/gamsa/activity/dto/DistrictFindAllResponse.java new file mode 100644 index 0000000..d045c62 --- /dev/null +++ b/src/main/java/com/gamsa/activity/dto/DistrictFindAllResponse.java @@ -0,0 +1,28 @@ +package com.gamsa.activity.dto; + +import com.gamsa.activity.domain.District; +import lombok.Builder; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@Builder +@RequiredArgsConstructor +public class DistrictFindAllResponse { + + private final int sidoGunguCode; + private final int sidoCode; + private final String sidoName; + private final String gunguName; + private final boolean sido; + + public static DistrictFindAllResponse from(District district) { + return DistrictFindAllResponse.builder() + .sidoGunguCode(district.getSidoGunguCode()) + .sidoCode(district.getSidoCode()) + .sidoName(district.getSidoName()) + .gunguName(district.getGunguName()) + .sido(district.isSido()) + .build(); + } +} diff --git a/src/main/java/com/gamsa/activity/dto/DistrictSaveRequest.java b/src/main/java/com/gamsa/activity/dto/DistrictSaveRequest.java new file mode 100644 index 0000000..6217fdd --- /dev/null +++ b/src/main/java/com/gamsa/activity/dto/DistrictSaveRequest.java @@ -0,0 +1,28 @@ +package com.gamsa.activity.dto; + +import com.gamsa.activity.domain.District; +import lombok.Builder; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@Builder +@RequiredArgsConstructor +public class DistrictSaveRequest { + + private final int sidoGunguCode; + private final int sidoCode; + private final String sidoName; + private final String gunguName; + private final boolean sido; + + public District toModel() { + return District.builder() + .sidoGunguCode(sidoGunguCode) + .sidoCode(sidoCode) + .sidoName(sidoName) + .gunguName(gunguName) + .sido(sido) + .build(); + } +} diff --git a/src/main/java/com/gamsa/activity/dto/InstituteDetailResponse.java b/src/main/java/com/gamsa/activity/dto/InstituteDetailResponse.java new file mode 100644 index 0000000..effb689 --- /dev/null +++ b/src/main/java/com/gamsa/activity/dto/InstituteDetailResponse.java @@ -0,0 +1,31 @@ +package com.gamsa.activity.dto; + +import com.gamsa.activity.domain.Institute; +import java.math.BigDecimal; +import lombok.Builder; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@Builder +@RequiredArgsConstructor +public class InstituteDetailResponse { + + private final Long instituteId; + private final String name; + private final String location; + private final BigDecimal latitude; + private final BigDecimal longitude; + private final String phone; + + public static InstituteDetailResponse from(Institute institute) { + return InstituteDetailResponse.builder() + .instituteId(institute.getInstituteId()) + .name(institute.getName()) + .location(institute.getLocation()) + .latitude(institute.getLatitude()) + .longitude(institute.getLongitude()) + .phone(institute.getPhone()) + .build(); + } +} diff --git a/src/main/java/com/gamsa/activity/dto/InstituteSaveRequest.java b/src/main/java/com/gamsa/activity/dto/InstituteSaveRequest.java new file mode 100644 index 0000000..630cc48 --- /dev/null +++ b/src/main/java/com/gamsa/activity/dto/InstituteSaveRequest.java @@ -0,0 +1,31 @@ +package com.gamsa.activity.dto; + +import com.gamsa.activity.domain.District; +import com.gamsa.activity.domain.Institute; +import java.math.BigDecimal; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class InstituteSaveRequest { + + private String name; + private String location; + private BigDecimal latitude; + private BigDecimal longitude; + private int sidoCode; + private int sidoGunguCode; + private String phone; + + public Institute toModel(District district) { + return Institute.builder() + .name(name) + .location(location) + .latitude(latitude) + .longitude(longitude) + .sidoGungu(district) + .phone(phone) + .build(); + } +} diff --git a/src/main/java/com/gamsa/activity/entity/ActivityJpaEntity.java b/src/main/java/com/gamsa/activity/entity/ActivityJpaEntity.java index 7106db4..7dc92b5 100644 --- a/src/main/java/com/gamsa/activity/entity/ActivityJpaEntity.java +++ b/src/main/java/com/gamsa/activity/entity/ActivityJpaEntity.java @@ -7,7 +7,10 @@ import jakarta.persistence.Column; import jakarta.persistence.Convert; import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; import jakarta.persistence.Temporal; import jakarta.persistence.TemporalType; @@ -92,6 +95,14 @@ public class ActivityJpaEntity extends BaseEntity { @Column(name = "category", length = 255) private Category category; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "institute_id") + private InstituteJpaEntity institute; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "sido_gungu_code", referencedColumnName = "sido_gungu_code") + private DistrictJpaEntity sidoGungu; + public static ActivityJpaEntity from(Activity activity) { return ActivityJpaEntity.builder() .actId(activity.getActId()) @@ -113,6 +124,8 @@ public static ActivityJpaEntity from(Activity activity) { .actPhone(activity.getActPhone()) .url(activity.getUrl()) .category(activity.getCategory()) + .institute(InstituteJpaEntity.from(activity.getInstitute())) + .sidoGungu(DistrictJpaEntity.from(activity.getSidoGungu())) .build(); } @@ -137,6 +150,8 @@ public Activity toModel() { .actPhone(actPhone) .url(url) .category(category) + .institute(institute.toModel()) + .sidoGungu(sidoGungu.toModel()) .build(); } diff --git a/src/main/java/com/gamsa/activity/entity/DistrictJpaEntity.java b/src/main/java/com/gamsa/activity/entity/DistrictJpaEntity.java new file mode 100644 index 0000000..d66c64e --- /dev/null +++ b/src/main/java/com/gamsa/activity/entity/DistrictJpaEntity.java @@ -0,0 +1,58 @@ +package com.gamsa.activity.entity; + +import com.gamsa.activity.domain.District; +import com.gamsa.common.entity.BaseEntity; +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 +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Entity +@Table(name = "District") +public class DistrictJpaEntity extends BaseEntity { + + @Id + @Column(name = "sido_gungu_code", nullable = false, unique = true) + private int sidoGunguCode; + + @Column(name = "sido_code", nullable = false) + private int sidoCode; + + @Column(name = "sido_name", length = 15, nullable = false) + private String sidoName; + + @Column(name = "gungu_name", length = 15) + private String gunguName; + + @Column(name = "sido", nullable = false) + private boolean sido; + + public static DistrictJpaEntity from(District district) { + return DistrictJpaEntity.builder() + .sidoGunguCode(district.getSidoGunguCode()) + .sidoCode(district.getSidoCode()) + .sidoName(district.getSidoName()) + .gunguName(district.getGunguName()) + .sido(district.isSido()) + .build(); + } + + public District toModel() { + return District.builder() + .sidoGunguCode(getSidoGunguCode()) + .sidoCode(getSidoCode()) + .sidoName(getSidoName()) + .gunguName(getGunguName()) + .sido(isSido()) + .build(); + } +} diff --git a/src/main/java/com/gamsa/activity/entity/InstituteJpaEntity.java b/src/main/java/com/gamsa/activity/entity/InstituteJpaEntity.java new file mode 100644 index 0000000..3dcb812 --- /dev/null +++ b/src/main/java/com/gamsa/activity/entity/InstituteJpaEntity.java @@ -0,0 +1,76 @@ +package com.gamsa.activity.entity; + +import com.gamsa.activity.domain.Institute; +import com.gamsa.common.entity.BaseEntity; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import java.math.BigDecimal; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Entity +@Table(name = "Institute") +public class InstituteJpaEntity extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "institute_id") + private Long instituteId; + + @Column(name = "name", unique = true) + private String name; + + @Column(name = "location", length = 255) + private String location; + + @Column(name = "latitude") + private BigDecimal latitude; + + @Column(name = "longitude") + private BigDecimal longitude; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "sido_gungu_code", referencedColumnName = "sido_gungu_code") + private DistrictJpaEntity sidoGungu; + + @Column(name = "phone", length = 12) + private String phone; + + public static InstituteJpaEntity from(Institute institute) { + return InstituteJpaEntity.builder() + .instituteId(institute.getInstituteId()) + .name(institute.getName()) + .location(institute.getLocation()) + .latitude(institute.getLatitude()) + .longitude(institute.getLongitude()) + .sidoGungu(DistrictJpaEntity.from(institute.getSidoGungu())) + .phone(institute.getPhone()) + .build(); + } + + public Institute toModel() { + return Institute.builder() + .instituteId(instituteId) + .name(name) + .location(location) + .latitude(latitude) + .longitude(longitude) + .sidoGungu(sidoGungu.toModel()) + .phone(phone) + .build(); + } +} diff --git a/src/main/java/com/gamsa/activity/repository/ActivityFilterBuilder.java b/src/main/java/com/gamsa/activity/repository/ActivityFilterBuilder.java index 4da810a..c9b83e9 100644 --- a/src/main/java/com/gamsa/activity/repository/ActivityFilterBuilder.java +++ b/src/main/java/com/gamsa/activity/repository/ActivityFilterBuilder.java @@ -13,6 +13,8 @@ public static BooleanBuilder createFilter(ActivityFilterRequest request) { BooleanBuilder filterBuilder = new BooleanBuilder(); eqCategory(filterBuilder, request.getCategory()); + eqSidoGunguCode(filterBuilder, request.getSidoGunguCode()); + eqSidoCode(filterBuilder, request.getSidoCode()); isTeenPossibleOnly(filterBuilder, request.isTeenPossibleOnly()); isDeadlineEndOnly(filterBuilder, request.isBeforeDeadlineOnly()); @@ -25,6 +27,18 @@ public static void eqCategory(BooleanBuilder filterBuilder, Category category) { } } + public static void eqSidoGunguCode(BooleanBuilder filterBuilder, Integer sidoGunguCode) { + if (sidoGunguCode != null) { + filterBuilder.and(activityJpaEntity.sidoGungu.sidoGunguCode.eq(sidoGunguCode)); + } + } + + public static void eqSidoCode(BooleanBuilder filterBuilder, Integer sidoCode) { + if (sidoCode != null) { + filterBuilder.and(activityJpaEntity.sidoGungu.sidoCode.eq(sidoCode)); + } + } + public static void isTeenPossibleOnly(BooleanBuilder filterBuilder, boolean teenPossibleOnly) { if (teenPossibleOnly) { filterBuilder.and(activityJpaEntity.teenPossible.isTrue()); diff --git a/src/main/java/com/gamsa/activity/repository/DistrictJpaRepository.java b/src/main/java/com/gamsa/activity/repository/DistrictJpaRepository.java new file mode 100644 index 0000000..8120bae --- /dev/null +++ b/src/main/java/com/gamsa/activity/repository/DistrictJpaRepository.java @@ -0,0 +1,13 @@ +package com.gamsa.activity.repository; + +import com.gamsa.activity.entity.DistrictJpaEntity; +import java.util.List; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface DistrictJpaRepository extends JpaRepository { + + Optional findBySidoGunguCode(int gunguCode); + + List findAllBySido(boolean sido); +} diff --git a/src/main/java/com/gamsa/activity/repository/DistrictRepository.java b/src/main/java/com/gamsa/activity/repository/DistrictRepository.java new file mode 100644 index 0000000..111326d --- /dev/null +++ b/src/main/java/com/gamsa/activity/repository/DistrictRepository.java @@ -0,0 +1,14 @@ +package com.gamsa.activity.repository; + +import com.gamsa.activity.domain.District; +import java.util.List; +import java.util.Optional; + +public interface DistrictRepository { + + void save(District district); + + Optional findBySidoGunguCode(int gunguCode); + + List findAllBysido(boolean sido); +} diff --git a/src/main/java/com/gamsa/activity/repository/DistrictRepositoryImpl.java b/src/main/java/com/gamsa/activity/repository/DistrictRepositoryImpl.java new file mode 100644 index 0000000..c4a0b65 --- /dev/null +++ b/src/main/java/com/gamsa/activity/repository/DistrictRepositoryImpl.java @@ -0,0 +1,33 @@ +package com.gamsa.activity.repository; + +import com.gamsa.activity.domain.District; +import com.gamsa.activity.entity.DistrictJpaEntity; +import java.util.List; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +@RequiredArgsConstructor +@Repository +public class DistrictRepositoryImpl implements DistrictRepository { + + private final DistrictJpaRepository districtJpaRepository; + + @Override + public void save(District district) { + districtJpaRepository.save(DistrictJpaEntity.from(district)); + } + + @Override + public Optional findBySidoGunguCode(int gunguCode) { + return districtJpaRepository.findBySidoGunguCode(gunguCode) + .map(DistrictJpaEntity::toModel); + } + + @Override + public List findAllBysido(boolean sido) { + return districtJpaRepository.findAllBySido(sido).stream() + .map(DistrictJpaEntity::toModel) + .toList(); + } +} diff --git a/src/main/java/com/gamsa/activity/repository/InstituteJpaRepository.java b/src/main/java/com/gamsa/activity/repository/InstituteJpaRepository.java new file mode 100644 index 0000000..dfe9d52 --- /dev/null +++ b/src/main/java/com/gamsa/activity/repository/InstituteJpaRepository.java @@ -0,0 +1,10 @@ +package com.gamsa.activity.repository; + +import com.gamsa.activity.entity.InstituteJpaEntity; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface InstituteJpaRepository extends JpaRepository { + + Optional findByName(String name); +} diff --git a/src/main/java/com/gamsa/activity/repository/InstituteRepository.java b/src/main/java/com/gamsa/activity/repository/InstituteRepository.java new file mode 100644 index 0000000..2e5f6c4 --- /dev/null +++ b/src/main/java/com/gamsa/activity/repository/InstituteRepository.java @@ -0,0 +1,13 @@ +package com.gamsa.activity.repository; + +import com.gamsa.activity.domain.Institute; +import java.util.Optional; + +public interface InstituteRepository { + + void save(Institute institute); + + Optional findById(Long id); + + Optional findByName(String name); +} diff --git a/src/main/java/com/gamsa/activity/repository/InstituteRepositoryImpl.java b/src/main/java/com/gamsa/activity/repository/InstituteRepositoryImpl.java new file mode 100644 index 0000000..a54e8bb --- /dev/null +++ b/src/main/java/com/gamsa/activity/repository/InstituteRepositoryImpl.java @@ -0,0 +1,31 @@ +package com.gamsa.activity.repository; + +import com.gamsa.activity.domain.Institute; +import com.gamsa.activity.entity.InstituteJpaEntity; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +@RequiredArgsConstructor +@Repository +public class InstituteRepositoryImpl implements InstituteRepository { + + private final InstituteJpaRepository instituteJpaRepository; + + @Override + public void save(Institute institute) { + instituteJpaRepository.save(InstituteJpaEntity.from(institute)); + } + + @Override + public Optional findById(Long id) { + return instituteJpaRepository.findById(id) + .map(InstituteJpaEntity::toModel); + } + + @Override + public Optional findByName(String name) { + return instituteJpaRepository.findByName(name) + .map(InstituteJpaEntity::toModel); + } +} diff --git a/src/main/java/com/gamsa/activity/service/ActivityService.java b/src/main/java/com/gamsa/activity/service/ActivityService.java index 2c8df8c..88b1687 100644 --- a/src/main/java/com/gamsa/activity/service/ActivityService.java +++ b/src/main/java/com/gamsa/activity/service/ActivityService.java @@ -2,12 +2,16 @@ import com.gamsa.activity.constant.ActivityErrorCode; import com.gamsa.activity.domain.Activity; +import com.gamsa.activity.domain.District; +import com.gamsa.activity.domain.Institute; import com.gamsa.activity.dto.ActivityDetailResponse; import com.gamsa.activity.dto.ActivityFilterRequest; import com.gamsa.activity.dto.ActivityFindSliceResponse; import com.gamsa.activity.dto.ActivitySaveRequest; import com.gamsa.activity.exception.ActivityException; import com.gamsa.activity.repository.ActivityRepository; +import com.gamsa.activity.repository.DistrictRepository; +import com.gamsa.activity.repository.InstituteRepository; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; @@ -18,13 +22,24 @@ public class ActivityService { private final ActivityRepository activityRepository; + private final InstituteRepository instituteRepository; + private final DistrictRepository districtRepository; public void save(ActivitySaveRequest saveRequest) { + // 중복 여부 확인 activityRepository.findById(saveRequest.getActId()) .ifPresent(activity -> { throw new ActivityException(ActivityErrorCode.ACTIVITY_ALREADY_EXISTS); }); - activityRepository.save(saveRequest.toModel()); + // 기관 존재 확인 + Institute institute = instituteRepository.findById(saveRequest.getInstituteId()) + .orElseThrow(() -> new ActivityException(ActivityErrorCode.INSTITUTE_NOT_EXISTS)); + + // 시도, 군구 존재 확인 + District sidoGungu = districtRepository.findBySidoGunguCode(saveRequest.getSidoGunguCode()) + .orElseThrow(() -> new ActivityException(ActivityErrorCode.DISTRICT_NOT_EXISTS)); + + activityRepository.save(saveRequest.toModel(institute, sidoGungu)); } public Slice findSlice(ActivityFilterRequest request, diff --git a/src/main/java/com/gamsa/activity/service/DistrictService.java b/src/main/java/com/gamsa/activity/service/DistrictService.java new file mode 100644 index 0000000..bcc853e --- /dev/null +++ b/src/main/java/com/gamsa/activity/service/DistrictService.java @@ -0,0 +1,47 @@ +package com.gamsa.activity.service; + +import com.gamsa.activity.constant.ActivityErrorCode; +import com.gamsa.activity.dto.DistrictFindAllResponse; +import com.gamsa.activity.dto.DistrictSaveRequest; +import com.gamsa.activity.exception.ActivityException; +import com.gamsa.activity.repository.DistrictRepository; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@RequiredArgsConstructor +@Service +public class DistrictService { + + private final DistrictRepository districtRepository; + + public void save(DistrictSaveRequest saveRequest) { + districtRepository.findBySidoGunguCode(saveRequest.getSidoGunguCode()) + .ifPresent(district -> { + throw new ActivityException(ActivityErrorCode.DISTRICT_ALREADY_EXISTS); + }); + districtRepository.save(saveRequest.toModel()); + } + + /** + * 시도 코드 데이터만 반환 + * + * @return 시도 코드 리스트 + */ + public List findAllSido() { + return districtRepository.findAllBysido(true).stream() + .map(DistrictFindAllResponse::from) + .toList(); + } + + /** + * 군구 코드 데이터만 반환 + * + * @return 군구 코드 리스트 + */ + public List findAllGungu() { + return districtRepository.findAllBysido(false).stream() + .map(DistrictFindAllResponse::from) + .toList(); + } +} diff --git a/src/main/java/com/gamsa/activity/service/InstituteService.java b/src/main/java/com/gamsa/activity/service/InstituteService.java new file mode 100644 index 0000000..7849c43 --- /dev/null +++ b/src/main/java/com/gamsa/activity/service/InstituteService.java @@ -0,0 +1,30 @@ +package com.gamsa.activity.service; + +import com.gamsa.activity.constant.ActivityErrorCode; +import com.gamsa.activity.domain.District; +import com.gamsa.activity.dto.InstituteSaveRequest; +import com.gamsa.activity.exception.ActivityException; +import com.gamsa.activity.repository.DistrictRepository; +import com.gamsa.activity.repository.InstituteRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@RequiredArgsConstructor +@Service +public class InstituteService { + + private final InstituteRepository instituteRepository; + private final DistrictRepository districtRepository; + + public void save(InstituteSaveRequest saveRequest) { + instituteRepository.findByName(saveRequest.getName()) + .ifPresent(institute -> { + throw new ActivityException(ActivityErrorCode.INSTITUTE_ALREADY_EXISTS); + }); + + District district = districtRepository.findBySidoGunguCode(saveRequest.getSidoGunguCode()) + .orElseThrow(() -> new ActivityException(ActivityErrorCode.DISTRICT_NOT_EXISTS)); + + instituteRepository.save(saveRequest.toModel(district)); + } +} diff --git a/src/test/java/com/gamsa/activity/entity/ActivityJpaEntityTest.java b/src/test/java/com/gamsa/activity/entity/ActivityJpaEntityTest.java index c8db623..3dfe74a 100644 --- a/src/test/java/com/gamsa/activity/entity/ActivityJpaEntityTest.java +++ b/src/test/java/com/gamsa/activity/entity/ActivityJpaEntityTest.java @@ -4,6 +4,9 @@ import com.gamsa.activity.constant.Category; import com.gamsa.activity.domain.Activity; +import com.gamsa.activity.domain.District; +import com.gamsa.activity.domain.Institute; +import java.math.BigDecimal; import java.time.LocalDateTime; import org.junit.jupiter.api.Test; @@ -12,6 +15,24 @@ class ActivityJpaEntityTest { @Test void 도메인모델에서_JPA엔티티로_변환() { // given + 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("어린이놀이안전관리 및 놀잇감 청결유지 및 정리") @@ -30,8 +51,10 @@ class ActivityJpaEntityTest { .actWeek(0111110) .actManager("윤순영") .actPhone("032-577-3026") - .category(Category.OTHER_ACTIVITIES) .url("https://...") + .category(Category.OTHER_ACTIVITIES) + .institute(institute) + .sidoGungu(district) .build(); // when @@ -44,6 +67,24 @@ class ActivityJpaEntityTest { @Test void JPA엔티에서_도메인모델로_변환() { // given + DistrictJpaEntity districtJpaEntity = DistrictJpaEntity.builder() + .sidoCode(1234) + .sidoGunguCode(8888) + .sidoName("서울특별시") + .gunguName("강남구") + .sido(false) + .build(); + + InstituteJpaEntity institute = InstituteJpaEntity.builder() + .instituteId(1L) + .name("도서관") + .location("서울시") + .latitude(new BigDecimal("123456789.12341234")) + .longitude(new BigDecimal("987654321.43214321")) + .sidoGungu(districtJpaEntity) + .phone("010xxxxxxxx") + .build(); + ActivityJpaEntity jpaEntity = ActivityJpaEntity.builder() .actId(1L) .actTitle("어린이놀이안전관리 및 놀잇감 청결유지 및 정리") @@ -64,6 +105,8 @@ class ActivityJpaEntityTest { .actPhone("032-577-3026") .url("https://...") .category(Category.OTHER_ACTIVITIES) + .institute(institute) + .sidoGungu(districtJpaEntity) .build(); // when diff --git a/src/test/java/com/gamsa/activity/entity/InstituteJpaEntityTest.java b/src/test/java/com/gamsa/activity/entity/InstituteJpaEntityTest.java new file mode 100644 index 0000000..8e10d69 --- /dev/null +++ b/src/test/java/com/gamsa/activity/entity/InstituteJpaEntityTest.java @@ -0,0 +1,68 @@ +package com.gamsa.activity.entity; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.gamsa.activity.domain.District; +import com.gamsa.activity.domain.Institute; +import java.math.BigDecimal; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class InstituteJpaEntityTest { + + + @Test + @DisplayName("도메인 모델에서 JPA엔티티로 변환") + void changeToJpaEntity() { + // given + District district = District.builder() + .sidoCode(1234) + .sidoGunguCode(8888) + .sidoName("서울특별시") + .gunguName("강남구") + .sido(false) + .build(); + + Institute model = Institute.builder() + .instituteId(1L) + .name("도서관") + .location("서울시") + .latitude(new BigDecimal("123456789.12341234")) + .longitude(new BigDecimal("987654321.43214321")) + .sidoGungu(district) + .phone("010xxxxxxxx") + .build(); + // when + InstituteJpaEntity jpaEntity = InstituteJpaEntity.from(model); + // then + assertThat(jpaEntity.getInstituteId()).isEqualTo(model.getInstituteId()); + } + + @Test + @DisplayName("JPA엔티티에서 도메인 모델로 변환") + void changeToDomainModel() { + // given + DistrictJpaEntity districtJpaEntity = DistrictJpaEntity.builder() + .sidoCode(1234) + .sidoGunguCode(8888) + .sidoName("서울특별시") + .gunguName("강남구") + .sido(false) + .build(); + + InstituteJpaEntity jpaEntity = InstituteJpaEntity.builder() + .instituteId(1L) + .name("도서관") + .location("서울시") + .latitude(new BigDecimal("123456789.12341234")) + .longitude(new BigDecimal("987654321.43214321")) + .sidoGungu(districtJpaEntity) + .phone("010xxxxxxxx") + .build(); + // when + Institute model = jpaEntity.toModel(); + // then + assertThat(model.getInstituteId()).isEqualTo(jpaEntity.getInstituteId()); + } + +} \ No newline at end of file diff --git a/src/test/java/com/gamsa/activity/repository/ActivityJpaRepositoryTest.java b/src/test/java/com/gamsa/activity/repository/ActivityJpaRepositoryTest.java index 37b457d..c9ad167 100644 --- a/src/test/java/com/gamsa/activity/repository/ActivityJpaRepositoryTest.java +++ b/src/test/java/com/gamsa/activity/repository/ActivityJpaRepositoryTest.java @@ -92,16 +92,16 @@ class ActivityJpaRepositoryTest { // 필터링 private final ActivityFilterRequest noFilterReq = new ActivityFilterRequest( - null, false, false); + null, null, null, false, false); private final ActivityFilterRequest otherCategoryFilterReq = new ActivityFilterRequest( - Category.OTHER_ACTIVITIES, false, false); + Category.OTHER_ACTIVITIES, null, null, false, false); private final ActivityFilterRequest teenPossibleFilterReq = new ActivityFilterRequest( - null, true, false); + null, null, null, true, false); private final ActivityFilterRequest beforeDeadlineFilterReq = new ActivityFilterRequest( - null, false, true); + null, null, null, false, true); @Test void 새_활동_저장() { diff --git a/src/test/java/com/gamsa/activity/repository/InstituteJpaRepositoryTest.java b/src/test/java/com/gamsa/activity/repository/InstituteJpaRepositoryTest.java new file mode 100644 index 0000000..45f82c2 --- /dev/null +++ b/src/test/java/com/gamsa/activity/repository/InstituteJpaRepositoryTest.java @@ -0,0 +1,39 @@ +package com.gamsa.activity.repository; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.gamsa.activity.entity.InstituteJpaEntity; +import com.gamsa.common.config.TestConfig; +import java.math.BigDecimal; +import org.junit.jupiter.api.DisplayName; +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; + +@DataJpaTest +@Import(TestConfig.class) +class InstituteJpaRepositoryTest { + + @Autowired + private InstituteJpaRepository instituteJpaRepository; + + private final InstituteJpaEntity jpaEntity = InstituteJpaEntity.builder() + .instituteId(1L) + .name("도서관") + .location("서울시") + .latitude(new BigDecimal("123456789.12341234")) + .longitude(new BigDecimal("987654321.43214321")) + .phone("010xxxxxxxx") + .build(); + + @Test + @DisplayName("새 봉사기관 저장") + void save() { + // when + instituteJpaRepository.save(jpaEntity); + // then + assertThat(instituteJpaRepository.findById(1L).get().getName()) + .isEqualTo(jpaEntity.getName()); + } +} \ No newline at end of file diff --git a/src/test/java/com/gamsa/activity/service/ActivityServiceTest.java b/src/test/java/com/gamsa/activity/service/ActivityServiceTest.java index c3a8356..9a1e164 100644 --- a/src/test/java/com/gamsa/activity/service/ActivityServiceTest.java +++ b/src/test/java/com/gamsa/activity/service/ActivityServiceTest.java @@ -10,8 +10,11 @@ import com.gamsa.activity.exception.ActivityException; import com.gamsa.activity.stub.StubEmptyActivityRepository; import com.gamsa.activity.stub.StubExistsActivityRepository; +import com.gamsa.activity.stub.StubExistsDistrictRepository; +import com.gamsa.activity.stub.StubExistsInstituteRepository; import java.time.LocalDateTime; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Slice; @@ -38,12 +41,18 @@ class ActivityServiceTest { .actPhone("032-577-3026") .url("https://...") .category(Category.OTHER_ACTIVITIES) + .instituteId(1L) + .sidoGunguCode(1) .build(); @Test void 활동객체를_저장하고_성공한다() { // given - ActivityService activityService = new ActivityService(new StubEmptyActivityRepository()); + ActivityService activityService = new ActivityService( + new StubEmptyActivityRepository(), + new StubExistsInstituteRepository(), + new StubExistsDistrictRepository() + ); // then Assertions.assertDoesNotThrow(() -> { @@ -56,7 +65,11 @@ class ActivityServiceTest { @Test void 이미_존재하는_ID의_활동객체를_생성하고_실패한다() { // given - ActivityService activityService = new ActivityService(new StubExistsActivityRepository()); + ActivityService activityService = new ActivityService( + new StubExistsActivityRepository(), + new StubExistsInstituteRepository(), + new StubExistsDistrictRepository() + ); // then Assertions.assertThrows(ActivityException.class, () -> { @@ -68,7 +81,11 @@ class ActivityServiceTest { @Test void 활동객체_리스트를_반환한다() { // given - ActivityService activityService = new ActivityService(new StubEmptyActivityRepository()); + ActivityService activityService = new ActivityService( + new StubEmptyActivityRepository(), + new StubExistsInstituteRepository(), + new StubExistsDistrictRepository() + ); // when Slice result = activityService.findSlice(null, @@ -81,7 +98,11 @@ class ActivityServiceTest { @Test void ID로_활동조회에_성공한다() { // given - ActivityService activityService = new ActivityService(new StubExistsActivityRepository()); + ActivityService activityService = new ActivityService( + new StubExistsActivityRepository(), + new StubExistsInstituteRepository(), + new StubExistsDistrictRepository() + ); // when ActivityDetailResponse result = activityService.findById(1L); @@ -93,7 +114,11 @@ class ActivityServiceTest { @Test void ID로_활동조회에_실패한다() { // given - ActivityService activityService = new ActivityService(new StubEmptyActivityRepository()); + ActivityService activityService = new ActivityService( + new StubEmptyActivityRepository(), + new StubExistsInstituteRepository(), + new StubExistsDistrictRepository() + ); // then Assertions.assertThrows(ActivityException.class, () -> { @@ -101,4 +126,10 @@ class ActivityServiceTest { activityService.findById(1L); }, ActivityErrorCode.ACTIVITY_NOT_EXISTS.getMsg()); } + + @Test + @DisplayName("활동 ID로 봉사 기관 정보를 조회한다.") + void findInstituteByActivityId() { + + } } \ No newline at end of file diff --git a/src/test/java/com/gamsa/activity/service/InstituteServiceTest.java b/src/test/java/com/gamsa/activity/service/InstituteServiceTest.java new file mode 100644 index 0000000..16814aa --- /dev/null +++ b/src/test/java/com/gamsa/activity/service/InstituteServiceTest.java @@ -0,0 +1,56 @@ +package com.gamsa.activity.service; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.gamsa.activity.constant.ActivityErrorCode; +import com.gamsa.activity.dto.InstituteSaveRequest; +import com.gamsa.activity.exception.ActivityException; +import com.gamsa.activity.stub.StubEmptyInstituteRepository; +import com.gamsa.activity.stub.StubExistsDistrictRepository; +import com.gamsa.activity.stub.StubExistsInstituteRepository; +import java.math.BigDecimal; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class InstituteServiceTest { + + private final InstituteSaveRequest saveRequest = InstituteSaveRequest.builder() + .name("도서관") + .location("서울시") + .latitude(new BigDecimal("123456789.12341234")) + .longitude(new BigDecimal("987654321.43214321")) + .phone("010xxxxxxxx") + .build(); + + + @Test + @DisplayName("봉사기관을 생성한다.") + void save() { + // given + InstituteService service = new InstituteService( + new StubEmptyInstituteRepository(), + new StubExistsDistrictRepository() + ); + // then + assertDoesNotThrow(() -> { + // when + service.save(saveRequest); + }); + } + + @Test + @DisplayName("봉시기관 동일 이름 충돌로 실패한다.") + void saveFail() { + // given + InstituteService service = new InstituteService( + new StubExistsInstituteRepository(), + new StubExistsDistrictRepository() + ); + // then + assertThrows(ActivityException.class, () -> { + // when + service.save(saveRequest); + }, ActivityErrorCode.INSTITUTE_ALREADY_EXISTS.getMsg()); + } +} \ No newline at end of file diff --git a/src/test/java/com/gamsa/activity/stub/StubEmptyInstituteRepository.java b/src/test/java/com/gamsa/activity/stub/StubEmptyInstituteRepository.java new file mode 100644 index 0000000..cd85f5f --- /dev/null +++ b/src/test/java/com/gamsa/activity/stub/StubEmptyInstituteRepository.java @@ -0,0 +1,23 @@ +package com.gamsa.activity.stub; + +import com.gamsa.activity.domain.Institute; +import com.gamsa.activity.repository.InstituteRepository; +import java.util.Optional; + +public class StubEmptyInstituteRepository implements InstituteRepository { + + @Override + public void save(Institute institute) { + // do nothing + } + + @Override + public Optional findById(Long id) { + return Optional.empty(); + } + + @Override + public Optional findByName(String name) { + return Optional.empty(); + } +} diff --git a/src/test/java/com/gamsa/activity/stub/StubExistsActivityRepository.java b/src/test/java/com/gamsa/activity/stub/StubExistsActivityRepository.java index 73bea05..d4161d2 100644 --- a/src/test/java/com/gamsa/activity/stub/StubExistsActivityRepository.java +++ b/src/test/java/com/gamsa/activity/stub/StubExistsActivityRepository.java @@ -1,8 +1,11 @@ package com.gamsa.activity.stub; import com.gamsa.activity.domain.Activity; +import com.gamsa.activity.domain.District; +import com.gamsa.activity.domain.Institute; import com.gamsa.activity.dto.ActivityFilterRequest; import com.gamsa.activity.repository.ActivityRepository; +import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.List; import java.util.Optional; @@ -12,6 +15,23 @@ public class StubExistsActivityRepository implements ActivityRepository { + private final District district = District.builder() + .sidoCode(1234) + .sidoGunguCode(8888) + .sidoName("서울특별시") + .gunguName("강남구") + .sido(false) + .build(); + + private final Institute institute = Institute.builder() + .instituteId(1L) + .name("도서관") + .location("서울시") + .latitude(new BigDecimal("123456789.12341234")) + .longitude(new BigDecimal("987654321.43214321")) + .phone("010xxxxxxxx") + .build(); + private final Activity activity = Activity.builder() .actId(1L) .actTitle("어린이놀이안전관리 및 놀잇감 청결유지 및 정리") @@ -31,6 +51,8 @@ public class StubExistsActivityRepository implements ActivityRepository { .actManager("윤순영") .actPhone("032-577-3026") .url("https://...") + .institute(institute) + .sidoGungu(district) .build(); @Override diff --git a/src/test/java/com/gamsa/activity/stub/StubExistsDistrictRepository.java b/src/test/java/com/gamsa/activity/stub/StubExistsDistrictRepository.java new file mode 100644 index 0000000..247b7d1 --- /dev/null +++ b/src/test/java/com/gamsa/activity/stub/StubExistsDistrictRepository.java @@ -0,0 +1,32 @@ +package com.gamsa.activity.stub; + +import com.gamsa.activity.domain.District; +import com.gamsa.activity.repository.DistrictRepository; +import java.util.List; +import java.util.Optional; + +public class StubExistsDistrictRepository implements DistrictRepository { + + private final District district = District.builder() + .sidoCode(1234) + .sidoGunguCode(8888) + .sidoName("서울특별시") + .gunguName("강남구") + .sido(false) + .build(); + + @Override + public void save(District district) { + // do nothing + } + + @Override + public Optional findBySidoGunguCode(int gunguCode) { + return Optional.of(district); + } + + @Override + public List findAllBysido(boolean sido) { + return List.of(district); + } +} diff --git a/src/test/java/com/gamsa/activity/stub/StubExistsInstituteRepository.java b/src/test/java/com/gamsa/activity/stub/StubExistsInstituteRepository.java new file mode 100644 index 0000000..09a6f77 --- /dev/null +++ b/src/test/java/com/gamsa/activity/stub/StubExistsInstituteRepository.java @@ -0,0 +1,33 @@ +package com.gamsa.activity.stub; + +import com.gamsa.activity.domain.Institute; +import com.gamsa.activity.repository.InstituteRepository; +import java.math.BigDecimal; +import java.util.Optional; + +public class StubExistsInstituteRepository implements InstituteRepository { + + private final Institute institute = Institute.builder() + .instituteId(1L) + .name("도서관") + .location("서울시") + .latitude(new BigDecimal("123456789.12341234")) + .longitude(new BigDecimal("987654321.43214321")) + .phone("010xxxxxxxx") + .build(); + + @Override + public void save(Institute institute) { + // do nothing + } + + @Override + public Optional findById(Long id) { + return Optional.of(institute); + } + + @Override + public Optional findByName(String name) { + return Optional.of(institute); + } +} diff --git a/src/test/java/com/gamsa/avatar/repository/AvatarJpaRepositoryTest.java b/src/test/java/com/gamsa/avatar/repository/AvatarJpaRepositoryTest.java index 8572c66..37f5252 100644 --- a/src/test/java/com/gamsa/avatar/repository/AvatarJpaRepositoryTest.java +++ b/src/test/java/com/gamsa/avatar/repository/AvatarJpaRepositoryTest.java @@ -11,9 +11,8 @@ import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.context.annotation.Import; -import java.time.LocalDateTime; - @DataJpaTest +@Import(TestConfig.class) public class AvatarJpaRepositoryTest { @Autowired private AvatarJpaRepository avatarJpaRepository; From 9634700438da796be9d25cac0a8234c23f06452b Mon Sep 17 00:00:00 2001 From: 5win <94297900+5win@users.noreply.github.com> Date: Mon, 28 Oct 2024 13:13:28 +0900 Subject: [PATCH 2/2] =?UTF-8?q?review:=20=EB=84=A4=20=EB=B2=88=EC=A7=B8=20?= =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20PR=20(#42)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 활동 조회 기능 완성 및 단위테스트 추가 (#15) * [#6] feat: JPA 레포지토리 연결 - save, findAll, findById 구현 * [#6] feat: 활동 리스트, 상세정보 응답 DTO 구현 * [#6] feat: JPA 엔티티 애노테이션 수정 - GeneratedValue 삭제 : 받아온 데이터의 id를 그대로 사용하기 위함. - String 컬럼에 length 제한 적용 - LocalDateTime 컬럼에 @Temporal 적용 * [#6] feat: 예외 메시지 enum 생성 및 적용 - 기존 string 하드코딩에서 enum으로 변경 * [#6] feat: Spring security 우회 설정 - 컨트롤러 테스트를 위해 모든 경로 permitAll로 지정하여 우회함. * [#6] fix: 컨트롤러 URI 변경 - program -> activity로 명명 변경에서 놓친 부분 수정 * [#6] fix: 활동 저장 오류 수정 - 서비스의 잘못된 로직 수정 - JpaEntity에 기본 생성자 추가 * [#6] refact: DTO 변수를 final로 변경 - 응답 dto의 변수를 final로 변경 - 저장 요청 dto에서 @Data 에서 @Getter로 변경 * [#6] feat: Activity 예외 수정 및 핸들러 추가 - common.constant.ErrorCode 추가 - common.exception.GlobalExceptionHandler 추가 - activity.exception.ActivityCustomException 수정 * [#6] feat: Activity 관련 클래스 변수 추가 및 삭제 - onlinePossible 변수 삭제 - actStartTime, actEndTime 변수 추가 * [#6] feat: 활동 제목 변수 추가 - actTitle 추가 * [#6] test: Activity 단위 테스트 추가 - 아래 클래스에 대한 단위 테스트를 추가함. - JpaEntity - Service - JpaRepository * weekly: 4주차 작업 병합 (#24) * docs: Update issue templates 이슈 템플릿 생성 * docs: Create pull_request_template.md - PR 템플릿 작성 * init: 프로젝트 생성 [#2] init: 프로젝트 생성 및 의존성 추가 (#3) (#4) * weekly: 3주차 작업 내용 머지 (#8) * [#2] init: 프로젝트 생성 및 의존성 추가 (#3) - 프로젝트 생성 - 의존성 추가 - H2 연결 및 테스트 * [#6] feat: 봉사활동 조회 기능 추가 (#7) - 아직 모두 구현하지 못했음 * fix: PR 템플릿 위치 수정 (#14) * [#5] init: PR 템플릿 위치 변경 - 템플릿 파일의 위치를 변경함 * rename: 기본 테스트 클래스 이름 변경 * feat: 활동 조회 기능 완성 및 단위테스트 추가 (#15) * [#6] feat: JPA 레포지토리 연결 - save, findAll, findById 구현 * [#6] feat: 활동 리스트, 상세정보 응답 DTO 구현 * [#6] feat: JPA 엔티티 애노테이션 수정 - GeneratedValue 삭제 : 받아온 데이터의 id를 그대로 사용하기 위함. - String 컬럼에 length 제한 적용 - LocalDateTime 컬럼에 @Temporal 적용 * [#6] feat: 예외 메시지 enum 생성 및 적용 - 기존 string 하드코딩에서 enum으로 변경 * [#6] feat: Spring security 우회 설정 - 컨트롤러 테스트를 위해 모든 경로 permitAll로 지정하여 우회함. * [#6] fix: 컨트롤러 URI 변경 - program -> activity로 명명 변경에서 놓친 부분 수정 * [#6] fix: 활동 저장 오류 수정 - 서비스의 잘못된 로직 수정 - JpaEntity에 기본 생성자 추가 * [#6] refact: DTO 변수를 final로 변경 - 응답 dto의 변수를 final로 변경 - 저장 요청 dto에서 @Data 에서 @Getter로 변경 * [#6] feat: Activity 예외 수정 및 핸들러 추가 - common.constant.ErrorCode 추가 - common.exception.GlobalExceptionHandler 추가 - activity.exception.ActivityCustomException 수정 * [#6] feat: Activity 관련 클래스 변수 추가 및 삭제 - onlinePossible 변수 삭제 - actStartTime, actEndTime 변수 추가 * [#6] feat: 활동 제목 변수 추가 - actTitle 추가 * [#6] test: Activity 단위 테스트 추가 - 아래 클래스에 대한 단위 테스트를 추가함. - JpaEntity - Service - JpaRepository * feat: 봉사활동 조회 무한스크롤 기능 및 단위테스트 구현 (#20) * [#18] feat: 활동 조회 id 기준 QueryDSL 무한스크롤 구현 - QueryDSL 의존성 추가 및 common.config.QueryDslConfig 파일 추가 - Slice 객체 반환하도록 변경 - CustomRepository 만드는 패턴을 통해 QueryDSL과 Spring data JPA 결합 * [#18] rename: 활동 테이블 PK명 id에서 actId로 변경 * [#18] test: 무한스크롤 단위 테스트 추가 및 수정 * [#18] feat: 활동 정렬 기준 상수클래스 생성 - 정렬 기준 상수클래스 생성하여 하드코딩 제거 - common.constant를 activity 패키지로 이동 * [#18] fix: ActivityCustomRepositoryImpl에서의 도메인 엔티티 종속성 제거 - Activity로의 종속성을 제거하고 ActivityJpaEntity만 종속하도록 수정함. * [#18] rename: 바꾸지 않은 findAll 명명을 모두 findSlice로 변경 * [#18] rename: ActivityCustomException을 ActivityException으로 변경 * [#18] refact: OrderSpecifier 정렬 기준 생성 메서드 리팩토링 불필요한 클래스 삭제 - common.utils.QueryDslUtil - activity.constant.ActivitySortType * feat: 봉사 분야 기능, 봉사 조회 필터링 기능 추가 (#23) * [#22] refact: lombok 사용하여 생성자 제거 * [#22] feat: H2콘솔 위한 X-Frame-Options 비활성화 * [#22] feat: 봉사 분야 컬럼 추가 - enum 형태로 관리 - CategoryConverter를 통해 DB에는 한글로 저장. * [#22] feat: 봉사활동 조회 필터링 기능 추가 - filter DTO를 추가하여 필터링 정보 전달. - 파라미터 변경에 따른 클래스, 테스트코드 수정 - ActivityFilterBuilder: 필터링 정보 조합 BooleanBuilder 반환 * [#22] fix: 마감 전 활동만 필터링 하도록 조건 수정 - 마감된 활동만 필터링에서 마감 전 활동 필터링으로 변경 * [#22] test: 활동 조회 정렬, 필터링 기능 단위테스트 - 마감 날짜 오름차순 정렬 조회 - 마감된 공고 중, 마감 날짜 가까운 순 정렬 - 카테고리 필터링 - 청소년 가능 활동만 필터링 - 마감되지 않은 활동만 필터링 * [#22] refact: 기존 활동 조회 단위 테스트 리팩토링 * feat: 아바타 관련 기능 구현 * feat: 아바타 관련 기능 구현 신규 아바타 저장 기존 아바타 호출 기존 아바타 수정 * test: 아바타 관련 단위 테스트 * fix: 아바타 기능 수정 --------- Co-authored-by: Awhn <69659322+Awhn@users.noreply.github.com> * feat: 봉사 기관 관련 기능 및 단위테스트 추가 (#29) * [#21] fix: AvatarJpaRepositoryTest에 @Import 추가 * [#21] refact: Activity 도메인 lombok 수정 - AllArgsConstructor 제거 * [#21] feat: 봉사기관 테이블 생성 및 조회 - 봉사활동:봉사기관 = N:1 (ManyToOne) - ActivityDetail 응답에 기관 정보 포함 * [#21] test: 봉사기관 관련 단위테스트 * [#21] fix: 리뷰 후 1차 수정 사항 - InstituteDetailResponse로 명명 수정 - 위도, 경도 BigDecimal로 변경 * feat: 시도, 시군구 API 및 시도, 시군구 필터링 기능 추가 (#31) * [#30] feat: 시도, 군구 CR API 구현 - 시도, 군구 영속성 엔티티 생성 - create, read API 구현 - 시도 조회, 군구 조회 별도로 생성 * [#30] fix: 변수명, 제약조건 수정 - gunguCode 에서 sidoGunguCode로 변경 - nullable 제약조건 추가 * [#30] feat: 봉사활동과 시도군구 연관관계 매핑 * [#30] feat: 시도, 시군구 필터링 조회 기능 추가 - 봉사활동 조회 시, 시도, 시군구 필터링 기능 추가 - sidoCode, sidoGunguCode로 queryDsl BooleanBuilder 추가. * infra: Github actions를 사용한 CI (#36) * feat: 봉사 활동 기록 기능 구현 (#39) * feat: 유저 기록 기능 구현 * test: 유저 기록 기능 테스트 * fix: activity 관련 의존성 수정 --------- Co-authored-by: Awhn <69659322+Awhn@users.noreply.github.com> --- .github/workflows/dev-ci.yml | 26 ++++ .../history/constant/ActivityStatus.java | 16 +++ .../constant/ActivityStatusConverter.java | 29 +++++ .../history/controller/HistoryController.java | 35 ++++++ .../com/gamsa/history/domain/History.java | 37 ++++++ .../history/dto/HistoryFindSliceResponse.java | 30 +++++ .../gamsa/history/dto/HistorySaveRequest.java | 26 ++++ .../history/entity/HistoryJpaEntity.java | 59 +++++++++ .../repository/HistoryCustomRepository.java | 9 ++ .../HistoryCustomRepositoryImpl.java | 59 +++++++++ .../repository/HistoryJpaRepository.java | 8 ++ .../history/repository/HistoryRepository.java | 18 +++ .../repository/HistoryRepositoryImpl.java | 38 ++++++ .../gamsa/history/service/HistoryService.java | 56 +++++++++ .../history/entity/HistoryJpaEntityTest.java | 113 ++++++++++++++++++ .../repository/HistoryJpaRepositoryTest.java | 102 ++++++++++++++++ .../history/service/HitstoryServiceTest.java | 57 +++++++++ .../history/stub/StubHistoryRepository.java | 99 +++++++++++++++ 18 files changed, 817 insertions(+) create mode 100644 .github/workflows/dev-ci.yml create mode 100644 src/main/java/com/gamsa/history/constant/ActivityStatus.java create mode 100644 src/main/java/com/gamsa/history/constant/ActivityStatusConverter.java create mode 100644 src/main/java/com/gamsa/history/controller/HistoryController.java create mode 100644 src/main/java/com/gamsa/history/domain/History.java create mode 100644 src/main/java/com/gamsa/history/dto/HistoryFindSliceResponse.java create mode 100644 src/main/java/com/gamsa/history/dto/HistorySaveRequest.java create mode 100644 src/main/java/com/gamsa/history/entity/HistoryJpaEntity.java create mode 100644 src/main/java/com/gamsa/history/repository/HistoryCustomRepository.java create mode 100644 src/main/java/com/gamsa/history/repository/HistoryCustomRepositoryImpl.java create mode 100644 src/main/java/com/gamsa/history/repository/HistoryJpaRepository.java create mode 100644 src/main/java/com/gamsa/history/repository/HistoryRepository.java create mode 100644 src/main/java/com/gamsa/history/repository/HistoryRepositoryImpl.java create mode 100644 src/main/java/com/gamsa/history/service/HistoryService.java create mode 100644 src/test/java/com/gamsa/history/entity/HistoryJpaEntityTest.java create mode 100644 src/test/java/com/gamsa/history/repository/HistoryJpaRepositoryTest.java create mode 100644 src/test/java/com/gamsa/history/service/HitstoryServiceTest.java create mode 100644 src/test/java/com/gamsa/history/stub/StubHistoryRepository.java diff --git a/.github/workflows/dev-ci.yml b/.github/workflows/dev-ci.yml new file mode 100644 index 0000000..7d56331 --- /dev/null +++ b/.github/workflows/dev-ci.yml @@ -0,0 +1,26 @@ +name: Backend CI + +on: + pull_request: + branches: [ "develop", "week**" ] + +jobs: + build: + + runs-on: ubuntu-latest + permissions: + contents: read + + 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 diff --git a/src/main/java/com/gamsa/history/constant/ActivityStatus.java b/src/main/java/com/gamsa/history/constant/ActivityStatus.java new file mode 100644 index 0000000..ad88bbd --- /dev/null +++ b/src/main/java/com/gamsa/history/constant/ActivityStatus.java @@ -0,0 +1,16 @@ +package com.gamsa.history.constant; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum ActivityStatus { + APPLIED("접수"), + WAITING("활동 대기"), + ACT("활동"), + FINISHED("활동 완료"), + REVIEWED("리뷰 완료"); + + private final String name; +} diff --git a/src/main/java/com/gamsa/history/constant/ActivityStatusConverter.java b/src/main/java/com/gamsa/history/constant/ActivityStatusConverter.java new file mode 100644 index 0000000..0f9a451 --- /dev/null +++ b/src/main/java/com/gamsa/history/constant/ActivityStatusConverter.java @@ -0,0 +1,29 @@ +package com.gamsa.history.constant; + +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; + +import java.util.stream.Stream; + +@Converter +public class ActivityStatusConverter implements AttributeConverter { + + @Override + public String convertToDatabaseColumn(ActivityStatus activityStatus) { + if (activityStatus == null) { + return null; + } + return activityStatus.getName(); + } + + @Override + public ActivityStatus convertToEntityAttribute(String name) { + if (name == null) { + return null; + } + return Stream.of(ActivityStatus.values()) + .filter(category -> category.getName().equals(name)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 활동 상태 접근.")); + } +} diff --git a/src/main/java/com/gamsa/history/controller/HistoryController.java b/src/main/java/com/gamsa/history/controller/HistoryController.java new file mode 100644 index 0000000..1dac62f --- /dev/null +++ b/src/main/java/com/gamsa/history/controller/HistoryController.java @@ -0,0 +1,35 @@ +package com.gamsa.history.controller; + +import com.gamsa.history.dto.HistoryFindSliceResponse; +import com.gamsa.history.dto.HistorySaveRequest; +import com.gamsa.history.service.HistoryService; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RequiredArgsConstructor +@RestController +@RequestMapping("/api/v1/history") +public class HistoryController { + private HistoryService historyService; + + @PostMapping + public ResponseEntity addHistory(@RequestBody HistorySaveRequest saveRequest) { + historyService.save(saveRequest); + return new ResponseEntity<>(HttpStatus.CREATED); + } + + @GetMapping("{avatar-id}") + public Slice findSliceByUserId(@PathVariable("avatar-id") long avatarId, Pageable pageable) { + return historyService.findSliceByAvatarId(avatarId, pageable); + } + + @DeleteMapping("{history-id}") + public ResponseEntity deleteHistory(@PathVariable("history-id") long historyId) { + historyService.delete(historyId); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } +} diff --git a/src/main/java/com/gamsa/history/domain/History.java b/src/main/java/com/gamsa/history/domain/History.java new file mode 100644 index 0000000..661816e --- /dev/null +++ b/src/main/java/com/gamsa/history/domain/History.java @@ -0,0 +1,37 @@ +package com.gamsa.history.domain; + +import com.gamsa.activity.domain.Activity; +import com.gamsa.avatar.domain.Avatar; +import com.gamsa.history.constant.ActivityStatus; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +import java.time.LocalDateTime; + +@Getter +@Builder +@AllArgsConstructor +public class History { + private long historyId; + private Avatar avatar; + private Activity activity; + private ActivityStatus activityStatus; + private boolean reviewed; + + public void changeActivityStatusOnDate(LocalDateTime now) { + if ((this.activityStatus == ActivityStatus.APPLIED) + && (now.isAfter(activity.getNoticeEndDate()))) { + this.activityStatus = ActivityStatus.WAITING; + } else if ((this.activityStatus == ActivityStatus.WAITING) + && (now.isAfter(activity.getActStartDate()))) { + this.activityStatus = ActivityStatus.ACT; + } else if (now.isAfter(activity.getActEndDate())) { + this.activityStatus = ActivityStatus.FINISHED; + } + } + + public void changeReviewed(boolean reviewed) { + this.reviewed = reviewed; + } +} diff --git a/src/main/java/com/gamsa/history/dto/HistoryFindSliceResponse.java b/src/main/java/com/gamsa/history/dto/HistoryFindSliceResponse.java new file mode 100644 index 0000000..c01fb39 --- /dev/null +++ b/src/main/java/com/gamsa/history/dto/HistoryFindSliceResponse.java @@ -0,0 +1,30 @@ +package com.gamsa.history.dto; + +import com.gamsa.activity.dto.ActivityDetailResponse; +import com.gamsa.history.constant.ActivityStatus; +import com.gamsa.history.domain.History; +import lombok.Builder; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@Builder +@RequiredArgsConstructor +public class HistoryFindSliceResponse { + + private final long historyId; + private final long avatarId; + private final ActivityDetailResponse activity; + private final ActivityStatus activityStatus; + private final boolean reviewed; + + public static HistoryFindSliceResponse from(final History history) { + return HistoryFindSliceResponse.builder() + .historyId(history.getHistoryId()) + .avatarId(history.getAvatar().getAvatarId()) + .activityStatus(history.getActivityStatus()) + .reviewed(history.isReviewed()) + .activity(ActivityDetailResponse.from(history.getActivity())) + .build(); + } +} diff --git a/src/main/java/com/gamsa/history/dto/HistorySaveRequest.java b/src/main/java/com/gamsa/history/dto/HistorySaveRequest.java new file mode 100644 index 0000000..b1f5964 --- /dev/null +++ b/src/main/java/com/gamsa/history/dto/HistorySaveRequest.java @@ -0,0 +1,26 @@ +package com.gamsa.history.dto; + +import com.gamsa.activity.domain.Activity; +import com.gamsa.avatar.domain.Avatar; +import com.gamsa.history.constant.ActivityStatus; +import com.gamsa.history.domain.History; +import lombok.Builder; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@Builder +@RequiredArgsConstructor +public class HistorySaveRequest { + private final long avatarId; + private final long actId; + + public History toModel(Avatar avatar, Activity activity) { + return History.builder() + .activity(activity) + .avatar(avatar) + .activityStatus(ActivityStatus.APPLIED) + .reviewed(false) + .build(); + } +} diff --git a/src/main/java/com/gamsa/history/entity/HistoryJpaEntity.java b/src/main/java/com/gamsa/history/entity/HistoryJpaEntity.java new file mode 100644 index 0000000..b9f7e07 --- /dev/null +++ b/src/main/java/com/gamsa/history/entity/HistoryJpaEntity.java @@ -0,0 +1,59 @@ +package com.gamsa.history.entity; + + +import com.gamsa.activity.entity.ActivityJpaEntity; +import com.gamsa.avatar.entity.AvatarJpaEntity; +import com.gamsa.common.entity.BaseEntity; +import com.gamsa.history.constant.ActivityStatus; +import com.gamsa.history.constant.ActivityStatusConverter; +import com.gamsa.history.domain.History; +import jakarta.persistence.*; +import lombok.*; + +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Entity +@Table(name = "History") +public class HistoryJpaEntity extends BaseEntity { + @Id + @GeneratedValue() + @Column(name = "history_id") + private long historyId; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "avatar_id") + private AvatarJpaEntity avatar; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "activity_id") + private ActivityJpaEntity activity; + + @Convert(converter = ActivityStatusConverter.class) + @Column(name = "activity_status") + private ActivityStatus activityStatus; + + @Column(name = "reviewed") + private boolean reviewed; + + public static HistoryJpaEntity from(History history) { + return HistoryJpaEntity.builder() + .historyId(history.getHistoryId()) + .avatar(AvatarJpaEntity.from(history.getAvatar())) + .activity(ActivityJpaEntity.from(history.getActivity())) + .activityStatus(history.getActivityStatus()) + .reviewed(history.isReviewed()) + .build(); + } + + public History toModel() { + return History.builder() + .historyId(historyId) + .avatar(avatar.toModel()) + .activity(activity.toModel()) + .activityStatus(activityStatus) + .reviewed(reviewed) + .build(); + } +} diff --git a/src/main/java/com/gamsa/history/repository/HistoryCustomRepository.java b/src/main/java/com/gamsa/history/repository/HistoryCustomRepository.java new file mode 100644 index 0000000..d04ab24 --- /dev/null +++ b/src/main/java/com/gamsa/history/repository/HistoryCustomRepository.java @@ -0,0 +1,9 @@ +package com.gamsa.history.repository; + +import com.gamsa.history.entity.HistoryJpaEntity; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; + +public interface HistoryCustomRepository { + Slice findSliceByAvatarId(long avatarId, Pageable pageable); +} diff --git a/src/main/java/com/gamsa/history/repository/HistoryCustomRepositoryImpl.java b/src/main/java/com/gamsa/history/repository/HistoryCustomRepositoryImpl.java new file mode 100644 index 0000000..012feed --- /dev/null +++ b/src/main/java/com/gamsa/history/repository/HistoryCustomRepositoryImpl.java @@ -0,0 +1,59 @@ +package com.gamsa.history.repository; + +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 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; + + @Override + public Slice findSliceByAvatarId(long avatarId, Pageable pageable) { + List orders = getAllOrderSpecifiers(pageable); + + List results = jpaQueryFactory + .selectFrom(historyJpaEntity) + .where(historyJpaEntity.avatar.avatarId.eq(avatarId)) + .orderBy(orders.toArray(OrderSpecifier[]::new)) + .offset(pageable.getOffset()) + .limit(pageable.getOffset() + 1) + .fetch(); + + return checkLastPage(pageable, results); + } + + private Slice checkLastPage(Pageable pageable, List results) { + boolean hasNest = false; + if (results.size() > pageable.getPageSize()) { + hasNest = true; + results.remove(pageable.getPageSize()); + } + return new SliceImpl<>(results, pageable, hasNest); + } + + private List getAllOrderSpecifiers(Pageable pageable) { + List orders = new ArrayList<>(); + + pageable.getSort().stream() + .forEach(order -> { + Order direction = order.getDirection().isAscending() ? Order.ASC : Order.DESC; + String property = order.getProperty(); + PathBuilder orderPath = new PathBuilder(HistoryJpaEntity.class, "activityJpaEntity"); + orders.add(new OrderSpecifier(direction, orderPath.get(property))); + } + ); + return orders; + } +} diff --git a/src/main/java/com/gamsa/history/repository/HistoryJpaRepository.java b/src/main/java/com/gamsa/history/repository/HistoryJpaRepository.java new file mode 100644 index 0000000..c3dc2ca --- /dev/null +++ b/src/main/java/com/gamsa/history/repository/HistoryJpaRepository.java @@ -0,0 +1,8 @@ +package com.gamsa.history.repository; + +import com.gamsa.history.entity.HistoryJpaEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface HistoryJpaRepository extends JpaRepository, + HistoryCustomRepository { +} diff --git a/src/main/java/com/gamsa/history/repository/HistoryRepository.java b/src/main/java/com/gamsa/history/repository/HistoryRepository.java new file mode 100644 index 0000000..f2f0683 --- /dev/null +++ b/src/main/java/com/gamsa/history/repository/HistoryRepository.java @@ -0,0 +1,18 @@ +package com.gamsa.history.repository; + +import com.gamsa.history.domain.History; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; + +import java.util.Optional; + +public interface HistoryRepository { + + void save(History history); + + Optional findById(long id); + + void delete(History history); + + Slice findSliceByAvatarId(long avatarId, Pageable pageable); +} diff --git a/src/main/java/com/gamsa/history/repository/HistoryRepositoryImpl.java b/src/main/java/com/gamsa/history/repository/HistoryRepositoryImpl.java new file mode 100644 index 0000000..7832a47 --- /dev/null +++ b/src/main/java/com/gamsa/history/repository/HistoryRepositoryImpl.java @@ -0,0 +1,38 @@ +package com.gamsa.history.repository; + +import com.gamsa.history.domain.History; +import com.gamsa.history.entity.HistoryJpaEntity; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@RequiredArgsConstructor +@Repository +public class HistoryRepositoryImpl implements HistoryRepository { + private final HistoryJpaRepository historyJpaRepository; + + @Override + public void save(History history) { + historyJpaRepository.save(HistoryJpaEntity.from(history)); + } + + @Override + public Slice findSliceByAvatarId(long avatarId, Pageable pageable) { + return historyJpaRepository.findSliceByAvatarId(avatarId, pageable) + .map(entity -> entity.toModel()); + } + + @Override + public void delete(History history) { + historyJpaRepository.delete(HistoryJpaEntity.from(history)); + } + + @Override + public Optional findById(long id) { + return historyJpaRepository.findById(id) + .map(entity -> entity.toModel()); + } +} diff --git a/src/main/java/com/gamsa/history/service/HistoryService.java b/src/main/java/com/gamsa/history/service/HistoryService.java new file mode 100644 index 0000000..8bb326c --- /dev/null +++ b/src/main/java/com/gamsa/history/service/HistoryService.java @@ -0,0 +1,56 @@ +package com.gamsa.history.service; + +import com.gamsa.activity.domain.Activity; +import com.gamsa.activity.repository.ActivityRepository; +import com.gamsa.avatar.domain.Avatar; +import com.gamsa.avatar.repository.AvatarRepository; +import com.gamsa.history.domain.History; +import com.gamsa.history.dto.HistoryFindSliceResponse; +import com.gamsa.history.dto.HistorySaveRequest; +import com.gamsa.history.repository.HistoryRepository; +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 { + private final HistoryRepository historyRepository; + 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); + History history = saveRequest.toModel(avatar, activity); + historyRepository.save(history); + } + + public Slice findSliceByAvatarId(long avatarId, Pageable pageable) { + Slice histories = historyRepository.findSliceByAvatarId(avatarId, pageable); + histories.forEach(this::checkDate); + return histories.map(HistoryFindSliceResponse::from); + } + + public void delete(long historyId) { + History history = historyRepository.findById(historyId).orElseThrow(NoSuchElementException::new); + historyRepository.delete(history); + } + + public void updateReviewed(long historyId, boolean isReviewed) { + History history = historyRepository.findById(historyId).orElseThrow(NoSuchElementException::new); + history.changeReviewed(isReviewed); + historyRepository.save(history); + } + + public History checkDate(History history) { + LocalDateTime now = LocalDateTime.now(); + history.changeActivityStatusOnDate(now); + historyRepository.save(history); + return history; + } +} diff --git a/src/test/java/com/gamsa/history/entity/HistoryJpaEntityTest.java b/src/test/java/com/gamsa/history/entity/HistoryJpaEntityTest.java new file mode 100644 index 0000000..26f0594 --- /dev/null +++ b/src/test/java/com/gamsa/history/entity/HistoryJpaEntityTest.java @@ -0,0 +1,113 @@ +package com.gamsa.history.entity; + +import com.gamsa.activity.constant.Category; +import com.gamsa.activity.domain.Activity; +import com.gamsa.activity.domain.District; +import com.gamsa.activity.domain.Institute; +import com.gamsa.activity.entity.ActivityJpaEntity; +import com.gamsa.avatar.constant.AgeRange; +import com.gamsa.avatar.constant.Experienced; +import com.gamsa.avatar.domain.Avatar; +import com.gamsa.avatar.entity.AvatarJpaEntity; +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 java.math.BigDecimal; +import java.time.LocalDateTime; + +import static org.assertj.core.api.Assertions.assertThat; + +@Import(TestConfig.class) +public class HistoryJpaEntityTest { + // given + 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(); + + Avatar avatar = Avatar.builder() + .avatarId(1L) + .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(); + + //when + HistoryJpaEntity historyJpaEntity = HistoryJpaEntity.from(history); + + //then + assertThat(historyJpaEntity.getHistoryId()).isEqualTo(history.getHistoryId()); + + } + + @Test + void 엔티티에서_도메인으로() { + //given + HistoryJpaEntity historyJpaEntity = HistoryJpaEntity.builder() + .historyId(1L) + .activity(ActivityJpaEntity.from(activity)) + .avatar(AvatarJpaEntity.from(avatar)) + .activityStatus(ActivityStatus.APPLIED) + .reviewed(false) + .build(); + + //when + History history = historyJpaEntity.toModel(); + + //then + assertThat(history.getHistoryId()).isEqualTo(historyJpaEntity.getHistoryId()); + } +} diff --git a/src/test/java/com/gamsa/history/repository/HistoryJpaRepositoryTest.java b/src/test/java/com/gamsa/history/repository/HistoryJpaRepositoryTest.java new file mode 100644 index 0000000..9e3c050 --- /dev/null +++ b/src/test/java/com/gamsa/history/repository/HistoryJpaRepositoryTest.java @@ -0,0 +1,102 @@ +package com.gamsa.history.repository; + +import com.gamsa.activity.constant.Category; +import com.gamsa.activity.domain.Activity; +import com.gamsa.activity.domain.District; +import com.gamsa.activity.domain.Institute; +import com.gamsa.activity.entity.ActivityJpaEntity; +import com.gamsa.avatar.constant.AgeRange; +import com.gamsa.avatar.constant.Experienced; +import com.gamsa.avatar.domain.Avatar; +import com.gamsa.avatar.entity.AvatarJpaEntity; +import com.gamsa.common.config.TestConfig; +import com.gamsa.history.constant.ActivityStatus; +import com.gamsa.history.entity.HistoryJpaEntity; +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 { + @Autowired + private HistoryJpaRepository historyJpaRepository; + + 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 Avatar avatar = Avatar.builder() + .avatarId(1L) + .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(); + + + @Test + void 새_기록_저장() { + // when + historyJpaRepository.save(historyJpaEntity); + + //then + assertThat(historyJpaRepository.findById(1L).get().getHistoryId()) + .isEqualTo(historyJpaEntity.getHistoryId()); + } +} + + diff --git a/src/test/java/com/gamsa/history/service/HitstoryServiceTest.java b/src/test/java/com/gamsa/history/service/HitstoryServiceTest.java new file mode 100644 index 0000000..9c63fb3 --- /dev/null +++ b/src/test/java/com/gamsa/history/service/HitstoryServiceTest.java @@ -0,0 +1,57 @@ +package com.gamsa.history.service; + +import com.gamsa.activity.stub.StubExistsActivityRepository; +import com.gamsa.avatar.stub.StubAvatarRepository; +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()); + + //when & then + assertDoesNotThrow(() -> historyService.save(historySaveRequest)); + } + + @Test + void 유저_기록_찾기() { + //given + HistoryService historyService = new HistoryService(new StubHistoryRepository(), new StubAvatarRepository(), new StubExistsActivityRepository()); + + //when & then + Pageable pageable = PageRequest.of(0, 10); + assertThat(historyService.findSliceByAvatarId(1L, pageable)).isNotNull(); + } + + @Test + void 기록_삭제() { + //given + HistoryService historyService = new HistoryService(new StubHistoryRepository(), new StubAvatarRepository(), new StubExistsActivityRepository()); + + //when & then + assertDoesNotThrow(() -> historyService.delete(1L)); + } + + @Test + void 리뷰_상태_업데이트() { + //given + HistoryService historyService = new HistoryService(new StubHistoryRepository(), new StubAvatarRepository(), new StubExistsActivityRepository()); + + //when & then + assertDoesNotThrow(() -> historyService.updateReviewed(1L, true)); + } +} diff --git a/src/test/java/com/gamsa/history/stub/StubHistoryRepository.java b/src/test/java/com/gamsa/history/stub/StubHistoryRepository.java new file mode 100644 index 0000000..d98a270 --- /dev/null +++ b/src/test/java/com/gamsa/history/stub/StubHistoryRepository.java @@ -0,0 +1,99 @@ +package com.gamsa.history.stub; + +import com.gamsa.activity.constant.Category; +import com.gamsa.activity.domain.Activity; +import com.gamsa.activity.domain.District; +import com.gamsa.activity.domain.Institute; +import com.gamsa.avatar.constant.AgeRange; +import com.gamsa.avatar.constant.Experienced; +import com.gamsa.avatar.domain.Avatar; +import com.gamsa.history.constant.ActivityStatus; +import com.gamsa.history.domain.History; +import com.gamsa.history.repository.HistoryRepository; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.SliceImpl; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +public class StubHistoryRepository implements HistoryRepository { + 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(); + + Avatar avatar = Avatar.builder() + .avatarId(1L) + .avatarLevel(1L) + .avatarExp(1L) + .nickname("닉네임") + .ageRange(AgeRange.ADULT) + .experienced(Experienced.NOVICE) + .build(); + + private final History history = History.builder() + .historyId(1L) + .activity(activity) + .avatar(avatar) + .activityStatus(ActivityStatus.APPLIED) + .reviewed(false) + .build(); + + @Override + public void save(History history) { + } + + @Override + public void delete(History history) { + } + + @Override + public Optional findById(long id) { + return Optional.of(history); + } + + @Override + public Slice findSliceByAvatarId(long avatarId, Pageable pageable) { + return new SliceImpl<>(List.of(history)); + } +}