diff --git a/Gibeom/umc10th/.env b/Gibeom/umc10th/.env index 70eba936..43b455e5 100644 --- a/Gibeom/umc10th/.env +++ b/Gibeom/umc10th/.env @@ -1,3 +1,3 @@ -DB_USER=root +DB_USER=USER DB_PW=MY_PASSWORD_HERE -DB_URL=jdbc:mysql://localhost:3306/umc10th?serverTimezone=Asia/Seoul&allowPublicKeyRetrieval=true&useSSL=false \ No newline at end of file +DB_URL=MY_URL \ No newline at end of file diff --git a/Gibeom/umc10th/build.gradle b/Gibeom/umc10th/build.gradle index d7ef7dff..2314bead 100644 --- a/Gibeom/umc10th/build.gradle +++ b/Gibeom/umc10th/build.gradle @@ -10,7 +10,7 @@ description = 'umc10th' java { toolchain { - languageVersion = JavaLanguageVersion.of(17) + languageVersion = JavaLanguageVersion.of(26) } } @@ -21,6 +21,7 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-webmvc' + implementation 'org.springframework.boot:spring-boot-starter-security' compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.mysql:mysql-connector-j' annotationProcessor 'org.projectlombok:lombok' diff --git a/Gibeom/umc10th/feedback_summary/FeedBack_Study b/Gibeom/umc10th/feedback_summary/FeedBack_Study new file mode 100644 index 00000000..04d93ed2 --- /dev/null +++ b/Gibeom/umc10th/feedback_summary/FeedBack_Study @@ -0,0 +1,26 @@ +ResponseEntity +- 정의 : 스프링 프레임워크에서 제공하는 클래스. +- Http요청 또는 응답에 해당하는 HttpStatus, HttpHeader와 HttpBody를 포함하는 클래스 +- Http 응답의 주권을 가짐. 커스텀 가능 +- 상속 구현 클래스 (RequestBody, ResponseEntity) + +기존 상태 +- 클라이언트 측에서는 ApiResponse로 응답시 정해진 응답 (예 : 200이면 200, 500이면 500) +- 현재 ApiResponse 클래스는 정적 팩토리 메서드 형식. +- ApiResponse.java + ``` + //성공한 경우 + public static ApiResponse onSuccess(BaseSuccessCode code, T result){ + return new ApiResponse<>(true, code.getCode(), code.getMessage(), result); + } + ``` +기존 방식의 한계 +- 클라이언트 측은 먼저 http status를 확인하고 바디를 확인함 +- 네트워크 불일치 : ApiResponse만 반환하면 내부 로직이 실패하더라도 Http 상태코드는 기본(200)으로 나가는 경우가 많았음 +- 상세한 정보가 Json바디 안에 숨겨져 있어 클라이언트가 Http 표준방식으로 1차 판단을 내리기 어려웠음 +- 단순한 소통 : 200, 500 위주의 단순한 소통만 가능했고, 201, 204 등 을 활용 못했음 + +Why? +- API 사용자(프론트엔드 등등)는 응답바디 안의 Json뿐 아닌 브라우저나 앱이 수신하는 Status Code를 보고 1차 판단을 내림. +- 유연성 : ResponseEntity를 래핑해 감싼 응답을 보낸다면 커스텀 응답, 쿠키, 헤더 등 표준화된 응답을 유연하게 보낼 수 있음. +- ApiResponse의 데이터 규격을 유지하고 ResponseEntity로 감싸 네트워크 수준 상태코드로 명시적 관리 \ No newline at end of file diff --git a/Gibeom/umc10th/keyword_summary/ch05.md b/Gibeom/umc10th/keyword_summary/ch05.md deleted file mode 100644 index 80cbaf9f..00000000 --- a/Gibeom/umc10th/keyword_summary/ch05.md +++ /dev/null @@ -1,75 +0,0 @@ -- 빌더 패턴이란? - -빌더 패턴 - -- 복잡한 객체 생성 과정과 표현 방법을 분리해 다양한 구성의 인스턴스를 만드는 생성 패턴 -- 생성자에 들어갈 매개 변수를 메서드로 하나하나 받아들이고 마지막에 통합 빌드해서 객체를 생성하는 방식 - -사용 이유 - -- 생성자 오버로딩 방식은 비 효율적이기 때문 -- 일관성과 불변성 문제를 해결하기 위한 빌더 패턴 -- 메서드 체이닝 : 각 설정 메서드가 **빌더 객체 자신을 반환**하므로 연쇄적으로 코드작성 가능 → 가독성향상 -- - `.name("임기범").age(20).build();` 형식으로 생성 (기차처럼 파라미터를 길게 나열하기만 하면 된다) - -- record vs static class - record : 자바 16에서 도입된 데이터 전달을 위한 캐리어, 불변데이터를 저장하는 데 최적화된 클래스 - - 정의 : `필드, 생성자, equals(), hashCode(), toString()`등을 컴파일러가 자동으로 생성해주는 데이터 중심 클래스 - - 사용이유 : DTO를 만들 때 반복적으로 작성되는 보일러플레이트 코드를 줄이기 위함 - - 보일러플레이트코드 : 최소한의 변경으로 여러곳에서 반복적으로 재사용되는 코드구조, - - 장점 : 코드가 매우 간결해짐. 모든 필드가 final로 선언되어 불변성을 보장 - - 단점 : 다른 클래스를 상속할 수 없으며, 내부상태를 변경할 수 없음. - -static class : 클래스 내부에 선언되지만, 바깥 클래스의 인스턴스 없이 사용할 수 있는 클래스 - - 정의 : 외부 클래스의 멤버 공간에 static 키워드와 함께 선언된 클래스입니다. - - 사용이유 : 특정 클래스 안에서만 보조적으로 사용되는 클래스를 논리적으로 그룹화 해 캡슐화를 강화할 때 사용(예 : Builder 클래스) - - 장점 : 외부 클래스의 private 멤버에 접근할 수 있으며, 메모리 누수 위험이 있는 일반 내부 클래스와 달리 바깥 객체에 대한 참조를 유지하지 않음 - - 단점 : 테스트 용이성 저하 DI의 어려움. Mock 객체로 대체하기가 까다로움 - - Mock 객체 : 테스트 할 때 필요한 실제 객체와 동일한 모의객체를 만들어 테스트 효용을 높이기위해 사용 - -- 제네릭이란? - 클래스나 메소드에서 사용할 데이터 타입을 미리 확정하지 않고, 외부에서 지정할 수 있도록 일반화하는 기능 - 특정 타입이나 메소드에 얽매이지 않는 유연한 코드 생성이 가능 -사용 이유 - - - 타입 안정성 향상 : 잘못된 타입이 사용될 경우 컴파일러가 이를 즉시 감지해 런타임에 발생할 수 있는 잠재적인 예외를 사전에 방지. - - 형변환 최소화 : 제네릭을 사용하지 않으면 객체를 꺼낼 때마다 Object에서 원래 타입으로 캐스팅 해야함, 제네릭은 생략 가능 - - 코드 재사용성 증대 : 하나의 클래스나 메소드를 정의해 다양한 데이터 타입에 대응할 수 있어 타입마다 유사한 코드를 반복해 작성할 필요가 없음 . - --@RestControllerAdvice이란? - 스프링에서 전역적으로 예외를 처리하고 JSON/XML 형태의 응답을 반환할 수 있게 해주는 어노테이션. - - `@ControllerAdvice`와 `@ResponseBody`를 합친 것. - -사용 이유 - - - 코드 중복 제거 : 각 컨트롤러마다 작성하던 try-catch 로직을 한 곳으로 모을 수 있음 - - 관심사 분리 : 비즈니스 로직과 예외처리 로직을 분리해 코드 가독성을 높임 - - 일관된 응답 구조 : 에러발생시 사용자에게 항상 동일 형식의 JSON 에러 메시지를 내려줄 수 있음 - -기능 - - 예외처리 (`ExceoptionHandler`) : 특정 예외가 발생했을 때 실행할 메서드 정의 - --Optional 이란? -정의 : null일 가능성이 있는 객체를 직접 다루지 않고, 객체를 담는 상자인 Optional에 넣어 안전하게 처리 - -사용 이유 : NPE 방지 - - - 명시적 의도 표현 : 메서드 반환타입이 Optional이면 값이 없을수도 있으니 주의하라는 메시지를 전달 - - 가독성 : 복잡한 `if(obj ≠ null)` 조건을 줄여 코드를 간결히 만듦 - - 생성`Optional.of(value)` : 절대 null이 아닌 값으로 생성 (null이면 NPE 발생) - - 값 가져오기 (`orElse(T other)` ) : 값이 없으면 기본값(other) 반환 \ No newline at end of file diff --git a/Gibeom/umc10th/keyword_summary/ch07.md b/Gibeom/umc10th/keyword_summary/ch07.md new file mode 100644 index 00000000..701095b1 --- /dev/null +++ b/Gibeom/umc10th/keyword_summary/ch07.md @@ -0,0 +1,90 @@ +- Page와 Slice + + Spring Data JPA는 페이징을 위해 두가지 객체를 제공한다. Slice & Page + + DB에 저장된 데이터들을 페이지에 맞춰 몇개씩 뿌릴 건지 알려주는 것 + + - Page + - pageable 객체를 사용해 오프셋 기반 페이지네이션을 구현할 수 있는 객체. + - Slice를 상속함 + - Slice + - Page 보다 좀 더 추상적. + - 커서 기반 페이지네이션을 구현할 때 사용할 수 있는 객체 + - Response에 총 데이터 갯수는 보내지 않음 + - Pageable 객체 + - JPA가 제공하는 페이지네이션을 위한 객체 + - 페이징 정보(페이지 번호, 페이지 크기, 정렬방식 등..)를 담고 있는 인터페이스 + - 스프링 Data JPA에서 제공하는 `PageRequest` 클래스를 통해 인스턴스화 가능 + - 사용 이유 + - 모든 데이터를 한번에 뿌리게 되면 성능 저하 등 문제가 발생 + - 페이지네이션을 사용해 적절한 수의 정보만 올려주면 프론트에서 적절한 처리가능. +- Java stream API + + 자바가 지원하는 함수형 프로그래밍 방식. + + 함수형 프로그래밍이란? + + - 코드가 “어떻게” 에서 “무엇을”로 포커싱이 옮겨간 프로그래밍 방식 + - 함수형 프로그래밍에서 함수는 `Int` ,`String` 같은 1급 객체로 취급. + - 이 특성을 활용하는 고차함수 : map, filter, reduce 등… + - (기존 객체의 상태를 변경하지 않고, 새로운 결과를 반환) + - 사용 이유 : 가독성, 불변성, 유지보수성, 병렬 처리성 (쓰레드 풀) + + 단점 + + - for문 같은 단순 반복문에 익숙한 개발자들에게는 람다식과 스트림이 이해가 어려울 수 있음 + - 중단점을 걸어서 디버깅하기 어려움. + - for문 보다 오버헤드가 조금 더 큼. + + 병렬 처리 + + - 작업을 여러 스레드에게 분할해 병렬적으로 처리하는 방법 + - `parallelStram()` 키워드와 `ForkJoinPool()` 을 사용해 스레드 지정 가능 + - CommonPool : 미리 생성해 놓은 스레드를 모든 애플리케이션이 이용하는 방식의 풀 + - 장점 : 정해진 수의 스레드를 미리 생성해 놓고 사용하기 때문에 스레드 생성/삭제 오버헤드 적음. 적은 양의 처리에 유리 + - 단점 : 각각의 애플리케이션 마다 알맞은 처리를 제공하기 힘들 수 있음 + - (군대 배식 느낌) + - ThreadPool : 각각의 컴포넌트마다 설정할 수 있는 풀 + - 장점 : 성능 튜닝 가능 + - 메모리 관리 등 신경써야 할 것들이 있음. + - (오마카세 느낌) +- 객체 그래프 탐색 + + 객체 그래프 탐색 (Object Graph Navigation) + + - 객체지향언어에서 참조를 사용해 연관된 객체를 타고 들어가 데이터를 조회하는 방식 + - ex) 멤버 객체에서 member.getTeam()처럼 연관관계를 통해 팀 정보를 가져오는 방식 + + 특징 : 객체 간 연관관계를 통해 자유롭게 메모리상의 객체를 이동하며 탐색 + + 장점 : SQL 직접 작성 시 필요한 조인 제약에서 벗어나 논리적인 도메인 모델 구조에 따라 데이터 조회 가능 + + JPA에서의 활용 : JPA는 연관된 객체를 처음부터 로딩하지 않고 실제 사용 시점에 조회하는 지연로딩을 사용해 객체그래프 탐색을 지원 + + 주의점 : 객체 그래프를 무분별 탐색하는 경우, 하이버네이트 등에서 N+1 문제가 발생할 수 있음. + + **내 요약** + + - Store store = member.getReviewList().get(0).getStore()와 같은 코드가 있다고 가정. + 1. 멤버의 리뷰리스트를 가져오고 + 2. 0번 객체를 가져오고 + 3. 그 리뷰의 가게를 가져오는 흐름을 표현 가능. + - 여기서 문제 : 어디까지 조회가 가능한가? + - 실행시 모든 데이터를 올려둘 수는 없음. 그렇다면 사용할 때만 필요한 정보를 가져오는 방식 Lazy-Loading(지연로딩)을 사용해 `.` 을 사용해 다음 객체를 불러올때마다 데이터를 가져옴. + - 만약 객체간 연결이 없거나, 다음 데이터를 조회할 수 없다면 null이 조회되거나 오류가 날 것. +- @Valid vs @Validated + + @Valid + + - 컨트롤러 단에서 객체의 유효성을 검사할 때 사용 + - 계층 구조 검증이 가능 (User 안에 있는 Address 객체도 검사하고 싶으면 Address 필드 위에 @Valid 를 붙여야 함) + - 한계 : 그룹화 검증이 불가능 + + @Validated + + - 스프링에서 자바표준기능을 확장해 만든 어노테이션 + - 용도 : Service 나 Repository 등 Bean 계층에서 검증이 필요할 때, 혹은 그룹검증이 필요할 때 사용. + - 특징 + - 제약조건 그룹화 : “회원가입때는 이 필드 검사하고, 정보수정때는 하지마” 같은 상황을 `groups` 속성으로 지정가능 + - 클래스 레벨 선언 : 클래스 상단에 `@Validated` 어노테이션을 붙여야 해당 클래스 내부의 메서드 파라미터에 대한 검증이 작동 + - 한계 : 계층구조 검증은 직접적으로 지원하지 않아, 내부 객체에는 여전히 @Valid를 써야함. \ No newline at end of file diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/Umc10thApplication.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/Umc10thApplication.java index 9983f43a..9202ae84 100644 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/Umc10thApplication.java +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/Umc10thApplication.java @@ -2,8 +2,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; @SpringBootApplication +@EnableJpaAuditing public class Umc10thApplication { public static void main(String[] args) { diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/controller/MemberController.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/controller/MemberController.java index 837a480a..c61e09c2 100644 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/controller/MemberController.java +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/controller/MemberController.java @@ -1,26 +1,65 @@ package com.example.umc10th.domain.member.controller; -import com.example.umc10th.domain.member.dto.MemberReqDTO; import com.example.umc10th.domain.member.dto.MemberResDTO; +import com.example.umc10th.domain.member.enums.MissionStatus; import com.example.umc10th.domain.member.service.MemberService; +import com.example.umc10th.domain.mission.dto.MissionResDTO; import com.example.umc10th.global.apiPayload.ApiResponse; import com.example.umc10th.global.apiPayload.code.BaseSuccessCode; -import com.example.umc10th.global.apiPayload.code.MemberSuccessCode; +import com.example.umc10th.domain.member.exception.code.MemberSuccessCode; import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; +import java.util.List; + @RestController @RequiredArgsConstructor -@RequestMapping("/api") +@RequestMapping("/api/v1/members") public class MemberController { - private MemberService memberService; + private final MemberService memberService; //마이페이지 - @PostMapping("/v1/users/me") - public ApiResponse getInfo( - @RequestBody MemberReqDTO.GetInfo dto + @GetMapping("/me") + public ResponseEntity> getInfo( + @AuthenticationPrincipal Long memberId + ){ + BaseSuccessCode code = MemberSuccessCode.OK; + MemberResDTO.GetInfo result = memberService.getInfo(memberId); + return ResponseEntity + .status(code.getStatus()) + .body(ApiResponse.onSuccess(code, result)); + } + + // 홈화면 + @GetMapping("/home") + public ResponseEntity> getHome( + @AuthenticationPrincipal Long memberId, + @RequestParam(defaultValue = "0") int page ){ BaseSuccessCode code = MemberSuccessCode.OK; - return ApiResponse.onSuccess(code, memberService.getInfo(dto)); + MemberResDTO.HomeResultDto result = memberService.getHome(memberId, page); + return ResponseEntity + .status(code.getStatus()) + .body(ApiResponse.onSuccess(code, result)); } + + // 진행중/완료 미션 목록 조회 + @GetMapping("/missions") + public ResponseEntity>> getMissionsByStatus( + @AuthenticationPrincipal Long memberId, + @RequestParam MissionStatus status, + @RequestParam Integer pageSize, + @RequestParam Integer pageNum, + @RequestParam (required = false) String sort + ){ + BaseSuccessCode code = MemberSuccessCode.OK; + List result = memberService.getMissionsByStatus(memberId, status, pageSize, pageNum, sort); + return ResponseEntity + .status(code.getStatus()) + .body(ApiResponse.onSuccess(code, result)); + } + } diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/converter/MemberConverter.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/converter/MemberConverter.java index 2c6188e0..a7412b8f 100644 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/converter/MemberConverter.java +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/converter/MemberConverter.java @@ -1,4 +1,47 @@ package com.example.umc10th.domain.member.converter; +import com.example.umc10th.domain.member.dto.MemberReqDTO; +import com.example.umc10th.domain.member.dto.MemberResDTO; +import com.example.umc10th.domain.member.entity.Member; +import com.example.umc10th.domain.member.entity.mapping.MemberMission; + +import org.springframework.data.domain.Page; + +import java.util.List; +import java.util.stream.Collectors; + public class MemberConverter { + + public static MemberResDTO.GetInfo toGetInfo(Member member) { + return MemberResDTO.GetInfo.builder() + .nickname(member.getNickname()) + .email(member.getEmail()) + .phoneNumber(member.getPhoneNumber()) + .userPoint(member.getUserPoint()) + .build(); + } + + public static MemberResDTO.HomeResultDto toHomeResult(Member member, Page memberMissions) { + List missionDtos = memberMissions.getContent().stream() + .map(MemberConverter::toHomeMissionDto) + .collect(Collectors.toList()); + + return MemberResDTO.HomeResultDto.builder() + .point(member.getUserPoint()) + .region(member.getRegion() != null ? member.getRegion().getRegionName() : null) + .missions(missionDtos) + .hasNext(memberMissions.hasNext()) + .build(); + } + + public static MemberResDTO.HomeMissionDto toHomeMissionDto(MemberMission memberMission) { + return MemberResDTO.HomeMissionDto.builder() + .missionId(memberMission.getMission().getId()) + .storeName(null) + .storeCategory(null) + .rewardPoint(memberMission.getMission().getPoint()) + .deadline(null) + .status(memberMission.getStatus().name()) + .build(); + } } diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/dto/MemberReqDTO.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/dto/MemberReqDTO.java index fbda7128..943a141f 100644 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/dto/MemberReqDTO.java +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/dto/MemberReqDTO.java @@ -1,8 +1,5 @@ package com.example.umc10th.domain.member.dto; public class MemberReqDTO { - //마이페이지 - public record GetInfo( - Long id - ){} + } diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/dto/MemberResDTO.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/dto/MemberResDTO.java index 32bfd448..9da8ee56 100644 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/dto/MemberResDTO.java +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/dto/MemberResDTO.java @@ -2,14 +2,36 @@ import lombok.Builder; +import java.util.List; + public class MemberResDTO { + + //마이페이지 @Builder public record GetInfo( - String name, - String profileUrl, + String nickname, String email, String phoneNumber, - Integer point + Integer userPoint ){} + //홈 화면 + @Builder + public record HomeResultDto( + Integer point, + String region, + List missions, + boolean hasNext + ){} + + //홈 화면 내 미션 목록 + @Builder + public record HomeMissionDto( + Long missionId, + String storeName, + String storeCategory, + Integer rewardPoint, + Integer deadline, + String status + ){} } diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/entity/Member.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/entity/Member.java index eb718c35..1f7fca6b 100644 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/entity/Member.java +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/entity/Member.java @@ -1,4 +1,55 @@ package com.example.umc10th.domain.member.entity; -public class Member { +import com.example.umc10th.domain.member.entity.mapping.MemberFood; +import com.example.umc10th.domain.member.enums.Gender; +import com.example.umc10th.domain.store.entity.Region; +import com.example.umc10th.global.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Table(name = "Member") +public class Member extends BaseEntity{ + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE) + private Long id; + + @Column(name = "name") + private String name; + + @Column(name = "gender", nullable = false) + @Enumerated(EnumType.STRING) + @Builder.Default + //EnumType.STRING DB에 문자열을 저장 + //EnumType.ORDINAL : Enum의 순서를 DB에 저장 , 거의 안씀 + private Gender gender = Gender.MALE; + + @Column(name = "nickname", nullable = false) + private String nickname; + + @Column(name = "phoneNumber", nullable = false) + private String phoneNumber; + + @Column(name = "user_point") + private Integer userPoint; + + @Column(name = "email") + private String email; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "region_id") + private Region region; + + @OneToMany(mappedBy = "member", cascade = CascadeType.REMOVE) + private List memberFoodList = new ArrayList<>(); } diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/entity/mapping/MemberFood.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/entity/mapping/MemberFood.java index c3ebc38d..b6804c06 100644 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/entity/mapping/MemberFood.java +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/entity/mapping/MemberFood.java @@ -1,4 +1,30 @@ package com.example.umc10th.domain.member.entity.mapping; +import com.example.umc10th.domain.member.entity.Member; +import com.example.umc10th.domain.store.entity.Food; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Table(name = "member_food") public class MemberFood { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id") + private Member member; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "food_id") + private Food food; } diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/entity/mapping/MemberMission.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/entity/mapping/MemberMission.java index 37de9874..b25d5806 100644 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/entity/mapping/MemberMission.java +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/entity/mapping/MemberMission.java @@ -1,4 +1,47 @@ package com.example.umc10th.domain.member.entity.mapping; +import com.example.umc10th.domain.member.entity.Member; +import com.example.umc10th.domain.member.enums.MissionStatus; +import com.example.umc10th.domain.mission.entity.Mission; +import com.example.umc10th.domain.review.entity.Review; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Table(name = "member_mission") public class MemberMission { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id") + private Member member; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "mission_id") + private Mission mission; + + private MissionStatus status; + + @Column(nullable = false) + private Boolean isRewarded; + + @Column(name = "low_price_limit") + private Integer lowPriceLimit; + + @Column(name = "deadline") + private Integer deadline; + + @OneToOne(mappedBy = "memberMission") + private Review review; + } diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/enums/Gender.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/enums/Gender.java index dc0a5232..3dc5d76a 100644 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/enums/Gender.java +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/enums/Gender.java @@ -1,5 +1,7 @@ package com.example.umc10th.domain.member.enums; public enum Gender { - + MALE, + FEMALE, + NONE; } diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/enums/MissionStatus.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/enums/MissionStatus.java index f55b2e40..4ec0ce75 100644 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/enums/MissionStatus.java +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/enums/MissionStatus.java @@ -1,4 +1,7 @@ package com.example.umc10th.domain.member.enums; public enum MissionStatus { + ACTIVE, + INACTIVE, + COMPLETED } diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/exception/MemberException.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/exception/MemberException.java new file mode 100644 index 00000000..8e7607bc --- /dev/null +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/exception/MemberException.java @@ -0,0 +1,11 @@ +package com.example.umc10th.domain.member.exception; + +import com.example.umc10th.global.apiPayload.Exception.ProjectException; +import com.example.umc10th.global.apiPayload.code.BaseErrorCode; + +public class MemberException extends ProjectException { + + public MemberException(BaseErrorCode errorCode) { + super(errorCode); + } +} diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/global/apiPayload/code/MemberErrorCode.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/exception/code/MemberErrorCode.java similarity index 77% rename from Gibeom/umc10th/src/main/java/com/example/umc10th/global/apiPayload/code/MemberErrorCode.java rename to Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/exception/code/MemberErrorCode.java index ae497aef..98c24940 100644 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/global/apiPayload/code/MemberErrorCode.java +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/exception/code/MemberErrorCode.java @@ -1,5 +1,6 @@ -package com.example.umc10th.global.apiPayload.code; +package com.example.umc10th.domain.member.exception.code; +import com.example.umc10th.global.apiPayload.code.BaseErrorCode; import lombok.Getter; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; @@ -11,6 +12,7 @@ public enum MemberErrorCode implements BaseErrorCode { "COMMON404_1", "해당 사용자를 찾을 수 없습니다." ), + ; private final HttpStatus status; private final String code; diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/global/apiPayload/code/MemberSuccessCode.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/exception/code/MemberSuccessCode.java similarity index 61% rename from Gibeom/umc10th/src/main/java/com/example/umc10th/global/apiPayload/code/MemberSuccessCode.java rename to Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/exception/code/MemberSuccessCode.java index 59bda436..5a5c3362 100644 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/global/apiPayload/code/MemberSuccessCode.java +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/exception/code/MemberSuccessCode.java @@ -1,5 +1,6 @@ -package com.example.umc10th.global.apiPayload.code; +package com.example.umc10th.domain.member.exception.code; +import com.example.umc10th.global.apiPayload.code.BaseSuccessCode; import lombok.Getter; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; @@ -9,7 +10,12 @@ public enum MemberSuccessCode implements BaseSuccessCode { OK(HttpStatus.OK, "MEMBER200_1", "성공적으로 유저를 조회했습니다."), + + SIGNUP_SUCCESS(HttpStatus.CREATED, + "MEMBER_201_1", + "회원가입이 성공적으로 완료되었습니다.") ; + private final HttpStatus status; private final String code; private final String message; diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/repository/MemberMissionRepository.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/repository/MemberMissionRepository.java new file mode 100644 index 00000000..8415eda3 --- /dev/null +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/repository/MemberMissionRepository.java @@ -0,0 +1,23 @@ +package com.example.umc10th.domain.member.repository; + +import com.example.umc10th.domain.member.entity.mapping.MemberMission; +import com.example.umc10th.domain.member.enums.MissionStatus; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; +import java.util.Optional; + +public interface MemberMissionRepository extends JpaRepository { + List findAllByMember_IdAndStatus(Long memberId, MissionStatus missionStatus); + Page findAllByMember_IdAndStatus(Long memberId, MissionStatus missionStatus, Pageable pageable); + Optional findByMember_IdAndMission_Id(Long memberId, Long missionId); + + @Query("SELECT mm FROM MemberMission mm WHERE mm.member.id = :memberId AND mm.status = :status") + Page findActiveMissions(@Param("memberId") Long memberId, @Param("status") MissionStatus status, Pageable pageable); + + +} diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/repository/MemberRepository.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/repository/MemberRepository.java index d6c37352..054ad34f 100644 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/repository/MemberRepository.java +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/repository/MemberRepository.java @@ -1,4 +1,7 @@ package com.example.umc10th.domain.member.repository; -public interface MemberRepository { +import com.example.umc10th.domain.member.entity.Member; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface MemberRepository extends JpaRepository { } diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/service/MemberService.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/service/MemberService.java index 24f3bbfb..70b5aae5 100644 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/service/MemberService.java +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/service/MemberService.java @@ -1,5 +1,68 @@ package com.example.umc10th.domain.member.service; +import com.example.umc10th.domain.member.converter.MemberConverter; +import com.example.umc10th.domain.member.dto.MemberReqDTO; +import com.example.umc10th.domain.member.dto.MemberResDTO; +import com.example.umc10th.domain.member.entity.Member; +import com.example.umc10th.domain.member.entity.mapping.MemberMission; +import com.example.umc10th.domain.member.enums.MissionStatus; +import com.example.umc10th.domain.member.exception.MemberException; +import com.example.umc10th.domain.member.exception.code.MemberErrorCode; +import com.example.umc10th.domain.member.repository.MemberMissionRepository; +import com.example.umc10th.domain.member.repository.MemberRepository; +import com.example.umc10th.domain.mission.converter.MissionConverter; +import com.example.umc10th.domain.mission.dto.MissionResDTO; +import com.example.umc10th.domain.mission.entity.Mission; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor public class MemberService { + private final MemberRepository memberRepository; + private final MemberMissionRepository memberMissionRepository; + + public MemberResDTO.GetInfo getInfo(Long memberId) { + Member member = memberRepository.findById(memberId) + .orElseThrow(() -> new MemberException(MemberErrorCode.NOT_FOUND)); + return MemberConverter.toGetInfo(member); + } + + //홈화면 (진행중인 미션 10개씩 페이징) + public MemberResDTO.HomeResultDto getHome(Long memberId, int page) { + Member member = memberRepository.findById(memberId) + .orElseThrow(() -> new MemberException(MemberErrorCode.NOT_FOUND)); + Page activeMissions = memberMissionRepository + .findActiveMissions(memberId, MissionStatus.ACTIVE, PageRequest.of(page,10)); + return MemberConverter.toHomeResult(member, activeMissions); + } + // Mission 조회 + public List getMissionsByStatus( + Long memberId, + MissionStatus status, + Integer pageSize, + Integer pageNum, + String sort + ) { + Sort sortInfo; + if (sort != null){ + sortInfo = Sort.by(sort); + } else { + sortInfo = Sort.by("id").descending(); + } + PageRequest pageRequest = PageRequest.of(pageNum, pageSize, sortInfo); + Page memberMissions = memberMissionRepository + .findAllByMember_IdAndStatus(memberId, status, pageRequest); + List missions = memberMissions.stream() + .map(MemberMission::getMission) + .collect(Collectors.toList()); + return MissionConverter.toMissionDtoList(missions); + } } diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/mission/controller/MissionController.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/mission/controller/MissionController.java index 123c9329..b784e565 100644 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/mission/controller/MissionController.java +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/mission/controller/MissionController.java @@ -1,4 +1,85 @@ package com.example.umc10th.domain.mission.controller; +import com.example.umc10th.domain.mission.dto.MissionReqDTO; +import com.example.umc10th.domain.mission.dto.MissionResDTO; +import com.example.umc10th.domain.mission.exception.code.MissionSuccessCode; +import com.example.umc10th.domain.mission.service.MissionService; +import com.example.umc10th.domain.review.dto.ReviewReqDTO; +import com.example.umc10th.domain.review.dto.ReviewResDTO; +import com.example.umc10th.domain.review.exception.code.ReviewSuccessCode; +import com.example.umc10th.global.apiPayload.ApiResponse; +import com.example.umc10th.global.apiPayload.code.BaseSuccessCode; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api") public class MissionController { + + private final MissionService missionService; + + + //리뷰 작성 (완료된 미션에 한해서) + @PostMapping("v1/missions/{missionId}/reviews") + public ResponseEntity> writeReview( + @PathVariable Long missionId, + @AuthenticationPrincipal Long memberId, + @RequestBody ReviewReqDTO.WriteReviewDto dto) { + BaseSuccessCode code = ReviewSuccessCode.WRITE_SUCCESS; + ReviewResDTO.WriteReviewResultDto result = missionService.writeReview(memberId, missionId, dto); + return ResponseEntity + .status(code.getStatus()) + .body(ApiResponse.onSuccess(code, result)); + } + + //가게 미션 생성 + @PostMapping("v1/stores/{storeId}/missions") + public ResponseEntity> createMission( + @PathVariable Long storeId, + @RequestBody @Valid MissionReqDTO.CreateMission dto + ){ + MissionSuccessCode code = MissionSuccessCode.CREATED; + MissionResDTO.CreateMissionResult result = missionService.createMission(storeId, dto); + return ResponseEntity + .status(code.getStatus()) + .body(ApiResponse.onSuccess(code, result)); + } + + //가게 미션들 조회 (오프셋 기반) + @GetMapping("v1/stores/{storeId}/missions") + public ResponseEntity>> getMissions( + @PathVariable Long storeId, + @RequestParam Integer pageSize, + @RequestParam Integer pageNumber, + @RequestParam(required = false) String sort + ){ + BaseSuccessCode code = MissionSuccessCode.OK; + Page result = missionService.getMissions(storeId, pageSize, pageNumber, sort); + return ResponseEntity + .status(code.getStatus()) + .body(ApiResponse.onSuccess(code, result)); + } + + //내가 진행중인 미션 조회하기 + @GetMapping("v1/members/{memberId}/missions") + public ResponseEntity>> getMemberMissions( + @PathVariable Long memberId, + @RequestParam Integer pageSize, + @RequestParam Integer pageNumber, + @RequestParam(required = false) String sort + ){ + BaseSuccessCode code = MissionSuccessCode.OK; + MissionResDTO.Pagination result = missionService.getMemberMissions(memberId, pageSize, pageNumber, sort); + return ResponseEntity + .status(HttpStatus.OK) + .body(ApiResponse.onSuccess(code, result)); + } } diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/mission/converter/MissionConverter.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/mission/converter/MissionConverter.java index 336f76a4..ec65420a 100644 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/mission/converter/MissionConverter.java +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/mission/converter/MissionConverter.java @@ -1,4 +1,73 @@ package com.example.umc10th.domain.mission.converter; +import com.example.umc10th.domain.mission.dto.MissionReqDTO; +import com.example.umc10th.domain.mission.dto.MissionResDTO; +import com.example.umc10th.domain.mission.entity.Mission; +import com.example.umc10th.domain.store.entity.Store; + +import java.util.List; +import java.util.stream.Collectors; + public class MissionConverter { + + public static MissionResDTO.MissionDto toMissionDto(Mission mission) { + return MissionResDTO.MissionDto.builder() + .missionId(mission.getId()) + .title(mission.getTitle()) + .rewardPoint(mission.getPoint()) + .build(); + } + + public static List toMissionDtoList(List missions) { + return missions.stream() + .map(MissionConverter::toMissionDto) + .collect(Collectors.toList()); + } + + //가게 미션 생성 + public static Mission toMission( + Store store, + MissionReqDTO.CreateMission dto + ){ + return Mission.builder() + .store(store) + .conditional(dto.conditional()) + .point(dto.point()) + .deadline(dto.deadLine()) + .build(); + } + //가게 내 미션 조회 + public static MissionResDTO.GetMission toGetMission( + Mission mission + ){ + return MissionResDTO.GetMission.builder() + .conditional(mission.getConditional()) + .point(mission.getPoint()) + .missionId(mission.getId()) + .build(); + } + + //페이지 네이션 틀 생성 + public static MissionResDTO.Pagination toPagination( + List data, + Integer pageNumber, + Integer pageSize + ){ + return MissionResDTO.Pagination.builder() + .data(data) + .pageNumber(pageNumber) + .pageSize(pageSize) + .build(); + } + + public static MissionResDTO.CreateMissionResult toCreateMissionResult( + Mission mission + ){ + return MissionResDTO.CreateMissionResult.builder() + .conditional(mission.getConditional()) + .point(mission.getPoint()) + .missionId(mission.getId()) + .deadline(mission.getDeadline()) + .build(); + } } diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/mission/dto/MissionReqDTO.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/mission/dto/MissionReqDTO.java index e33508b9..73156036 100644 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/mission/dto/MissionReqDTO.java +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/mission/dto/MissionReqDTO.java @@ -1,4 +1,19 @@ package com.example.umc10th.domain.mission.dto; +import com.example.umc10th.domain.member.enums.MissionStatus; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +import java.time.LocalDate; + public class MissionReqDTO { + //가게 미션 생성 + public record CreateMission( + @NotNull(message = "마감기한은 필수입니다.") + LocalDate deadLine, + @NotNull(message = "미션 성공 포인트는 필수입니다.") + Integer point, + @NotBlank(message = "조건은 빈칸일 수 없습니다.") + String conditional + ){} } diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/mission/dto/MissionResDTO.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/mission/dto/MissionResDTO.java index 7fc76975..e25087af 100644 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/mission/dto/MissionResDTO.java +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/mission/dto/MissionResDTO.java @@ -1,4 +1,42 @@ package com.example.umc10th.domain.mission.dto; +import com.example.umc10th.domain.member.enums.MissionStatus; +import lombok.Builder; + +import java.time.LocalDate; +import java.util.List; + public class MissionResDTO { + + @Builder + public record MissionDto( + Long missionId, + String title, + Integer rewardPoint + ) {} + + //가게 내 미션 조회 + @Builder + public record GetMission( + Long missionId, + Integer point, + String conditional + ){} + + //페이지네이션 틀 + @Builder + public record Pagination( + List data, + Integer pageNumber, + Integer pageSize + ){} + + //생성된 미션 정보 반환 DTO + @Builder + public record CreateMissionResult( + Long missionId, + String conditional, + Integer point, + LocalDate deadline + ){} } diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/mission/entity/Mission.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/mission/entity/Mission.java index 1dbae7cf..e6055599 100644 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/mission/entity/Mission.java +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/mission/entity/Mission.java @@ -1,4 +1,52 @@ package com.example.umc10th.domain.mission.entity; -public class Mission { +import com.example.umc10th.domain.store.entity.Store; +import com.example.umc10th.domain.store.entity.mapping.StoreMission; +import com.example.umc10th.global.entity.BaseEntity; +import com.example.umc10th.domain.member.entity.mapping.MemberMission; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; + +@Entity +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Table(name = "mission") +public class Mission extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "title") + private String title; + + @Column(name = "conditional") + private String conditional; + + @OneToMany(mappedBy = "mission", cascade = CascadeType.REMOVE) + @Builder.Default + private List storeMissionList = new ArrayList<>(); + + @Column(name = "point", nullable = false) + private Integer point; + + @Column(name = "deadline") + private LocalDate deadline; + + @OneToMany(mappedBy = "mission", cascade = CascadeType.REMOVE) + @Builder.Default + private List memberMissionList = new ArrayList<>(); + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "store_id") + private Store store; } diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/mission/exception/MemberException.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/mission/exception/MemberException.java deleted file mode 100644 index f3c8ccc1..00000000 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/mission/exception/MemberException.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.example.umc10th.domain.mission.exception; - -public class MemberException extends RuntimeException { - public MemberException(String message) { - super(message); - } -} diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/mission/exception/MissionException.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/mission/exception/MissionException.java new file mode 100644 index 00000000..d0c786bb --- /dev/null +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/mission/exception/MissionException.java @@ -0,0 +1,11 @@ +package com.example.umc10th.domain.mission.exception; + +import com.example.umc10th.global.apiPayload.Exception.ProjectException; +import com.example.umc10th.global.apiPayload.code.BaseErrorCode; + +public class MissionException extends ProjectException { + + public MissionException(BaseErrorCode errorCode) { + super(errorCode); + } +} diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/mission/exception/code/MissionErrorCode.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/mission/exception/code/MissionErrorCode.java index ed23a100..1474f69d 100644 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/mission/exception/code/MissionErrorCode.java +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/mission/exception/code/MissionErrorCode.java @@ -1,4 +1,28 @@ -package com.example.umc10th.domain.mission; +package com.example.umc10th.domain.mission.exception.code; -public enum MissionErrorCode { -} \ No newline at end of file +import com.example.umc10th.global.apiPayload.code.BaseErrorCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@Getter +@RequiredArgsConstructor +public enum MissionErrorCode implements BaseErrorCode { + + NOT_FOUND(HttpStatus.NOT_FOUND, + "MISSION404_1", + "해당 가게를 찾을 수 없습니다."), + + ALREADY_CHALLENGED(HttpStatus.CONFLICT, + "MISSION409_1", + "이미 도전 중인 미션입니다."), + + NOT_COMPLETED(HttpStatus.BAD_REQUEST, + "MISSION400_1", + "완료되지 않은 미션입니다."), + ; + + private final HttpStatus status; + private final String code; + private final String message; +} diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/mission/exception/code/MissionSuccessCode.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/mission/exception/code/MissionSuccessCode.java index 3db58c9a..b723c1e8 100644 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/mission/exception/code/MissionSuccessCode.java +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/mission/exception/code/MissionSuccessCode.java @@ -1,2 +1,31 @@ -public enum MissionSuccessCode { +package com.example.umc10th.domain.mission.exception.code; + +import com.example.umc10th.global.apiPayload.code.BaseSuccessCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@Getter +@RequiredArgsConstructor +public enum MissionSuccessCode implements BaseSuccessCode { + + OK(HttpStatus.OK, + "MISSION200_1", + "성공적으러 미션을 조회했습니다."), + + CHALLENGE_SUCCESS(HttpStatus.CREATED, + "MISSION201_1", + "미션 도전이 성공적으로 완료되었습니다."), + CHALLENGE_UPDATE(HttpStatus.OK, + "MISSION200_2", + "미션 상태가 성공적으로 변경되었습니다." + ), + CREATED(HttpStatus.CREATED, + "MISSION200_3", + "성공적으로 미션을 생성했습니다.") + ; + + private final HttpStatus status; + private final String code; + private final String message; } diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/mission/repository/MissionRepository.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/mission/repository/MissionRepository.java index 3f7d19d8..306aa3c7 100644 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/mission/repository/MissionRepository.java +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/mission/repository/MissionRepository.java @@ -1,4 +1,19 @@ package com.example.umc10th.domain.mission.repository; -public interface MissionRepository { +import com.example.umc10th.domain.mission.dto.MissionResDTO; +import com.example.umc10th.domain.mission.entity.Mission; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface MissionRepository extends JpaRepository { + Page findAllByStore_Id(Long storeId, Pageable pageable); + + // MissionRepository.java + @Query("SELECT m FROM Mission m JOIN m.memberMissionList mm WHERE mm.member.id = :memberId") + Page findAllByMember_Id(@Param("memberId") Long memberId, Pageable pageable); } diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/mission/service/MissionService.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/mission/service/MissionService.java index d69ce745..d4692a8b 100644 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/mission/service/MissionService.java +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/mission/service/MissionService.java @@ -1,4 +1,139 @@ package com.example.umc10th.domain.mission.service; +import com.example.umc10th.domain.member.entity.Member; +import com.example.umc10th.domain.member.entity.mapping.MemberMission; +import com.example.umc10th.domain.member.enums.MissionStatus; +import com.example.umc10th.domain.member.exception.MemberException; +import com.example.umc10th.domain.member.exception.code.MemberErrorCode; +import com.example.umc10th.domain.mission.converter.MissionConverter; +import com.example.umc10th.domain.mission.dto.MissionReqDTO; +import com.example.umc10th.domain.mission.dto.MissionResDTO; +import com.example.umc10th.domain.mission.entity.Mission; +import com.example.umc10th.domain.mission.exception.MissionException; +import com.example.umc10th.domain.mission.exception.code.MissionErrorCode; +import com.example.umc10th.domain.member.repository.MemberMissionRepository; +import com.example.umc10th.domain.member.repository.MemberRepository; +import com.example.umc10th.domain.mission.repository.MissionRepository; +import com.example.umc10th.domain.review.converter.ReviewConverter; +import com.example.umc10th.domain.review.dto.ReviewReqDTO; +import com.example.umc10th.domain.review.dto.ReviewResDTO; +import com.example.umc10th.domain.review.entity.Review; +import com.example.umc10th.domain.review.exception.ReviewException; +import com.example.umc10th.domain.review.exception.code.ReviewErrorCode; +import com.example.umc10th.domain.review.repository.ReviewRepository; +import com.example.umc10th.domain.store.entity.Store; +import com.example.umc10th.domain.store.exception.StoreException; +import com.example.umc10th.domain.store.exception.code.StoreErrorCode; +import com.example.umc10th.domain.store.repository.StoreRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@RequiredArgsConstructor public class MissionService { + + private final MemberMissionRepository memberMissionRepository; + private final MemberRepository memberRepository; + private final ReviewRepository reviewRepository; + private final StoreRepository storeRepository; + private final MissionRepository missionRepository; + + //완료된 미션 + @Transactional + public ReviewResDTO.WriteReviewResultDto writeReview(Long memberId, Long missionId, ReviewReqDTO.WriteReviewDto dto) { + Member member = memberRepository.findById(memberId) + .orElseThrow(() -> new MemberException(MemberErrorCode.NOT_FOUND)); + + MemberMission memberMission = memberMissionRepository + .findByMember_IdAndMission_Id(memberId, missionId) + .orElseThrow(() -> new MissionException(MissionErrorCode.NOT_FOUND)); + + if (memberMission.getStatus() != MissionStatus.COMPLETED) { + throw new MissionException(MissionErrorCode.NOT_COMPLETED); + } + + if (memberMission.getReview() != null) { + throw new ReviewException(ReviewErrorCode.ALREADY_REVIEWED); + } + + Review review = ReviewConverter.toReview(dto, member, memberMission); + Review savedReview = reviewRepository.save(review); + return ReviewConverter.toWriteReviewResult(savedReview); + } + + //가게 미션 생성 + @Transactional + public MissionResDTO.CreateMissionResult createMission( + Long storeId, + MissionReqDTO.CreateMission dto + ){ + //가게 찾기 + Store store = storeRepository.findById(storeId) + .orElseThrow(() -> new StoreException(StoreErrorCode.NOT_FOUND)); + //미션 생성 + Mission mission = MissionConverter.toMission(store, dto); + + //미션 DB에 미션 저장 + missionRepository.save(mission); + return MissionConverter.toCreateMissionResult(mission); + } + + //가게 미션 조회 + public Page getMissions( + Long storeId, + Integer pageSize, + Integer pageNumber, + String sort + ){ + //정렬정보 생성 + Sort sortInfo; + if (sort != null){ + sortInfo = Sort.by(sort); + } else { + sortInfo = Sort.by("id").descending(); + } + + //페이지 정보들을 PageRequest로 만들기 + PageRequest pageRequest = PageRequest.of(pageNumber, pageSize, sortInfo); + + //가게 내 미션들 조회 + Page missionList = missionRepository.findAllByStore_Id(storeId, pageRequest); + + return missionList.map(MissionConverter::toGetMission); + } + + //유저가 진행중인 미션 조회하기 + public MissionResDTO.Pagination getMemberMissions( + Long memberId, + Integer pageSize, + Integer pageNumber, + String sort + ){ + // 정렬 정보 + Sort sortInfo; + if (sort != null){ + sortInfo = Sort.by(sort); + } else { + sortInfo = Sort.by("id").descending(); + } + + //페이지 정보 PageRequest로 만들기 + PageRequest pageRequest = PageRequest.of(pageNumber, pageSize, sortInfo); + + //내 미션들 조회 {memberId} + Page missionList = missionRepository.findAllByMember_Id(memberId, pageRequest); + return MissionConverter.toPagination( + missionList.map(MissionConverter::toGetMission).toList(), + missionList.getNumber(), + missionList.getSize() + ); + } + + } diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/review/controller/ReviewController.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/review/controller/ReviewController.java index 71b8fa76..e78133cb 100644 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/review/controller/ReviewController.java +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/review/controller/ReviewController.java @@ -1,4 +1,39 @@ package com.example.umc10th.domain.review.controller; +import com.example.umc10th.domain.review.dto.ReviewReqDTO; +import com.example.umc10th.domain.review.dto.ReviewResDTO; +import com.example.umc10th.domain.review.exception.code.ReviewSuccessCode; +import com.example.umc10th.domain.review.service.ReviewService; +import com.example.umc10th.global.apiPayload.ApiResponse; +import com.example.umc10th.global.apiPayload.code.BaseSuccessCode; +import lombok.RequiredArgsConstructor; +import org.apache.coyote.Request; +import org.springframework.data.domain.Page; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api") public class ReviewController { + + private final ReviewService reviewService; + + //유저의 모든 리뷰 가져오기 (Cursor) + @GetMapping("/v1/members/{memberId}/reviews") + public ResponseEntity>> getMemberReviews( + @PathVariable Long memberId, + @RequestParam Integer pageSize, + @RequestParam String cursor, + @RequestParam String query + ) { + BaseSuccessCode code = ReviewSuccessCode.OK; + ReviewResDTO.Pagination result = reviewService.getMemberReviewsOrderByScore(memberId, pageSize, cursor, query); + return ResponseEntity + .status(code.getStatus()) + .body(ApiResponse.onSuccess(code, result)); + } + } diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/review/converter/ReviewConverter.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/review/converter/ReviewConverter.java index c2fd1816..0fefa599 100644 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/review/converter/ReviewConverter.java +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/review/converter/ReviewConverter.java @@ -1,4 +1,71 @@ package com.example.umc10th.domain.review.converter; +import com.example.umc10th.domain.member.entity.Member; +import com.example.umc10th.domain.member.entity.mapping.MemberMission; +import com.example.umc10th.domain.review.dto.ReviewReqDTO; +import com.example.umc10th.domain.review.dto.ReviewResDTO; +import com.example.umc10th.domain.review.entity.Review; + +import java.util.List; +import java.util.stream.Collectors; + public class ReviewConverter { + + public static Review toReview(ReviewReqDTO.WriteReviewDto dto, Member member, MemberMission memberMission) { + return Review.builder() + .member(member) + .memberMission(memberMission) + .score(dto.score()) + .title(dto.title()) + .content(dto.content()) + .build(); + } + + public static ReviewResDTO.WriteReviewResultDto toWriteReviewResult(Review review) { + return ReviewResDTO.WriteReviewResultDto.builder() + .reviewId(review.getId()) + .title(review.getTitle()) + .score(review.getScore()) + .build(); + } + + public static ReviewResDTO.reviewDTO toReviewDTO(Review review) { + return ReviewResDTO.reviewDTO.builder() + .reviewId(review.getId()) + .score(review.getScore()) + .title(review.getTitle()) + .build(); + } + public static List toReviewDTOList(List reviews) { + return reviews.stream() + .map(ReviewConverter::toReviewDTO) + .collect(Collectors.toList()); + } + + // + public static ReviewResDTO.getReview toGetReview( + Review review + ) { + return ReviewResDTO.getReview.builder() + .reviewId(review.getId()) + .title(review.getTitle()) + .content(review.getContent()) + .build(); + } + + //페이지네이션 틀 생성 + public static ReviewResDTO.Pagination toPagination( + List data, + Boolean hasNext, + String nextCursor, + Integer pageSize + + ) { + return ReviewResDTO.Pagination.builder() + .data(data) + .pageSize(pageSize) + .hasNext(hasNext) + .nextCursor(nextCursor) + .build(); + } } diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/review/dto/ReviewReqDTO.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/review/dto/ReviewReqDTO.java index cc73208d..16bc5743 100644 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/review/dto/ReviewReqDTO.java +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/review/dto/ReviewReqDTO.java @@ -1,4 +1,9 @@ package com.example.umc10th.domain.review.dto; public class ReviewReqDTO { + public record WriteReviewDto( + int score, + String title, + String content + ) {} } diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/review/dto/ReviewResDTO.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/review/dto/ReviewResDTO.java index f1c48e95..a3118314 100644 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/review/dto/ReviewResDTO.java +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/review/dto/ReviewResDTO.java @@ -1,4 +1,38 @@ package com.example.umc10th.domain.review.dto; +import lombok.Builder; + +import java.util.List; + public class ReviewResDTO { + @Builder + public record reviewDTO( + Long reviewId, + int score, + String title + ) {} + + @Builder + public record WriteReviewResultDto( + Long reviewId, + String title, + int score + ) {} + // 리뷰 조회 + @Builder + public record getReview( + Long reviewId, + String title, + String content + ){} + + //페이지네이션 틀 + @Builder + public record Pagination( + List data, + Boolean hasNext, // 다음 데이터가 있는지 + String nextCursor, // 다음 커서는 무엇인지 + Integer pageSize + ){} + } diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/review/entity/OwnerComment.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/review/entity/OwnerComment.java index c527a7f1..1d861067 100644 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/review/entity/OwnerComment.java +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/review/entity/OwnerComment.java @@ -1,4 +1,28 @@ package com.example.umc10th.domain.review.entity; -public class OwnerComment { +import com.example.umc10th.global.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Getter +@Table(name = "OwnerComment") +public class OwnerComment extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "review_id") + private Review review; + + @Column(name = "content") + private String content; + } diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/review/entity/Review.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/review/entity/Review.java index 5542f946..8ed31734 100644 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/review/entity/Review.java +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/review/entity/Review.java @@ -1,4 +1,49 @@ package com.example.umc10th.domain.review.entity; -public class Review { +import com.example.umc10th.domain.member.entity.Member; +import com.example.umc10th.domain.member.entity.mapping.MemberMission; +import com.example.umc10th.global.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Table(name = "Review") +public class Review extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id") + private Member member; + + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_mission_id", unique = true) + private MemberMission memberMission; + + @Column(name = "score", precision = 3, scale = 2, nullable = false) + private int score; + + @Column(name = "title", nullable = false, length = 255) + private String title; + + @Column(name = "content", nullable = false, length = 500) + private String content; + + @OneToOne(mappedBy = "review") + private OwnerComment ownerComment; + + @Builder.Default + @OneToMany(mappedBy = "review", cascade = CascadeType.REMOVE) + private List ImgList = new ArrayList<>(); } diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/review/entity/ReviewImg.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/review/entity/ReviewImg.java index 65b0353c..638a26e9 100644 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/review/entity/ReviewImg.java +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/review/entity/ReviewImg.java @@ -1,4 +1,33 @@ package com.example.umc10th.domain.review.entity; -public class ReviewImg { + +import com.example.umc10th.global.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Builder +@Table(name = "ReviewImg") +public class ReviewImg extends BaseEntity { + //Id + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + // 다 대 1 연결 + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "review_id") + private Review review; + + //이미지 URL + @Column(name = "ImgURL") + private String imgUrl; + + } diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/review/exception/ReviewException.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/review/exception/ReviewException.java index ef414bb3..10677d93 100644 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/review/exception/ReviewException.java +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/review/exception/ReviewException.java @@ -1,5 +1,10 @@ -public class ReviewException extends RuntimeException { - public ReviewException(String message) { - super(message); +package com.example.umc10th.domain.review.exception; + +import com.example.umc10th.global.apiPayload.Exception.ProjectException; +import com.example.umc10th.global.apiPayload.code.BaseErrorCode; + +public class ReviewException extends ProjectException { + public ReviewException(BaseErrorCode code) { + super(code); } } diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/review/exception/code/ReviewErrorCode.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/review/exception/code/ReviewErrorCode.java index bd1acea2..d6f22157 100644 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/review/exception/code/ReviewErrorCode.java +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/review/exception/code/ReviewErrorCode.java @@ -1,4 +1,29 @@ -package code; +package com.example.umc10th.domain.review.exception.code; -public enum ReviewErrorCode { +import com.example.umc10th.global.apiPayload.code.BaseErrorCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@Getter +@RequiredArgsConstructor +public enum ReviewErrorCode implements BaseErrorCode { + + NOT_FOUND(HttpStatus.NOT_FOUND, + "REVIEW404_1", + "리뷰를 찾을 수 없음" + ), + + ALREADY_REVIEWED(HttpStatus.CONFLICT, + "REVIEW409_1", + "이미 리뷰를 작성한 미션입니다." + ), + QUERY_NOT_VALID( + HttpStatus.BAD_REQUEST, + "REVIEW400_1", + "잘못된 요청 파라미터입니다." + ); + private final HttpStatus status; + private final String code; + private final String message; } diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/review/exception/code/ReviewSuccessCode.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/review/exception/code/ReviewSuccessCode.java index 7ab0ea0e..8addf6dc 100644 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/review/exception/code/ReviewSuccessCode.java +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/review/exception/code/ReviewSuccessCode.java @@ -1,4 +1,23 @@ -package code; +package com.example.umc10th.domain.review.exception.code; -public enum ReviewSuccessCode { +import com.example.umc10th.global.apiPayload.code.BaseSuccessCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@Getter +@RequiredArgsConstructor +public enum ReviewSuccessCode implements BaseSuccessCode { + OK(HttpStatus.OK, + "REVIEW200_1", + "리뷰 조회 성공" + ), + WRITE_SUCCESS(HttpStatus.CREATED, + "REVIEW201_1", + "리뷰 작성 성공" + ),; + + private final HttpStatus status; + private final String code; + private final String message; } diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/review/repository/ReviewRepository.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/review/repository/ReviewRepository.java index 589caf9c..782cc7b9 100644 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/review/repository/ReviewRepository.java +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/review/repository/ReviewRepository.java @@ -1,4 +1,35 @@ package com.example.umc10th.domain.review.repository; -public interface ReviewRepository { +import com.example.umc10th.domain.member.entity.Member; +import com.example.umc10th.domain.review.entity.Review; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface ReviewRepository extends JpaRepository { + Page findByMember_Id(Long memberId, Pageable pageable); + + List findByMember(Member member); + + // id 순 페이징 + Slice findReviewsByMember_IdAndIdLessThanOrderByIdDesc(Long memberId, Long idCursor, Pageable pageable); + Slice findReviewsByMember_IdOrderByIdDesc(Long memberId, Pageable pageable); + + // 별점 순 페이징 + Slice findReviewsByMember_IdOrderByScoreDescIdDesc(Long memberId, Pageable pageable); + + @Query("SELECT r FROM Review r WHERE r.member.id = :memberId " + + "AND (r.score < :scoreCursor OR (r.score = :scoreCursor AND r.id < :idCursor)) " + + "ORDER BY r.score DESC, r.id DESC") + Slice findReviewsByScoreCursor( + @Param("memberId") Long memberId, + @Param("scoreCursor") long scoreCursor, + @Param("idCursor") long idCursor, + Pageable pageable + ); } diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/review/service/ReviewService.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/review/service/ReviewService.java index c60e3503..f6c18235 100644 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/review/service/ReviewService.java +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/review/service/ReviewService.java @@ -1,4 +1,120 @@ package com.example.umc10th.domain.review.service; +import com.example.umc10th.domain.review.converter.ReviewConverter; +import com.example.umc10th.domain.review.dto.ReviewResDTO; +import com.example.umc10th.domain.review.entity.Review; +import com.example.umc10th.domain.review.exception.ReviewException; +import com.example.umc10th.domain.review.exception.code.ReviewErrorCode; +import com.example.umc10th.domain.review.repository.ReviewRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor public class ReviewService { -} + private final ReviewRepository reviewRepository; + //id 페이지네이션 + public ReviewResDTO.Pagination getMemberReviewsOrderById( + Long memberId, + Integer pageSize, + String cursor, + String query + ) { + + //페이지 정보 PageRequest만들기 + PageRequest pageRequest = PageRequest.of(0, pageSize); + + long idCursor; + Slice reviewList; + String nextCursor; + + //커서가 있는 경우 + if (!cursor.equals("-1")) { + + // 커서 분리 + String[] cursorSplit = cursor.split(":"); + switch (query.toLowerCase()) { + case "id": + idCursor = Long.parseLong(cursorSplit[1]); + // 멤버의 리뷰들 조회 & where 절에 커서값 기입 + reviewList = reviewRepository.findReviewsByMember_IdAndIdLessThanOrderByIdDesc( + memberId, + idCursor, + pageRequest + ); + break; + default: + throw new ReviewException(ReviewErrorCode.QUERY_NOT_VALID); + } + } else { + //커서 없이 조회 + reviewList = reviewRepository.findReviewsByMember_IdOrderByIdDesc(memberId, pageRequest); + } + + List content = reviewList.getContent(); + nextCursor = (reviewList.hasNext() && !content.isEmpty()) + ? content.getLast().getId() + ":" + content.getLast().getId() + : null; + + // 응답 DTO로 포장하기 + return ReviewConverter.toPagination( + reviewList.map(ReviewConverter::toGetReview).toList(), + reviewList.hasNext(), + nextCursor, + reviewList.getSize() + ); + } + //별점 순 페이징 + public ReviewResDTO.Pagination getMemberReviewsOrderByScore( + Long memberId, + Integer pageSize, + String cursor, + String query + ) { + PageRequest pageRequest = PageRequest.of(0, pageSize); + Slice reviewList; + String nextCursor; + //커서가 있는 경우 + if (!cursor.equals("-1")) { + // 커서 분리 + String[] cursorSplit = cursor.split(":"); + switch (query.toLowerCase()) { + case "score": + // 커서 타입 변환 + long scoreCursor = Long.parseLong(cursorSplit[0]); + long idCursor = Long.parseLong(cursorSplit[1]); + + //리뷰들 조회 & where절에 커서 값 기입 + reviewList = reviewRepository.findReviewsByScoreCursor( + memberId, + scoreCursor, + idCursor, + pageRequest); + break; + default: + throw new ReviewException(ReviewErrorCode.QUERY_NOT_VALID); + } + } else { + //커서 없이 조회 + reviewList = reviewRepository.findReviewsByMember_IdOrderByScoreDescIdDesc(memberId, pageRequest); + } + List content = reviewList.getContent(); + nextCursor = (reviewList.hasNext() && !content.isEmpty()) + ? content.getLast().getScore() + ":" + content.getLast().getId() + : null; + + //응답 DTO로 포장하기 + return ReviewConverter.toPagination( + reviewList.map(ReviewConverter::toGetReview).toList(), + reviewList.hasNext(), + nextCursor, + reviewList.getSize() + ); + } +} \ No newline at end of file diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/controller/StoreController.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/controller/StoreController.java index 14178a41..f1c27841 100644 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/controller/StoreController.java +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/controller/StoreController.java @@ -1,4 +1,34 @@ package com.example.umc10th.domain.store.controller; +import com.example.umc10th.domain.store.dto.StoreResDTO; +import com.example.umc10th.domain.store.exception.code.StoreSuccessCode; +import com.example.umc10th.domain.store.service.StoreService; +import com.example.umc10th.global.apiPayload.ApiResponse; +import com.example.umc10th.global.apiPayload.code.BaseSuccessCode; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api") public class StoreController { + + private final StoreService storeService; + + @GetMapping("/v1/stores") + public ResponseEntity>> getStoreList( + @RequestParam Long regionId + ){ + BaseSuccessCode code = StoreSuccessCode.OK; + List result = storeService.getStoreList(regionId); + return ResponseEntity + .status(code.getStatus()) + .body(ApiResponse.onSuccess(code, result)); + } } diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/converter/StoreConverter.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/converter/StoreConverter.java index bf12e4af..b26c3462 100644 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/converter/StoreConverter.java +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/converter/StoreConverter.java @@ -1,4 +1,30 @@ package com.example.umc10th.domain.store.converter; +import com.example.umc10th.domain.mission.dto.MissionReqDTO; +import com.example.umc10th.domain.mission.entity.Mission; +import com.example.umc10th.domain.store.dto.StoreResDTO; +import com.example.umc10th.domain.store.entity.Store; + +import java.util.List; +import java.util.stream.Collectors; + public class StoreConverter { + + //StoreDTO + public static StoreResDTO.StoreDTO toStoreDto(Store store) { + return StoreResDTO.StoreDTO.builder() + .storeId(store.getId()) + .storeName(store.getStoreName()) + .address(store.getAddress()) + .region(store.getRegion().getRegionName()) + .build(); + } + + // List 반환 + public static List toStoreDtoList(List stores) { + return stores.stream() + .map(StoreConverter::toStoreDto) + .collect(Collectors.toList()); + } + } diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/dto/StoreResDTO.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/dto/StoreResDTO.java index 6e2babd3..d61564c5 100644 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/dto/StoreResDTO.java +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/dto/StoreResDTO.java @@ -1,4 +1,13 @@ package com.example.umc10th.domain.store.dto; +import lombok.Builder; + public class StoreResDTO { + @Builder + public record StoreDTO( + Long storeId, + String storeName, + String address, + String region + ){} } diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/entity/Food.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/entity/Food.java new file mode 100644 index 00000000..10be86d9 --- /dev/null +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/entity/Food.java @@ -0,0 +1,28 @@ +package com.example.umc10th.domain.store.entity; + +import com.example.umc10th.domain.member.entity.mapping.MemberFood; +import com.example.umc10th.domain.store.enums.FoodName; +import jakarta.persistence.*; +import lombok.*; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Table(name = "food") +public class Food { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "name") + @Enumerated(EnumType.STRING) + private FoodName name; + + @OneToMany(mappedBy = "food") + private List memberFoodList = new ArrayList<>(); +} diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/entity/FoodCategory.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/entity/FoodCategory.java deleted file mode 100644 index 5e486abd..00000000 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/entity/FoodCategory.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.example.umc10th.domain.store.entity; - -public class FoodCategory { -} diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/entity/Region.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/entity/Region.java index 7afe9e06..4b63d134 100644 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/entity/Region.java +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/entity/Region.java @@ -1,4 +1,30 @@ package com.example.umc10th.domain.store.entity; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Table(name = "region") + public class Region { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "region_name") + private String regionName; + + @Builder.Default + @OneToMany(mappedBy = "region") + private List storeList = new ArrayList<>(); } diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/entity/Store.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/entity/Store.java index 5bf43ff0..9e9363bf 100644 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/entity/Store.java +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/entity/Store.java @@ -1,4 +1,46 @@ package com.example.umc10th.domain.store.entity; +import com.example.umc10th.domain.mission.entity.Mission; +import com.example.umc10th.domain.store.entity.mapping.StoreMission; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Table(name = "store") public class Store { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "storeName", nullable = false) + private String storeName; + + @Column(name = "address", nullable = false) + private String address; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "region_id") + private Region region; + + @Builder.Default + @OneToMany(mappedBy = "store", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + private List StoreMissions = new ArrayList<>(); + + @OneToMany(mappedBy = "store", cascade = CascadeType.ALL) + private List storeImgs = new ArrayList<>(); + + @OneToMany(mappedBy = "store") + private List missions = new ArrayList<>(); + } diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/entity/StoreImg.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/entity/StoreImg.java index efe04715..8ed4d6e9 100644 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/entity/StoreImg.java +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/entity/StoreImg.java @@ -1,4 +1,24 @@ package com.example.umc10th.domain.store.entity; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Table(name = "StoreImg") public class StoreImg { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "store_id") + private Store store; } diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/entity/mapping/StoreMission.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/entity/mapping/StoreMission.java index 0b608e1e..ea320331 100644 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/entity/mapping/StoreMission.java +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/entity/mapping/StoreMission.java @@ -1,4 +1,32 @@ package com.example.umc10th.domain.store.entity.mapping; -public class StoreMission { +import com.example.umc10th.domain.mission.entity.Mission; +import com.example.umc10th.domain.store.entity.Store; +import com.example.umc10th.global.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Table(name = "StoreMission") +@Builder +public class StoreMission extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "store_id") + private Store store; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "mission_id") + private Mission mission; + } diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/enums/CategoryName.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/enums/CategoryName.java deleted file mode 100644 index de263979..00000000 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/enums/CategoryName.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.example.umc10th.domain.store.enums; - -public enum CategoryName { -} diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/enums/FoodName.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/enums/FoodName.java new file mode 100644 index 00000000..12e45089 --- /dev/null +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/enums/FoodName.java @@ -0,0 +1,8 @@ +package com.example.umc10th.domain.store.enums; + +public enum FoodName { + KOREAN, + JAPANESE, + CHINESE, + NONE; +} diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/exception/FoodException.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/exception/FoodException.java new file mode 100644 index 00000000..1cc10c8b --- /dev/null +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/exception/FoodException.java @@ -0,0 +1,10 @@ +package com.example.umc10th.domain.store.exception; + +import com.example.umc10th.domain.store.exception.code.FoodErrorCode; +import com.example.umc10th.global.apiPayload.Exception.ProjectException; + +public class FoodException extends ProjectException { + public FoodException(FoodErrorCode errorCode) { + super(errorCode); + } +} diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/exception/StoreException.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/exception/StoreException.java new file mode 100644 index 00000000..edf1bfde --- /dev/null +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/exception/StoreException.java @@ -0,0 +1,10 @@ +package com.example.umc10th.domain.store.exception; + +import com.example.umc10th.domain.store.exception.code.StoreErrorCode; +import com.example.umc10th.global.apiPayload.Exception.ProjectException; + +public class StoreException extends ProjectException { + public StoreException(StoreErrorCode errorCode) { + super(errorCode); + } +} diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/exception/code/FoodErrorCode.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/exception/code/FoodErrorCode.java new file mode 100644 index 00000000..0957b064 --- /dev/null +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/exception/code/FoodErrorCode.java @@ -0,0 +1,25 @@ +package com.example.umc10th.domain.store.exception.code; + +import com.example.umc10th.global.apiPayload.code.BaseErrorCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@Getter +@RequiredArgsConstructor +public enum FoodErrorCode implements BaseErrorCode { + + NOT_FOUND(HttpStatus.NOT_FOUND, + "FOOD404_1", + "해당 음식을 찾을 수 없습니다."), + ; + + private final HttpStatus status; + private final String code; + private final String message; + + @Override + public HttpStatus getStatus() { + return status; + } +} diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/exception/code/FoodSuccessCode.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/exception/code/FoodSuccessCode.java new file mode 100644 index 00000000..c5072aab --- /dev/null +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/exception/code/FoodSuccessCode.java @@ -0,0 +1,20 @@ +package com.example.umc10th.domain.store.exception.code; + +import com.example.umc10th.global.apiPayload.code.BaseSuccessCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@Getter +@RequiredArgsConstructor +public enum FoodSuccessCode implements BaseSuccessCode { + OK(HttpStatus.OK, + "FOOD200_1", + "음식 조회 성공" + ),; + private final HttpStatus status; + private final String code; + private final String message; + + +} diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/exception/code/StoreErrorCode.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/exception/code/StoreErrorCode.java new file mode 100644 index 00000000..c8e30cc6 --- /dev/null +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/exception/code/StoreErrorCode.java @@ -0,0 +1,24 @@ +package com.example.umc10th.domain.store.exception.code; + +import com.example.umc10th.global.apiPayload.code.BaseErrorCode; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum StoreErrorCode implements BaseErrorCode { + NOT_FOUND(HttpStatus.NOT_FOUND, + "FOOD404_1", + "해당 가게를 찾을 수 없습니다."), + ; + + private final HttpStatus status; + private final String code; + private final String message; + + @Override + public HttpStatus getStatus() { + return status; + } +} diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/exception/code/StoreSuccessCode.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/exception/code/StoreSuccessCode.java new file mode 100644 index 00000000..4bf41d77 --- /dev/null +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/exception/code/StoreSuccessCode.java @@ -0,0 +1,18 @@ +package com.example.umc10th.domain.store.exception.code; + +import com.example.umc10th.global.apiPayload.code.BaseSuccessCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@Getter +@RequiredArgsConstructor +public enum StoreSuccessCode implements BaseSuccessCode { + OK(HttpStatus.OK, + "STORE200_1", + "가게 조회 성공"),; + + private final HttpStatus status; + private final String code; + private final String message; +} diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/repository/StoreRepository.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/repository/StoreRepository.java index beb3c508..a9c86738 100644 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/repository/StoreRepository.java +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/repository/StoreRepository.java @@ -1,4 +1,11 @@ package com.example.umc10th.domain.store.repository; -public interface StoreRepository { +import com.example.umc10th.domain.mission.entity.Mission; +import com.example.umc10th.domain.store.entity.Store; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface StoreRepository extends JpaRepository { + List findByRegion_Id(Long regionId); } diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/service/StoreService.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/service/StoreService.java index e6059e68..afe70eb7 100644 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/service/StoreService.java +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/service/StoreService.java @@ -1,4 +1,21 @@ package com.example.umc10th.domain.store.service; +import com.example.umc10th.domain.store.converter.StoreConverter; +import com.example.umc10th.domain.store.dto.StoreResDTO; +import com.example.umc10th.domain.store.entity.Store; +import com.example.umc10th.domain.store.repository.StoreRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor public class StoreService { + private final StoreRepository storeRepository; + + public List getStoreList(Long regionId) { + List stores = storeRepository.findByRegion_Id(regionId); + return StoreConverter.toStoreDtoList(stores); + } } diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/support/entity/Alarm.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/support/entity/Alarm.java deleted file mode 100644 index 31956afa..00000000 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/support/entity/Alarm.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.example.umc10th.domain.support.entity; - -public class Alarm { -} diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/support/entity/AlarmSetting.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/support/entity/AlarmSetting.java deleted file mode 100644 index 77ac4492..00000000 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/support/entity/AlarmSetting.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.example.umc10th.domain.member.entity; - -public class AlarmSetting { -} diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/support/entity/AlarmTerms.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/support/entity/AlarmTerms.java deleted file mode 100644 index 4e8aed3b..00000000 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/support/entity/AlarmTerms.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.example.umc10th.domain.support.entity; - -public class AlarmTerms { -} diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/support/entity/QnA.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/support/entity/QnA.java index 3fe689f7..4ea3e797 100644 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/support/entity/QnA.java +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/support/entity/QnA.java @@ -1,4 +1,34 @@ package com.example.umc10th.domain.support.entity; +import com.example.umc10th.domain.member.entity.Member; +import com.example.umc10th.domain.support.enums.QnaType; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Table(name = "qna") public class QnA { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id") + private Member member; + + @Column(name = "title") + private String title; + + @Column(name = "content") + private String content; + + @Column(name = "is_proceed") + private Boolean isProceed; + + @Column(name = "type") + private QnaType type; } diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/support/entity/TermOfUse.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/support/entity/TermOfUse.java deleted file mode 100644 index a09377a0..00000000 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/support/entity/TermOfUse.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.example.umc10th.domain.support.entity; - -public class TermOfUse { -} diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/support/enums/AlarmType.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/support/enums/AlarmType.java deleted file mode 100644 index 79848aaf..00000000 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/support/enums/AlarmType.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.example.umc10th.domain.support.enums; - -public enum AlarmType { -} diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/support/enums/QnaType.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/support/enums/QnaType.java index 4d4d6848..f8994d51 100644 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/support/enums/QnaType.java +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/support/enums/QnaType.java @@ -1,4 +1,7 @@ package com.example.umc10th.domain.support.enums; public enum QnaType { + SYSTEM, + ERROR, + OTHER } diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/global/apiPayload/Exception/MemberException.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/global/apiPayload/Exception/MemberException.java deleted file mode 100644 index e392aa3c..00000000 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/global/apiPayload/Exception/MemberException.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.example.umc10th.global.apiPayload.Exception; - -import com.example.umc10th.global.apiPayload.code.BaseErrorCode; -import lombok.Getter; -@Getter -public class MemberException extends ProjectException { - public MemberException(BaseErrorCode errorCode) {super(errorCode);} - -} - - - diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/global/apiPayload/handler/GeneralExceptionAdvice.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/global/apiPayload/handler/GeneralExceptionAdvice.java index aad6eef5..4b28c393 100644 --- a/Gibeom/umc10th/src/main/java/com/example/umc10th/global/apiPayload/handler/GeneralExceptionAdvice.java +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/global/apiPayload/handler/GeneralExceptionAdvice.java @@ -1,15 +1,18 @@ package com.example.umc10th.global.apiPayload.handler; import com.example.umc10th.global.apiPayload.ApiResponse; -import com.example.umc10th.global.apiPayload.Exception.MemberException; import com.example.umc10th.global.apiPayload.Exception.ProjectException; import com.example.umc10th.global.apiPayload.code.BaseErrorCode; import com.example.umc10th.global.apiPayload.code.GeneralErrorCode; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; +import java.util.HashMap; +import java.util.Map; + @Slf4j //Lombok 어노테이션 @RestControllerAdvice public class GeneralExceptionAdvice { @@ -38,4 +41,20 @@ public ResponseEntity> handleException( ) ); } + + //@Valid 어노테이션 검증 실패 예외 + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity>> handleMethodArgumentNotValidException( + MethodArgumentNotValidException e + ){ + //검증 실패한 변수명과 실패 이유를 담을 Map + Map errors = new HashMap<>(); + e.getBindingResult().getFieldErrors().forEach(error -> { + errors.put(error.getField(), error.getDefaultMessage()); + }); + + BaseErrorCode code = GeneralErrorCode.BAD_REQUEST; + return ResponseEntity.status(code.getStatus()) + .body(ApiResponse.onFailure(code, errors)); + } } diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/global/config/SecurityConfig.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/global/config/SecurityConfig.java new file mode 100644 index 00000000..6b8c1df0 --- /dev/null +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/global/config/SecurityConfig.java @@ -0,0 +1,23 @@ +package com.example.umc10th.global.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.web.SecurityFilterChain; + +@Configuration +@EnableWebSecurity +public class SecurityConfig { + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + .csrf(AbstractHttpConfigurer::disable) + .authorizeHttpRequests(auth -> auth + .anyRequest().permitAll() + ); + return http.build(); + } +} diff --git a/Gibeom/umc10th/src/main/java/com/example/umc10th/global/entity/BaseEntity.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/global/entity/BaseEntity.java new file mode 100644 index 00000000..ffd64461 --- /dev/null +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/global/entity/BaseEntity.java @@ -0,0 +1,27 @@ +package com.example.umc10th.global.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.MappedSuperclass; +import lombok.Getter; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import java.time.LocalDateTime; + +@Getter +@EntityListeners(AuditingEntityListener.class) +@MappedSuperclass +public abstract class BaseEntity { + @CreatedDate + @Column(name = "created_at", nullable = false) + private LocalDateTime createdAt; + + @LastModifiedDate + @Column(name = "update_at", nullable = false) + private LocalDateTime updatedAt; + + @Column(name = "deleted_at") + private LocalDateTime deletedAt; +}