From de259432645ec9b9f4f69455c37e886b6f3ae2d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=84=EA=B8=B0=EB=B2=94?= Date: Tue, 5 May 2026 17:15:25 +0900 Subject: [PATCH 01/20] =?UTF-8?q?feat=20:=206=EC=A3=BC=EC=B0=A8=20api=20?= =?UTF-8?q?=EC=84=A4=EA=B3=84=20=EA=B8=B0=EC=B4=88=20jpa?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Gibeom/umc10th/.env | 2 +- Gibeom/umc10th/build.gradle | 1 + .../example/umc10th/Umc10thApplication.java | 2 + .../member/controller/MemberController.java | 38 ++++++++++++--- .../member/converter/MemberConverter.java | 43 +++++++++++++++++ .../domain/member/dto/MemberReqDTO.java | 7 ++- .../domain/member/dto/MemberResDTO.java | 28 +++++++++-- .../umc10th/domain/member/entity/Member.java | 48 ++++++++++++++++++- .../member/entity/mapping/MemberFood.java | 26 ++++++++++ .../member/entity/mapping/MemberMission.java | 39 +++++++++++++++ .../umc10th/domain/member/enums/Gender.java | 4 +- .../domain/member/enums/MissionStatus.java | 3 ++ .../member/exception/MemberException.java | 11 +++++ .../exception}/code/MemberErrorCode.java | 4 +- .../exception}/code/MemberSuccessCode.java | 8 +++- .../repository/MemberMissionRepository.java | 20 ++++++++ .../member/repository/MemberRepository.java | 5 +- .../domain/member/service/MemberService.java | 48 +++++++++++++++++++ .../mission/controller/MissionController.java | 26 ++++++++++ .../mission/converter/MissionConverter.java | 22 +++++++++ .../domain/mission/dto/MissionResDTO.java | 10 ++++ .../domain/mission/entity/Mission.java | 39 ++++++++++++++- .../mission/exception/MemberException.java | 7 --- .../mission/exception/MissionException.java | 11 +++++ .../exception/code/MissionErrorCode.java | 30 ++++++++++-- .../exception/code/MissionSuccessCode.java | 28 ++++++++++- .../mission/repository/MissionRepository.java | 5 +- .../mission/service/MissionService.java | 43 +++++++++++++++++ .../review/controller/ReviewController.java | 25 ++++++++++ .../review/converter/ReviewConverter.java | 38 +++++++++++++++ .../domain/review/dto/ReviewReqDTO.java | 5 ++ .../domain/review/dto/ReviewResDTO.java | 15 ++++++ .../domain/review/entity/OwnerComment.java | 26 +++++++++- .../umc10th/domain/review/entity/Review.java | 42 +++++++++++++++- .../domain/review/entity/ReviewImg.java | 31 +++++++++++- .../review/exception/ReviewException.java | 11 +++-- .../exception/code/ReviewErrorCode.java | 19 +++++++- .../exception/code/ReviewSuccessCode.java | 23 ++++++++- .../review/repository/ReviewRepository.java | 8 +++- .../domain/review/service/ReviewService.java | 17 +++++++ .../store/controller/StoreController.java | 26 ++++++++++ .../store/converter/StoreConverter.java | 23 +++++++++ .../umc10th/domain/store/dto/StoreResDTO.java | 9 ++++ .../umc10th/domain/store/entity/Food.java | 28 +++++++++++ .../domain/store/entity/FoodCategory.java | 4 -- .../umc10th/domain/store/entity/Region.java | 26 ++++++++++ .../umc10th/domain/store/entity/Store.java | 38 +++++++++++++++ .../umc10th/domain/store/entity/StoreImg.java | 20 ++++++++ .../store/entity/mapping/StoreMission.java | 30 +++++++++++- .../domain/store/enums/CategoryName.java | 4 -- .../umc10th/domain/store/enums/FoodName.java | 8 ++++ .../domain/store/exception/FoodException.java | 10 ++++ .../store/exception/StoreException.java | 10 ++++ .../store/exception/code/FoodErrorCode.java | 25 ++++++++++ .../store/exception/code/FoodSuccessCode.java | 20 ++++++++ .../store/exception/code/StoreErrorCode.java | 24 ++++++++++ .../exception/code/StoreSuccessCode.java | 18 +++++++ .../store/repository/StoreRepository.java | 8 +++- .../domain/store/service/StoreService.java | 17 +++++++ .../umc10th/domain/support/entity/Alarm.java | 4 -- .../domain/support/entity/AlarmSetting.java | 4 -- .../domain/support/entity/AlarmTerms.java | 4 -- .../umc10th/domain/support/entity/QnA.java | 30 ++++++++++++ .../domain/support/entity/TermOfUse.java | 4 -- .../domain/support/enums/AlarmType.java | 4 -- .../umc10th/domain/support/enums/QnaType.java | 3 ++ .../apiPayload/Exception/MemberException.java | 12 ----- .../handler/GeneralExceptionAdvice.java | 1 - .../umc10th/global/config/SecurityConfig.java | 23 +++++++++ .../umc10th/global/entity/BaseEntity.java | 27 +++++++++++ 70 files changed, 1196 insertions(+), 86 deletions(-) create mode 100644 Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/exception/MemberException.java rename Gibeom/umc10th/src/main/java/com/example/umc10th/{global/apiPayload => domain/member/exception}/code/MemberErrorCode.java (77%) rename Gibeom/umc10th/src/main/java/com/example/umc10th/{global/apiPayload => domain/member/exception}/code/MemberSuccessCode.java (61%) create mode 100644 Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/repository/MemberMissionRepository.java delete mode 100644 Gibeom/umc10th/src/main/java/com/example/umc10th/domain/mission/exception/MemberException.java create mode 100644 Gibeom/umc10th/src/main/java/com/example/umc10th/domain/mission/exception/MissionException.java create mode 100644 Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/entity/Food.java delete mode 100644 Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/entity/FoodCategory.java delete mode 100644 Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/enums/CategoryName.java create mode 100644 Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/enums/FoodName.java create mode 100644 Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/exception/FoodException.java create mode 100644 Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/exception/StoreException.java create mode 100644 Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/exception/code/FoodErrorCode.java create mode 100644 Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/exception/code/FoodSuccessCode.java create mode 100644 Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/exception/code/StoreErrorCode.java create mode 100644 Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/exception/code/StoreSuccessCode.java delete mode 100644 Gibeom/umc10th/src/main/java/com/example/umc10th/domain/support/entity/Alarm.java delete mode 100644 Gibeom/umc10th/src/main/java/com/example/umc10th/domain/support/entity/AlarmSetting.java delete mode 100644 Gibeom/umc10th/src/main/java/com/example/umc10th/domain/support/entity/AlarmTerms.java delete mode 100644 Gibeom/umc10th/src/main/java/com/example/umc10th/domain/support/entity/TermOfUse.java delete mode 100644 Gibeom/umc10th/src/main/java/com/example/umc10th/domain/support/enums/AlarmType.java delete mode 100644 Gibeom/umc10th/src/main/java/com/example/umc10th/global/apiPayload/Exception/MemberException.java create mode 100644 Gibeom/umc10th/src/main/java/com/example/umc10th/global/config/SecurityConfig.java create mode 100644 Gibeom/umc10th/src/main/java/com/example/umc10th/global/entity/BaseEntity.java diff --git a/Gibeom/umc10th/.env b/Gibeom/umc10th/.env index 70eba936..8b7065cf 100644 --- a/Gibeom/umc10th/.env +++ b/Gibeom/umc10th/.env @@ -1,3 +1,3 @@ DB_USER=root 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=jdbc:mysql://localhost:3306/umc10th?serverTimezone=Asia/Seoul&allowPublicKeyRetrieval=true&useSSL=false diff --git a/Gibeom/umc10th/build.gradle b/Gibeom/umc10th/build.gradle index d7ef7dff..6a0ce14e 100644 --- a/Gibeom/umc10th/build.gradle +++ b/Gibeom/umc10th/build.gradle @@ -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/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..a8fe7933 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 @@ -2,25 +2,51 @@ 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.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") + @GetMapping("/me") public ApiResponse getInfo( - @RequestBody MemberReqDTO.GetInfo dto + @AuthenticationPrincipal Long memberId + ){ + BaseSuccessCode code = MemberSuccessCode.OK; + return ApiResponse.onSuccess(code, memberService.getInfo(memberId)); + } + + // 홈화면 + @GetMapping("/home") + public ApiResponse getHome( + @AuthenticationPrincipal Long memberId, + @RequestParam(defaultValue = "0") int page ){ BaseSuccessCode code = MemberSuccessCode.OK; - return ApiResponse.onSuccess(code, memberService.getInfo(dto)); + return ApiResponse.onSuccess(code, memberService.getHome(memberId, page)); } + + // 진행중/완료 미션 목록 조회 + @GetMapping("/missions") + public ApiResponse> getMissionsByStatus( + @AuthenticationPrincipal Long memberId, + @RequestParam MissionStatus status + ){ + BaseSuccessCode code = MemberSuccessCode.OK; + return ApiResponse.onSuccess(code, memberService.getMissionsByStatus(memberId, status)); + } + } 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..cd782bab 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(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().getRewardPoint()) + .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..7610e7b6 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,7 @@ package com.example.umc10th.domain.member.dto; +import com.example.umc10th.domain.member.enums.Gender; + 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..51a4794b 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,50 @@ 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.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; + + @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..1d968781 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, cascade = CascadeType.REMOVE) + @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..5b0145a4 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,43 @@ 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 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; + } 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..d2c4199b --- /dev/null +++ b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/member/repository/MemberMissionRepository.java @@ -0,0 +1,20 @@ +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); + 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..5bc02885 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,53 @@ 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.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); + } + public List getMissionsByStatus(Long memberId, MissionStatus status) { + List memberMissions = memberMissionRepository + .findAllByMember_IdAndStatus(memberId, status); + 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..18247392 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,30 @@ package com.example.umc10th.domain.mission.controller; +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 lombok.RequiredArgsConstructor; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api") public class MissionController { + + private final MissionService missionService; + + + //리뷰 작성 (완료된 미션에 한해서) + @PostMapping("v1/missions/{missionId}/reviews") + public ApiResponse writeReview( + @PathVariable Long missionId, + @AuthenticationPrincipal Long memberId, + @RequestBody ReviewReqDTO.WriteReviewDto dto) { + BaseSuccessCode code = ReviewSuccessCode.WRITE_SUCCESS; + return ApiResponse.onSuccess(code, missionService.writeReview(memberId, missionId, dto)); + } } 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..fa12c5a7 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,26 @@ package com.example.umc10th.domain.mission.converter; +import com.example.umc10th.domain.mission.dto.MissionResDTO; +import com.example.umc10th.domain.mission.entity.Mission; + +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.getRewardPoint()) + .build(); + } + + public static List toMissionDtoList(List missions) { + return missions.stream() + .map(MissionConverter::toMissionDto) + .collect(Collectors.toList()); + } + + } 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..0a5a24e2 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,14 @@ package com.example.umc10th.domain.mission.dto; +import lombok.Builder; + public class MissionResDTO { + + @Builder + public record MissionDto( + Long missionId, + String title, + Integer rewardPoint + ) {} + } 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..9496c8c7 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,41 @@ package com.example.umc10th.domain.mission.entity; -public class Mission { +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.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; + + @OneToMany(mappedBy = "mission", cascade = CascadeType.REMOVE) + @Builder.Default + private List storeMissionList = new ArrayList<>(); + + @Column(name = "reward_point", nullable = false) + private Integer rewardPoint; + + @OneToMany(mappedBy = "mission", cascade = CascadeType.REMOVE) + @Builder.Default + private List memberMissionList = new ArrayList<>(); + } 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..56a62d7c 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..f03361f3 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,28 @@ -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", + "미션 상태가 성공적으로 변경되었습니다." + ), + ; + + 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..71f7528f 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,7 @@ package com.example.umc10th.domain.mission.repository; -public interface MissionRepository { +import com.example.umc10th.domain.mission.entity.Mission; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface MissionRepository extends JpaRepository { } 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..d9abd08e 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,47 @@ 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.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.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.repository.ReviewRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor public class MissionService { + + private final MemberMissionRepository memberMissionRepository; + private final MemberRepository memberRepository; + private final ReviewRepository reviewRepository; + //완료된 미션 + @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); + } + + Review review = ReviewConverter.toReview(dto, member); + Review savedReview = reviewRepository.save(review); + return ReviewConverter.toWriteReviewResult(savedReview); + } + } 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..7761a195 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,29 @@ 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.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api") public class ReviewController { + + private final ReviewService reviewService; + + //유저의 모든 리뷰 가져오기 + @GetMapping("/v1/members/{memberId}/reviews") + public ApiResponse> getReviews( + @PathVariable Long memberId) { + BaseSuccessCode code = ReviewSuccessCode.OK; + return ApiResponse.onSuccess(code, reviewService.getReviewList(memberId)); + } + } 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..3f5aba10 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,42 @@ package com.example.umc10th.domain.review.converter; +import com.example.umc10th.domain.member.entity.Member; +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) { + return Review.builder() + .member(member) + .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()); + } } 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..635c1628 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,19 @@ package com.example.umc10th.domain.review.dto; +import lombok.Builder; + public class ReviewResDTO { + @Builder + public record reviewDTO( + Long reviewId, + int score, + String title + ) {} + + @Builder + public record WriteReviewResultDto( + Long reviewId, + String title, + int score + ) {} } 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..a0a0e172 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,44 @@ package com.example.umc10th.domain.review.entity; -public class Review { +import com.example.umc10th.domain.member.entity.Member; +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; + + @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..6e88f175 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,19 @@ -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", + "리뷰를 찾을 수 없음" + ),; + 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..228e6940 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,10 @@ package com.example.umc10th.domain.review.repository; -public interface ReviewRepository { +import com.example.umc10th.domain.review.entity.Review; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface ReviewRepository extends JpaRepository { + List findByMember_Id(Long memberId); } 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..425e4401 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,21 @@ 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.repository.ReviewRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor public class ReviewService { + private final ReviewRepository reviewRepository; + + public List getReviewList(Long memberId) { + List review = reviewRepository.findByMember_Id(memberId); + return ReviewConverter.toReviewDTOList(review); + } } 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..386d4589 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,30 @@ 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.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 ApiResponse> getStoreList( + @RequestParam Long regionId // [수정] Region 엔티티 → Long + ){ + BaseSuccessCode code = StoreSuccessCode.OK; + return ApiResponse.onSuccess(code, storeService.getStoreList(regionId)); + } } 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..98e2cfe8 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,27 @@ package com.example.umc10th.domain.store.converter; +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.getStoreId()) + .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..b17db63b 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..cb23eb1a 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,42 @@ package com.example.umc10th.domain.store.entity; +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 storeId; + + @Column(name = "storeName", nullable = false) + private String storeName; + + @Column(name = "address", nullable = false) + private String address; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "region_id", nullable = false) + 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<>(); + } 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..ad82b8e1 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,10 @@ package com.example.umc10th.domain.store.repository; -public interface StoreRepository { +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..9d03a68e 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,7 +1,6 @@ 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; 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; +} From 7deb3e8eea391d067e8887a4a2d4c6df2935f59c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=84=EA=B8=B0=EB=B2=94?= Date: Tue, 5 May 2026 19:43:55 +0900 Subject: [PATCH 02/20] =?UTF-8?q?docs=20:=20ch06.md=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Gibeom/umc10th/keyword_summary/ch05.md | 75 ------------------------ Gibeom/umc10th/keyword_summary/ch06.md | 80 ++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 75 deletions(-) delete mode 100644 Gibeom/umc10th/keyword_summary/ch05.md create mode 100644 Gibeom/umc10th/keyword_summary/ch06.md 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/ch06.md b/Gibeom/umc10th/keyword_summary/ch06.md new file mode 100644 index 00000000..b28a1fba --- /dev/null +++ b/Gibeom/umc10th/keyword_summary/ch06.md @@ -0,0 +1,80 @@ +- JPA란? + + JPA(Java Persistence API)란, **자바 진영에서 ORM(Object-Relational Mapping) 기술의 표준스펙.** + + 실제 구현체는 Hibernatem EclipseLink 등이 있고, 스프링 부트에선 보통 Hibernate를 기본 구현체로 사용. + +- N+1 문제란? + + 1번의 쿼리로 N개의 데이터를 가져왔는데, 연관된 데이터를 채우기 위해 추가로 N번의 쿼리가 실행되는 현상을 말함 + + 발생과정 + + 1. 최초쿼리 (1) : `SELECT * FROM Member` 를 실행해 10명의 회원을 가져옴 + 2. 연관 데이터 조회(N) : 즉시 로딩 설정이 되어 있다면, JPA는 각 회원(10명)이 가진 팀(Team) 정보를 채우기 위해 회원별로 팀 조회 쿼리를 각각 날림 + 3. 결과 : 쿼리 1번으로 끝날일이 총 11번(1 + 10) 호출되어 DB 성능이 저하됨 + +- 지연로딩과 즉시로딩의 차이는? + + 지연 로딩 + + - 필요할 때 까지 미룬다 + - 엔티티 조회할 때 연관된 객체는 가져오지 않고, 실제 그 데이터를 사용하는 시점에 DB를 조회함 + - 동작 방식 : 조회 시점에 연관된 객체 자리에 프록시라는 가짜 객체를 넣어둠. 실제로 연관객체의 내부 데이터에 접근할 때 SQL이 실행. + - 어노테이션 : `ManyToOne(fetch=FetchType.LAZY)` + - 단점 : 연관된 데이터가 필요 없는 경우 불필요한 조인이나 쿼리를 줄여 성능 최적화 가능 + + 즉시 로딩 + + - 한번에 다 가져온다 + - 동작 방식 : JPA 구현체는 대개 SQL의 JOIN을 사용하여 한번의 쿼리로 모든 객체를 즉시 함께 조회 + - 어노테이션 : `@ManyToOne(fetch = FetchType.EAGER)` + - 단점 : 연관된 테이블이 많아질수록 쿼리가 복잡해지고, N + 1 문제를 발생시켜 성능 저하의 주범이 됨 + +- JPQL란? + + 엔티티 객체를 대상으로 쿼리하는 객체지향 쿼리 언어(단순히 JPA SQL) + + - 객체지향 쿼리 : 테이블 컬럼명이 아니라 엔티티의 필드(변수)이름을 사용해 쿼리를 작성함 + + 왜 사용할까? + + - JPA의 기본 메서드 (find())는 식별자로 조회할 때 유용하지만 복잡한 검색조건을 처리하기엔 한계가 있음. + - 코드는 자바객체인데 쿼리는 테이블 기준이면 싱크가 깨짐. JPQL은 자바 클래스와 필드 이름을 그대로 쓰기 때문에 훨씬 직관적 + - 이를 해결하기 위해 SQL의 장점을 가져오면서 객체 지향의 이점을 유지하는 JPQL을 사용. + +- Fetch Join란? + + 연관된 엔티티나 컬렉션을 SQL 한번에 함께 조회하는 방법 + + 1. 일반 Join VS Fetch Join + - **일반 Join** + - 동작 : SQL 상에서는 조인이 일어나지만, JPQL은 SELECT절에 명시된 엔티티만 조회 + - 결과 : 연관된 엔티티는 프록시가 상태로 남아있어, 나중에 접근할 때 추가쿼리가 발생 + - **Fetch Join** + - 동작 : SQL 조인을 실행하면서 연관된 엔티티의 데이터까지 한번에 SELECT해서 가져옴. + - 결과 : 연관된 엔티티가 프록시가 아닌 실제 엔티티로 채워진 상태로 조회됨. 따라서 추가쿼리 없이 데이터를 바로 사용할 수 있음. + +- @EntityGraph란? + + Fetch 조인을 보완하기 위해 나온 기능. 쿼리 수행 시점에 연관된 엔티티들을 함께 조회하도록 설정하는 어노테이션 + + - 사용 이유 + - JPQL의 Fetch Join은 쿼리문 안에 조인코드를 직접 작성해야 한다는 번거로움이 있음. + - 코드의 간결함 : 스프링 부트 JPA를 사용하면 메서드 위에 어노테이션 하나만 붙여서 N + 1 문제를 해결할 수 있음. + - 쿼리분리 : 똑같은 findAll()이라도 어떤 메서드에서는 연관데이터를 가져오고, 어떤 메서드에서는 안 가져오게끔 유연히 관리 가능 + +- commit과 flush 차이점은? + - Flush (플러시) : 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화하는 작업 + - 하는일 : 쓰기 지연 SQL 저장소에 쌓여있던 INSERT, UPDATE, DELETE 쿼리를 DB로 보냄 + - 영향 : 쿼리가 DB로 전송되지만, DB 입장에서는 아직 임시 쿼리 상태임. 다른 트랜잭션에선 이 데이터를 못 봄 + - 영속성 컨텍스트 유지 : 플러시가 일어나도 영속성 컨텍스트 내의 엔티티들은 지워지지 않고 그대로 유지. + - 발생시점 + 1. em.flush() 직접 호출 + 2. 트랜잭션 커밋 시 + 3. JPQL 쿼리 실행 시 + - Commit (커밋) : 데이터베이스의 트랜잭션을 종료하고 변경사항을 영구적으로 반영하는 작업 + - 하는일 : 내부적으로 em.flush()를 먼저 호출해 쿼리를 보낸 후 DB에 커밋 명령을 내림 + - 영향 : 데이터가 실제로 DB에 저장되며, 다른 사용자나 시스템에서도 변경된 데이터를 조회할 수 있게됨 + - 트랜잭션 종료 : 커밋이 완료되면 해당 트랜잭션이 완전히 종료. + From ecfb9c6e28b04ce2928cc36fd12226a3ede6255d Mon Sep 17 00:00:00 2001 From: Gibeom Date: Tue, 5 May 2026 20:09:27 +0900 Subject: [PATCH 03/20] =?UTF-8?q?feat=20:=20=EB=A6=AC=EB=B7=B0/=EB=A9=A4?= =?UTF-8?q?=EB=B2=84=EA=B4=80=EA=B3=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Gibeom/umc10th/.env | 4 ++-- .../umc10th/domain/member/converter/MemberConverter.java | 2 +- .../example/umc10th/domain/member/dto/MemberReqDTO.java | 2 -- .../com/example/umc10th/domain/member/entity/Member.java | 5 +++++ .../umc10th/domain/member/entity/mapping/MemberFood.java | 2 +- .../domain/member/entity/mapping/MemberMission.java | 4 ++++ .../umc10th/domain/mission/service/MissionService.java | 8 +++++++- .../umc10th/domain/review/converter/ReviewConverter.java | 4 +++- .../com/example/umc10th/domain/review/entity/Review.java | 5 +++++ .../domain/review/exception/code/ReviewErrorCode.java | 5 +++++ 10 files changed, 33 insertions(+), 8 deletions(-) diff --git a/Gibeom/umc10th/.env b/Gibeom/umc10th/.env index 8b7065cf..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 +DB_URL=MY_URL \ No newline at end of file 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 cd782bab..e2af995b 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 @@ -28,7 +28,7 @@ public static MemberResDTO.HomeResultDto toHomeResult(Member member, Page 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 1d968781..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 @@ -24,7 +24,7 @@ public class MemberFood { @JoinColumn(name = "member_id") private Member member; - @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.REMOVE) + @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 5b0145a4..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 @@ -3,6 +3,7 @@ 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; @@ -40,4 +41,7 @@ public class MemberMission { @Column(name = "deadline") private Integer deadline; + @OneToOne(mappedBy = "memberMission") + private Review review; + } 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 d9abd08e..df3239c3 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 @@ -13,6 +13,8 @@ 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 lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -39,7 +41,11 @@ public ReviewResDTO.WriteReviewResultDto writeReview(Long memberId, Long mission throw new MissionException(MissionErrorCode.NOT_COMPLETED); } - Review review = ReviewConverter.toReview(dto, member); + 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); } 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 3f5aba10..48d335b9 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,6 +1,7 @@ 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; @@ -10,9 +11,10 @@ public class ReviewConverter { - public static Review toReview(ReviewReqDTO.WriteReviewDto dto, Member member) { + 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()) 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 a0a0e172..40cf3f4a 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,6 +1,7 @@ package com.example.umc10th.domain.review.entity; 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; @@ -26,6 +27,10 @@ public class Review extends BaseEntity { @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; 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 6e88f175..2865eb07 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 @@ -12,6 +12,11 @@ public enum ReviewErrorCode implements BaseErrorCode { NOT_FOUND(HttpStatus.NOT_FOUND, "REVIEW404_1", "리뷰를 찾을 수 없음" + ), + + ALREADY_REVIEWED(HttpStatus.CONFLICT, + "REVIEW409_1", + "이미 리뷰를 작성한 미션입니다." ),; private final HttpStatus status; private final String code; From 9729251b55d2a9b0eecdd93392557a7aa312114e Mon Sep 17 00:00:00 2001 From: Gibeom Date: Sat, 9 May 2026 20:22:47 +0900 Subject: [PATCH 04/20] =?UTF-8?q?feat=20:=20=EB=A9=A4=EB=B2=84=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=20=ED=8E=98=EC=9D=B4=EC=A7=95=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/controller/MemberController.java | 8 ++++--- .../member/converter/MemberConverter.java | 2 +- .../repository/MemberMissionRepository.java | 2 ++ .../domain/member/service/MemberService.java | 22 +++++++++++++++++-- 4 files changed, 28 insertions(+), 6 deletions(-) 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 a8fe7933..2343770c 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,6 +1,5 @@ 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; @@ -43,10 +42,13 @@ public ApiResponse getHome( @GetMapping("/missions") public ApiResponse> getMissionsByStatus( @AuthenticationPrincipal Long memberId, - @RequestParam MissionStatus status + @RequestParam MissionStatus status, + @RequestParam Integer pageSize, + @RequestParam Integer pageNum, + @RequestParam (required = false) String sort ){ BaseSuccessCode code = MemberSuccessCode.OK; - return ApiResponse.onSuccess(code, memberService.getMissionsByStatus(memberId, status)); + return ApiResponse.onSuccess(code, memberService.getMissionsByStatus(memberId, status, pageSize, pageNum, sort)); } } 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 e2af995b..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 @@ -39,7 +39,7 @@ public static MemberResDTO.HomeMissionDto toHomeMissionDto(MemberMission memberM .missionId(memberMission.getMission().getId()) .storeName(null) .storeCategory(null) - .rewardPoint(memberMission.getMission().getRewardPoint()) + .rewardPoint(memberMission.getMission().getPoint()) .deadline(null) .status(memberMission.getStatus().name()) .build(); 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 index d2c4199b..87a8a246 100644 --- 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 @@ -17,4 +17,6 @@ public interface MemberMissionRepository extends JpaRepository findActiveMissions(@Param("memberId") Long memberId, @Param("status") MissionStatus status, Pageable pageable); + + } 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 5bc02885..0d9cf1da 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 @@ -16,6 +16,8 @@ import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.security.core.parameters.P; import org.springframework.stereotype.Service; import java.util.List; @@ -41,9 +43,25 @@ public MemberResDTO.HomeResultDto getHome(Long memberId, int page) { .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(); + } - public List getMissionsByStatus(Long memberId, MissionStatus status) { - List memberMissions = memberMissionRepository + PageRequest pageRequest + = PageRequest.of(pageNum, pageSize, sortInfo); + + List memberMissions = memberMissionRepository .findAllByMember_IdAndStatus(memberId, status); List missions = memberMissions.stream() .map(MemberMission::getMission) From b1e2237a13ac540a267e36451149379d0d25b0b8 Mon Sep 17 00:00:00 2001 From: Gibeom Date: Sat, 9 May 2026 20:24:00 +0900 Subject: [PATCH 05/20] =?UTF-8?q?feat=20:=20=EB=AF=B8=EC=85=98=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=20=EC=98=A4=ED=94=84=EC=85=8B=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=95=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mission/controller/MissionController.java | 39 ++++++++ .../mission/converter/MissionConverter.java | 38 +++++++- .../domain/mission/dto/MissionReqDTO.java | 14 +++ .../domain/mission/dto/MissionResDTO.java | 18 ++++ .../domain/mission/entity/Mission.java | 15 +++- .../exception/code/MissionErrorCode.java | 2 +- .../exception/code/MissionSuccessCode.java | 5 +- .../mission/repository/MissionRepository.java | 12 +++ .../mission/service/MissionService.java | 90 +++++++++++++++++++ 9 files changed, 228 insertions(+), 5 deletions(-) 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 18247392..1a101437 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,15 +1,21 @@ 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.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; +import java.util.List; + @RestController @RequiredArgsConstructor @RequestMapping("/api") @@ -27,4 +33,37 @@ public ApiResponse writeReview( BaseSuccessCode code = ReviewSuccessCode.WRITE_SUCCESS; return ApiResponse.onSuccess(code, missionService.writeReview(memberId, missionId, dto)); } + //가게 미션 생성 + @PostMapping("v1/stores/{storeId}/missions") + public ApiResponse createMission( + @PathVariable Long storeId, + @RequestBody @Valid MissionReqDTO.CreateMission dto + ){ + MissionSuccessCode code = MissionSuccessCode.CREATED; + return ApiResponse.onSuccess(code, missionService.createMission(storeId, dto)); + } + + //가게 미션들 조회 + @GetMapping("v1/store/{storeId}/missions") + public ApiResponse> getMissions( + @PathVariable Long storeId, + @RequestParam Integer pageSize, + @RequestParam Integer pageNumber, + @RequestParam(required = false) String sort + ){ + BaseSuccessCode code = MissionSuccessCode.OK; + return ApiResponse.onSuccess(code, missionService.getMissions(storeId, pageSize, pageNumber, sort)); + } + + //내가 진행중인 미션 조회하기 + @GetMapping("v1/members/{memberId}/missions") + public ApiResponse> getMemberMissions( + @RequestBody Long memberId, + @RequestParam Integer pageSize, + @RequestParam Integer pageNumber, + @RequestParam(required = false) String sort + ){ + BaseSuccessCode code = MissionSuccessCode.OK; + return ApiResponse.onSuccess(code, missionService.getMemberMissions(memberId, pageSize, pageNumber, sort)); + } } 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 fa12c5a7..938a879a 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,7 +1,9 @@ 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; @@ -12,7 +14,7 @@ public static MissionResDTO.MissionDto toMissionDto(Mission mission) { return MissionResDTO.MissionDto.builder() .missionId(mission.getId()) .title(mission.getTitle()) - .rewardPoint(mission.getRewardPoint()) + .rewardPoint(mission.getPoint()) .build(); } @@ -22,5 +24,39 @@ public static List toMissionDtoList(List miss .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(); + } } 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..a9319844 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,18 @@ package com.example.umc10th.domain.mission.dto; +import com.example.umc10th.domain.member.enums.MissionStatus; +import jakarta.validation.constraints.NotNull; + +import java.time.LocalDate; + public class MissionReqDTO { + //가게 미션 생성 + public record CreateMission( + @NotNull(message = "마감기한은 필수입니다.") + LocalDate deadLine, + @NotNull(message = "미션 성공 포인트는 필수입니다.") + Integer point, + @NotNull(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 0a5a24e2..0c075bf1 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,7 +1,10 @@ package com.example.umc10th.domain.mission.dto; +import com.example.umc10th.domain.member.enums.MissionStatus; import lombok.Builder; +import java.util.List; + public class MissionResDTO { @Builder @@ -11,4 +14,19 @@ public record MissionDto( Integer rewardPoint ) {} + //가게 내 미션 조회 + @Builder + public record GetMission( + Long missionId, + Integer point, + String conditional + ){} + + //페이지네이션 틀 + @Builder + public record Pagination( + List data, + Integer pageNumber, + Integer pageSize + ){} } 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 9496c8c7..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,5 +1,6 @@ package com.example.umc10th.domain.mission.entity; +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; @@ -9,6 +10,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; +import java.time.LocalDate; import java.util.ArrayList; import java.util.List; @@ -27,15 +29,24 @@ public class Mission extends BaseEntity { @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 = "reward_point", nullable = false) - private Integer rewardPoint; + @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/code/MissionErrorCode.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/mission/exception/code/MissionErrorCode.java index 56a62d7c..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 @@ -11,7 +11,7 @@ public enum MissionErrorCode implements BaseErrorCode { NOT_FOUND(HttpStatus.NOT_FOUND, "MISSION404_1", - "해당 미션을 찾을 수 없습니다."), + "해당 가게를 찾을 수 없습니다."), ALREADY_CHALLENGED(HttpStatus.CONFLICT, "MISSION409_1", 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 f03361f3..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 @@ -11,7 +11,7 @@ public enum MissionSuccessCode implements BaseSuccessCode { OK(HttpStatus.OK, "MISSION200_1", - "미션 조회에 성공했습니다."), + "성공적으러 미션을 조회했습니다."), CHALLENGE_SUCCESS(HttpStatus.CREATED, "MISSION201_1", @@ -20,6 +20,9 @@ public enum MissionSuccessCode implements BaseSuccessCode { "MISSION200_2", "미션 상태가 성공적으로 변경되었습니다." ), + CREATED(HttpStatus.CREATED, + "MISSION200_3", + "성공적으로 미션을 생성했습니다.") ; private final HttpStatus status; 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 71f7528f..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,7 +1,19 @@ package com.example.umc10th.domain.mission.repository; +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 df3239c3..124d440c 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 @@ -5,10 +5,15 @@ 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; @@ -16,10 +21,19 @@ 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 { @@ -27,6 +41,9 @@ 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) { @@ -50,4 +67,77 @@ public ReviewResDTO.WriteReviewResultDto writeReview(Long memberId, Long mission return ReviewConverter.toWriteReviewResult(savedReview); } + //가게 미션 생성 + @Transactional + public Void 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 null; + } + + //가게 미션 조회 + public MissionResDTO.Pagination 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 MissionConverter.toPagination( + missionList.map(MissionConverter::toGetMission).toList(), + missionList.getNumber(), + missionList.getSize() + ); + } + + //유저가 진행중인 미션 조회하기 + 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() + ); + } + + } From 2213fdd3120458ed38642199fba540d6079cdb36 Mon Sep 17 00:00:00 2001 From: Gibeom Date: Sat, 9 May 2026 20:25:11 +0900 Subject: [PATCH 06/20] =?UTF-8?q?feat=20:=20=EB=A6=AC=EB=B7=B0=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=20=EC=BB=A4=EC=84=9C=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=95=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../review/controller/ReviewController.java | 14 ++- .../review/converter/ReviewConverter.java | 27 +++++ .../domain/review/dto/ReviewResDTO.java | 19 ++++ .../umc10th/domain/review/entity/Review.java | 2 +- .../exception/code/ReviewErrorCode.java | 7 +- .../review/repository/ReviewRepository.java | 26 ++++- .../domain/review/service/ReviewService.java | 104 +++++++++++++++++- 7 files changed, 189 insertions(+), 10 deletions(-) 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 7761a195..351d0744 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 @@ -7,6 +7,8 @@ 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.web.bind.annotation.*; import java.util.List; @@ -18,12 +20,16 @@ public class ReviewController { private final ReviewService reviewService; - //유저의 모든 리뷰 가져오기 + //유저의 모든 리뷰 가져오기 (Cursor) @GetMapping("/v1/members/{memberId}/reviews") - public ApiResponse> getReviews( - @PathVariable Long memberId) { + public ApiResponse> getMemberReviews( + @PathVariable Long memberId, + @RequestParam Integer pageSize, + @RequestParam String cursor, + @RequestParam String query + ) { BaseSuccessCode code = ReviewSuccessCode.OK; - return ApiResponse.onSuccess(code, reviewService.getReviewList(memberId)); + return ApiResponse.onSuccess(code, reviewService.getMemberReviewsOrderById(memberId,pageSize, cursor, query)); } } 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 48d335b9..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 @@ -41,4 +41,31 @@ public static List toReviewDTOList(List reviews) .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/ReviewResDTO.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/review/dto/ReviewResDTO.java index 635c1628..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 @@ -2,6 +2,8 @@ import lombok.Builder; +import java.util.List; + public class ReviewResDTO { @Builder public record reviewDTO( @@ -16,4 +18,21 @@ public record WriteReviewResultDto( 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/Review.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/review/entity/Review.java index 40cf3f4a..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 @@ -17,7 +17,7 @@ @Builder @NoArgsConstructor @AllArgsConstructor -@Table(name = "review") +@Table(name = "Review") public class Review extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) 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 2865eb07..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 @@ -17,7 +17,12 @@ public enum ReviewErrorCode implements BaseErrorCode { 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/repository/ReviewRepository.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/review/repository/ReviewRepository.java index 228e6940..d9f57f18 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,10 +1,34 @@ package com.example.umc10th.domain.review.repository; +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 { - List findByMember_Id(Long memberId); + Page findByMember_Id(Long memberId, Pageable pageable); + + List member(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") int scoreCursor, + 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 425e4401..c7d3e693 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 @@ -3,8 +3,14 @@ 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; @@ -14,8 +20,100 @@ public class ReviewService { private final ReviewRepository reviewRepository; - public List getReviewList(Long memberId) { - List review = reviewRepository.findByMember_Id(memberId); - return ReviewConverter.toReviewDTOList(review); + 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": + Long prevCursor = Long.parseLong(cursorSplit[0]); //이거 워크북 코드에 왜 있는지? + 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); + } + + //다음 커서 계산 (마지막 요소의 id) +// nextCursor = reviewList.getContent().getLast().getId() + " : " + reviewList.getContent().getLast.getId(); + List content = reviewList.getContent(); + nextCursor = content.isEmpty() ? null + : "id:" + content.get(content.size() - 1).getId(); + + // 응답 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": + // 커서 타입 변환 + int scoreCursor = Integer.parseInt(cursorSplit[0]); + + //리뷰들 조회 & where절에 커서 값 기입 + reviewList = reviewRepository.findReviewsByScoreCursor( + memberId, + scoreCursor, + pageRequest); + break; + default: + throw new ReviewException(ReviewErrorCode.QUERY_NOT_VALID); + } + } else { + //커서 없이 조회 + reviewList = reviewRepository.findReviewsByMember_IdOrderByScoreDescIdDesc(memberId, pageRequest); + } + //다음 커서 계산 + List content = reviewList.getContent(); + nextCursor = content.isEmpty() ? null + : content.get(content.size() - 1).getScore() + ":" + content.get(content.size() - 1).getScore(); + //응답 DTO로 포장하기 + return ReviewConverter.toPagination( + reviewList.map(ReviewConverter::toGetReview).toList(), + reviewList.hasNext(), + nextCursor, + reviewList.getSize() + ); } } From 6254b938275a1f18b0da49f0b7e9b675a9fa084a Mon Sep 17 00:00:00 2001 From: Gibeom Date: Sat, 9 May 2026 20:26:36 +0900 Subject: [PATCH 07/20] =?UTF-8?q?refactor=20:=20Store=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EA=B5=AC=ED=98=84=20=EC=9D=BC=EB=B6=80=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../umc10th/domain/store/converter/StoreConverter.java | 5 ++++- .../example/umc10th/domain/store/entity/Region.java | 2 +- .../com/example/umc10th/domain/store/entity/Store.java | 10 +++++++--- .../domain/store/repository/StoreRepository.java | 1 + 4 files changed, 13 insertions(+), 5 deletions(-) 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 98e2cfe8..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,5 +1,7 @@ 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; @@ -11,7 +13,7 @@ public class StoreConverter { //StoreDTO public static StoreResDTO.StoreDTO toStoreDto(Store store) { return StoreResDTO.StoreDTO.builder() - .storeId(store.getStoreId()) + .storeId(store.getId()) .storeName(store.getStoreName()) .address(store.getAddress()) .region(store.getRegion().getRegionName()) @@ -24,4 +26,5 @@ public static List toStoreDtoList(List stores) { .map(StoreConverter::toStoreDto) .collect(Collectors.toList()); } + } 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 b17db63b..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 @@ -14,7 +14,7 @@ @Builder @NoArgsConstructor @AllArgsConstructor -@Table(name = "Region") +@Table(name = "region") public class Region { @Id 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 cb23eb1a..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,5 +1,6 @@ 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; @@ -15,12 +16,12 @@ @NoArgsConstructor @AllArgsConstructor @Builder -@Table(name = "Store") +@Table(name = "store") public class Store { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long storeId; + private Long id; @Column(name = "storeName", nullable = false) private String storeName; @@ -29,7 +30,7 @@ public class Store { private String address; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "region_id", nullable = false) + @JoinColumn(name = "region_id") private Region region; @Builder.Default @@ -39,4 +40,7 @@ public class Store { @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/repository/StoreRepository.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/repository/StoreRepository.java index ad82b8e1..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,5 +1,6 @@ package com.example.umc10th.domain.store.repository; +import com.example.umc10th.domain.mission.entity.Mission; import com.example.umc10th.domain.store.entity.Store; import org.springframework.data.jpa.repository.JpaRepository; From e26be52d07172f7f462843c7a916102e8b62ecc4 Mon Sep 17 00:00:00 2001 From: Gibeom Date: Sat, 9 May 2026 20:27:25 +0900 Subject: [PATCH 08/20] =?UTF-8?q?feat=20:=20Valid=20Exception=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../handler/GeneralExceptionAdvice.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) 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 9d03a68e..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 @@ -6,9 +6,13 @@ 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 { @@ -37,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)); + } } From 64b93bfc48b852d2a4757b701f08b17b27881af3 Mon Sep 17 00:00:00 2001 From: Gibeom Date: Sat, 9 May 2026 20:51:27 +0900 Subject: [PATCH 09/20] =?UTF-8?q?refactor=20:=20=EC=9E=90=EB=B0=94=20?= =?UTF-8?q?=EB=B2=84=EC=A0=84=20=EB=B3=80=EA=B2=BD=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B8=ED=95=9C=20=EC=BD=94=EB=93=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../umc10th/domain/review/service/ReviewService.java | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) 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 c7d3e693..d618ae50 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 @@ -59,11 +59,7 @@ public ReviewResDTO.Pagination getMemberReviewsOrderById } //다음 커서 계산 (마지막 요소의 id) -// nextCursor = reviewList.getContent().getLast().getId() + " : " + reviewList.getContent().getLast.getId(); - List content = reviewList.getContent(); - nextCursor = content.isEmpty() ? null - : "id:" + content.get(content.size() - 1).getId(); - + nextCursor = reviewList.getContent().getLast().getId() + ":" + reviewList.getContent().getLast().getId(); // 응답 DTO로 포장하기 return ReviewConverter.toPagination( reviewList.map(ReviewConverter::toGetReview).toList(), @@ -105,9 +101,7 @@ public ReviewResDTO.Pagination getMemberReviewsOrderBySc reviewList = reviewRepository.findReviewsByMember_IdOrderByScoreDescIdDesc(memberId, pageRequest); } //다음 커서 계산 - List content = reviewList.getContent(); - nextCursor = content.isEmpty() ? null - : content.get(content.size() - 1).getScore() + ":" + content.get(content.size() - 1).getScore(); + nextCursor = reviewList.getContent().getLast().getId() + ":" + reviewList.getContent().getLast().getId(); //응답 DTO로 포장하기 return ReviewConverter.toPagination( reviewList.map(ReviewConverter::toGetReview).toList(), From 7a12edac978d84a66c9d21eb61e7b102d21a1ac4 Mon Sep 17 00:00:00 2001 From: Gibeom Date: Sat, 9 May 2026 20:52:24 +0900 Subject: [PATCH 10/20] =?UTF-8?q?refactor=20:=20=EC=A2=85=EC=86=8D?= =?UTF-8?q?=EC=84=B1=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Gibeom/umc10th/build.gradle | 2 +- Gibeom/umc10th/src/main/resources/application.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Gibeom/umc10th/build.gradle b/Gibeom/umc10th/build.gradle index 6a0ce14e..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) } } diff --git a/Gibeom/umc10th/src/main/resources/application.yml b/Gibeom/umc10th/src/main/resources/application.yml index f7681ea6..874078ee 100644 --- a/Gibeom/umc10th/src/main/resources/application.yml +++ b/Gibeom/umc10th/src/main/resources/application.yml @@ -13,7 +13,7 @@ spring: database-platform: org.hibernate.dialect.MySQLDialect # Hibernate?? ??? MySQL ??(dialect) ?? show-sql: true # ??? SQL ??? ??? ???? ?? ?? hibernate: - ddl-auto: update # ?????? ?? ? ?????? ???? ??? ?? + ddl-auto: create # ?????? ?? ? ?????? ???? ??? ?? properties: hibernate: format_sql: true # ???? SQL ??? ?? ?? ??? \ No newline at end of file From ebfe500939c73061cc7f398981461ab033d18461 Mon Sep 17 00:00:00 2001 From: Gibeom Date: Sun, 10 May 2026 14:42:44 +0900 Subject: [PATCH 11/20] =?UTF-8?q?refactor=20:=20=EC=9E=90=EB=B0=94=20?= =?UTF-8?q?=EB=B2=84=EC=A0=84=20=EB=B3=80=EA=B2=BD=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B8=ED=95=9C=20=EC=BD=94=EB=93=9C=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Gibeom/umc10th/keyword_summary/{ch06.md => ch07.md} | 0 .../example/umc10th/domain/member/service/MemberService.java | 2 +- .../umc10th/domain/mission/controller/MissionController.java | 2 +- .../example/umc10th/domain/review/service/ReviewService.java | 1 - 4 files changed, 2 insertions(+), 3 deletions(-) rename Gibeom/umc10th/keyword_summary/{ch06.md => ch07.md} (100%) diff --git a/Gibeom/umc10th/keyword_summary/ch06.md b/Gibeom/umc10th/keyword_summary/ch07.md similarity index 100% rename from Gibeom/umc10th/keyword_summary/ch06.md rename to Gibeom/umc10th/keyword_summary/ch07.md 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 0d9cf1da..90561356 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 @@ -57,7 +57,7 @@ public List getMissionsByStatus( } else { sortInfo = Sort.by("id").descending(); } - + // PageRequest 클래스를 사용해 Pageable 객체를 인스턴스화 PageRequest pageRequest = PageRequest.of(pageNum, pageSize, sortInfo); 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 1a101437..36e7c085 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 @@ -58,7 +58,7 @@ public ApiResponse> getMissio //내가 진행중인 미션 조회하기 @GetMapping("v1/members/{memberId}/missions") public ApiResponse> getMemberMissions( - @RequestBody Long memberId, + @PathVariable Long memberId, @RequestParam Integer pageSize, @RequestParam Integer pageNumber, @RequestParam(required = false) String sort 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 d618ae50..1e82b29d 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 @@ -41,7 +41,6 @@ public ReviewResDTO.Pagination getMemberReviewsOrderById String[] cursorSplit = cursor.split(":"); switch (query.toLowerCase()) { case "id": - Long prevCursor = Long.parseLong(cursorSplit[0]); //이거 워크북 코드에 왜 있는지? idCursor = Long.parseLong(cursorSplit[1]); // 멤버의 리뷰들 조회 & where 절에 커서값 기입 reviewList = reviewRepository.findReviewsByMember_IdAndIdLessThanOrderByIdDesc( From 6c6ab147d08e0666421557e8ea8b25512deea99a Mon Sep 17 00:00:00 2001 From: Gibeom Date: Sun, 10 May 2026 14:43:18 +0900 Subject: [PATCH 12/20] =?UTF-8?q?docs=20:=207=EC=A3=BC=EC=B0=A8=20?= =?UTF-8?q?=ED=95=B5=EC=8B=AC=20=ED=82=A4=EC=9B=8C=EB=93=9C=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Gibeom/umc10th/keyword_summary/ch07.md | 132 +++++++++++++------------ 1 file changed, 71 insertions(+), 61 deletions(-) diff --git a/Gibeom/umc10th/keyword_summary/ch07.md b/Gibeom/umc10th/keyword_summary/ch07.md index b28a1fba..3481bad5 100644 --- a/Gibeom/umc10th/keyword_summary/ch07.md +++ b/Gibeom/umc10th/keyword_summary/ch07.md @@ -1,80 +1,90 @@ -- JPA란? - - JPA(Java Persistence API)란, **자바 진영에서 ORM(Object-Relational Mapping) 기술의 표준스펙.** - - 실제 구현체는 Hibernatem EclipseLink 등이 있고, 스프링 부트에선 보통 Hibernate를 기본 구현체로 사용. - -- N+1 문제란? - - 1번의 쿼리로 N개의 데이터를 가져왔는데, 연관된 데이터를 채우기 위해 추가로 N번의 쿼리가 실행되는 현상을 말함 +- Page와 Slice + + Spring Data JPA는 페이징을 위해 두가지 객체를 제공한다. Slice & Page + + DB에 저장된 데이터들을 페이지에 맞춰 몇개씩 뿌릴 건지 알려주는 것 + + - Page + - pageable 객체를 사용해 오프셋 기반 페이지네이션을 구현할 수 있는 객체. + - Slice를 상속함 + - Slice + - Page 보다 좀 더 추상적. + - 커서 기반 페이지네이션을 구현할 때 사용할 수 있는 객체 + - Response에 총 데이터 갯수는 보내지 않음 + - Pageable 객체 + - JPA가 제공하는 페이지네이션을 위한 객체 + - 페이징 정보(페이지 번호, 페이지 크기, 정렬방식 등..)를 담고 있는 인터페이스 + - 스프링 Data JPA에서 제공하는 `PageRequest` 클래스를 통해 인스턴스화 가능 + - 사용 이유 + - 모든 데이터를 한번에 뿌리게 되면 성능 저하 등 문제가 발생 + - 페이지네이션을 사용해 적절한 수의 정보만 올려주면 프론트에서 적절한 처리가능. +- Java stream API - 발생과정 + 스프링이 지원하는 함수형 프로그래밍 방식. - 1. 최초쿼리 (1) : `SELECT * FROM Member` 를 실행해 10명의 회원을 가져옴 - 2. 연관 데이터 조회(N) : 즉시 로딩 설정이 되어 있다면, JPA는 각 회원(10명)이 가진 팀(Team) 정보를 채우기 위해 회원별로 팀 조회 쿼리를 각각 날림 - 3. 결과 : 쿼리 1번으로 끝날일이 총 11번(1 + 10) 호출되어 DB 성능이 저하됨 + 함수형 프로그래밍이란? -- 지연로딩과 즉시로딩의 차이는? + - 코드가 “어떻게” 에서 “무엇을”로 포커싱이 옮겨간 프로그래밍 방식 + - 함수형 프로그래밍에서 함수는 `Int` ,`String` 같은 1급 객체로 취급. + - 이 특성을 활용하는 고차함수 : map, filter, reduce 등… + - (기존 객체의 상태를 변경하지 않고, 새로운 결과를 반환) + - 사용 이유 : 가독성, 불변성, 유지보수성, 병렬 처리성 (쓰레드 풀) - 지연 로딩 + 단점 - - 필요할 때 까지 미룬다 - - 엔티티 조회할 때 연관된 객체는 가져오지 않고, 실제 그 데이터를 사용하는 시점에 DB를 조회함 - - 동작 방식 : 조회 시점에 연관된 객체 자리에 프록시라는 가짜 객체를 넣어둠. 실제로 연관객체의 내부 데이터에 접근할 때 SQL이 실행. - - 어노테이션 : `ManyToOne(fetch=FetchType.LAZY)` - - 단점 : 연관된 데이터가 필요 없는 경우 불필요한 조인이나 쿼리를 줄여 성능 최적화 가능 + - for문 같은 단순 반복문에 익숙한 개발자들에게는 람다식과 스트림이 이해가 어려울 수 있음 + - 중단점을 걸어서 디버깅하기 어려움. + - for문 보다 오버헤드가 조금 더 큼. - 즉시 로딩 + 병렬 처리 - - 한번에 다 가져온다 - - 동작 방식 : JPA 구현체는 대개 SQL의 JOIN을 사용하여 한번의 쿼리로 모든 객체를 즉시 함께 조회 - - 어노테이션 : `@ManyToOne(fetch = FetchType.EAGER)` - - 단점 : 연관된 테이블이 많아질수록 쿼리가 복잡해지고, N + 1 문제를 발생시켜 성능 저하의 주범이 됨 + - 작업을 여러 스레드에게 분할해 병렬적으로 처리하는 방법 + - `parallelStram()` 키워드와 `ForkJoinPool()` 을 사용해 스레드 지정 가능 + - CommonPool : 미리 생성해 놓은 스레드를 모든 애플리케이션이 이용하는 방식의 풀 + - 장점 : 정해진 수의 스레드를 미리 생성해 놓고 사용하기 때문에 스레드 생성/삭제 오버헤드 적음. 적은 양의 처리에 유리 + - 단점 : 각각의 애플리케이션 마다 알맞은 처리를 제공하기 힘들 수 있음 + - (군대 배식 느낌) + - ThreadPool : 각각의 컴포넌트마다 설정할 수 있는 풀 + - 장점 : 성능 튜닝 가능 + - 메모리 관리 등 신경써야 할 것들이 있음. + - (오마카세 느낌) +- 객체 그래프 탐색 -- JPQL란? + 객체 그래프 탐색 (Object Graph Navigation) - 엔티티 객체를 대상으로 쿼리하는 객체지향 쿼리 언어(단순히 JPA SQL) + - 객체지향언어에서 참조를 사용해 연관된 객체를 타고 들어가 데이터를 조회하는 방식 + - ex) 멤버 객체에서 member.getTeam()처럼 연관관계를 통해 팀 정보를 가져오는 방식 - - 객체지향 쿼리 : 테이블 컬럼명이 아니라 엔티티의 필드(변수)이름을 사용해 쿼리를 작성함 + 특징 : 객체 간 연관관계를 통해 자유롭게 메모리상의 객체를 이동하며 탐색 - 왜 사용할까? + 장점 : SQL 직접 작성 시 필요한 조인 제약에서 벗어나 논리적인 도메인 모델 구조에 따라 데이터 조회 가능 - - JPA의 기본 메서드 (find())는 식별자로 조회할 때 유용하지만 복잡한 검색조건을 처리하기엔 한계가 있음. - - 코드는 자바객체인데 쿼리는 테이블 기준이면 싱크가 깨짐. JPQL은 자바 클래스와 필드 이름을 그대로 쓰기 때문에 훨씬 직관적 - - 이를 해결하기 위해 SQL의 장점을 가져오면서 객체 지향의 이점을 유지하는 JPQL을 사용. + JPA에서의 활용 : JPA는 연관된 객체를 처음부터 로딩하지 않고 실제 사용 시점에 조회하는 지연로딩을 사용해 객체그래프 탐색을 지원 -- Fetch Join란? + 주의점 : 객체 그래프를 무분별 탐색하는 경우, 하이버네이트 등에서 N+1 문제가 발생할 수 있음. - 연관된 엔티티나 컬렉션을 SQL 한번에 함께 조회하는 방법 + **내 요약** - 1. 일반 Join VS Fetch Join - - **일반 Join** - - 동작 : SQL 상에서는 조인이 일어나지만, JPQL은 SELECT절에 명시된 엔티티만 조회 - - 결과 : 연관된 엔티티는 프록시가 상태로 남아있어, 나중에 접근할 때 추가쿼리가 발생 - - **Fetch Join** - - 동작 : SQL 조인을 실행하면서 연관된 엔티티의 데이터까지 한번에 SELECT해서 가져옴. - - 결과 : 연관된 엔티티가 프록시가 아닌 실제 엔티티로 채워진 상태로 조회됨. 따라서 추가쿼리 없이 데이터를 바로 사용할 수 있음. + - Store store = member.getReviewList().get(0).getStore()와 같은 코드가 있다고 가정. + 1. 멤버의 리뷰리스트를 가져오고 + 2. 0번 객체를 가져오고 + 3. 그 리뷰의 가게를 가져오는 흐름을 표현 가능. + - 여기서 문제 : 어디까지 조회가 가능한가? + - 실행시 모든 데이터를 올려둘 수는 없음. 그렇다면 사용할 때만 필요한 정보를 가져오는 방식 Lazy-Loading(지연로딩)을 사용해 `.` 을 사용해 다음 객체를 불러올때마다 데이터를 가져옴. + - 만약 객체간 연결이 없거나, 다음 데이터를 조회할 수 없다면 null이 조회되거나 오류가 날 것. +- @Valid vs @Validated -- @EntityGraph란? + @Valid - Fetch 조인을 보완하기 위해 나온 기능. 쿼리 수행 시점에 연관된 엔티티들을 함께 조회하도록 설정하는 어노테이션 + - 컨트롤러 단에서 객체의 유효성을 검사할 때 사용 + - 계층 구조 검증이 가능 (User 안에 있는 Address 객체도 검사하고 싶으면 Address 필드 위에 @Valid 를 붙여야 함) + - 한계 : 그룹화 검증이 불가능 - - 사용 이유 - - JPQL의 Fetch Join은 쿼리문 안에 조인코드를 직접 작성해야 한다는 번거로움이 있음. - - 코드의 간결함 : 스프링 부트 JPA를 사용하면 메서드 위에 어노테이션 하나만 붙여서 N + 1 문제를 해결할 수 있음. - - 쿼리분리 : 똑같은 findAll()이라도 어떤 메서드에서는 연관데이터를 가져오고, 어떤 메서드에서는 안 가져오게끔 유연히 관리 가능 - -- commit과 flush 차이점은? - - Flush (플러시) : 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화하는 작업 - - 하는일 : 쓰기 지연 SQL 저장소에 쌓여있던 INSERT, UPDATE, DELETE 쿼리를 DB로 보냄 - - 영향 : 쿼리가 DB로 전송되지만, DB 입장에서는 아직 임시 쿼리 상태임. 다른 트랜잭션에선 이 데이터를 못 봄 - - 영속성 컨텍스트 유지 : 플러시가 일어나도 영속성 컨텍스트 내의 엔티티들은 지워지지 않고 그대로 유지. - - 발생시점 - 1. em.flush() 직접 호출 - 2. 트랜잭션 커밋 시 - 3. JPQL 쿼리 실행 시 - - Commit (커밋) : 데이터베이스의 트랜잭션을 종료하고 변경사항을 영구적으로 반영하는 작업 - - 하는일 : 내부적으로 em.flush()를 먼저 호출해 쿼리를 보낸 후 DB에 커밋 명령을 내림 - - 영향 : 데이터가 실제로 DB에 저장되며, 다른 사용자나 시스템에서도 변경된 데이터를 조회할 수 있게됨 - - 트랜잭션 종료 : 커밋이 완료되면 해당 트랜잭션이 완전히 종료. + @Validated + - 스프링에서 자바표준기능을 확장해 만든 어노테이션 + - 용도 : Service 나 Repository 등 Bean 계층에서 검증이 필요할 때, 혹은 그룹검증이 필요할 때 사용. + - 특징 + - 제약조건 그룹화 : “회원가입때는 이 필드 검사하고, 정보수정때는 하지마” 같은 상황을 `groups` 속성으로 지정가능 + - 클래스 레벨 선언 : 클래스 상단에 `@Validated` 어노테이션을 붙여야 해당 클래스 내부의 메서드 파라미터에 대한 검증이 작동 + - 한계 : 계층구조 검증은 직접적으로 지원하지 않아, 내부 객체에는 여전히 @Valid를 써야함. \ No newline at end of file From 46cda5e716bcf9aff4bab6163163a9fb923e1157 Mon Sep 17 00:00:00 2001 From: Gibeom Date: Mon, 11 May 2026 18:46:27 +0900 Subject: [PATCH 13/20] =?UTF-8?q?refactor=20:=20ddl-auto=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Gibeom/umc10th/src/main/resources/application.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gibeom/umc10th/src/main/resources/application.yml b/Gibeom/umc10th/src/main/resources/application.yml index 874078ee..f7681ea6 100644 --- a/Gibeom/umc10th/src/main/resources/application.yml +++ b/Gibeom/umc10th/src/main/resources/application.yml @@ -13,7 +13,7 @@ spring: database-platform: org.hibernate.dialect.MySQLDialect # Hibernate?? ??? MySQL ??(dialect) ?? show-sql: true # ??? SQL ??? ??? ???? ?? ?? hibernate: - ddl-auto: create # ?????? ?? ? ?????? ???? ??? ?? + ddl-auto: update # ?????? ?? ? ?????? ???? ??? ?? properties: hibernate: format_sql: true # ???? SQL ??? ?? ?? ??? \ No newline at end of file From 067cc435da74a31a112ee3e5686106867a8b3fe2 Mon Sep 17 00:00:00 2001 From: Gibeom Date: Mon, 11 May 2026 18:50:18 +0900 Subject: [PATCH 14/20] =?UTF-8?q?refactor=20:=20member=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=20Repository=20=EB=A9=94=EC=86=8C=EB=93=9C?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/repository/MemberMissionRepository.java | 1 + .../umc10th/domain/member/service/MemberService.java | 9 +++------ 2 files changed, 4 insertions(+), 6 deletions(-) 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 index 87a8a246..8415eda3 100644 --- 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 @@ -13,6 +13,7 @@ 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") 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 90561356..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 @@ -17,7 +17,6 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; -import org.springframework.security.core.parameters.P; import org.springframework.stereotype.Service; import java.util.List; @@ -57,12 +56,10 @@ public List getMissionsByStatus( } else { sortInfo = Sort.by("id").descending(); } - // PageRequest 클래스를 사용해 Pageable 객체를 인스턴스화 - PageRequest pageRequest - = PageRequest.of(pageNum, pageSize, sortInfo); + PageRequest pageRequest = PageRequest.of(pageNum, pageSize, sortInfo); - List memberMissions = memberMissionRepository - .findAllByMember_IdAndStatus(memberId, status); + Page memberMissions = memberMissionRepository + .findAllByMember_IdAndStatus(memberId, status, pageRequest); List missions = memberMissions.stream() .map(MemberMission::getMission) .collect(Collectors.toList()); From b0f79375eee21b130fc6b14a181d234327b28599 Mon Sep 17 00:00:00 2001 From: Gibeom Date: Mon, 11 May 2026 18:51:06 +0900 Subject: [PATCH 15/20] =?UTF-8?q?refactor=20:=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=EB=84=A4=EC=9D=B4=EC=85=98=20Score:id=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../review/repository/ReviewRepository.java | 1 + .../domain/review/service/ReviewService.java | 20 +++++++++++++------ 2 files changed, 15 insertions(+), 6 deletions(-) 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 d9f57f18..57d1901e 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 @@ -29,6 +29,7 @@ public interface ReviewRepository extends JpaRepository { Slice findReviewsByScoreCursor( @Param("memberId") Long memberId, @Param("scoreCursor") int scoreCursor, + @Param("idCursor") int 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 1e82b29d..21cd14e5 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 @@ -19,7 +19,7 @@ @RequiredArgsConstructor public class ReviewService { private final ReviewRepository reviewRepository; - + //id 페이지네이션 public ReviewResDTO.Pagination getMemberReviewsOrderById( Long memberId, Integer pageSize, @@ -57,8 +57,11 @@ public ReviewResDTO.Pagination getMemberReviewsOrderById reviewList = reviewRepository.findReviewsByMember_IdOrderByIdDesc(memberId, pageRequest); } - //다음 커서 계산 (마지막 요소의 id) - nextCursor = reviewList.getContent().getLast().getId() + ":" + reviewList.getContent().getLast().getId(); + 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(), @@ -85,11 +88,13 @@ public ReviewResDTO.Pagination getMemberReviewsOrderBySc case "score": // 커서 타입 변환 int scoreCursor = Integer.parseInt(cursorSplit[0]); + int idCursor = Integer.parseInt(cursorSplit[1]); //리뷰들 조회 & where절에 커서 값 기입 reviewList = reviewRepository.findReviewsByScoreCursor( memberId, scoreCursor, + idCursor, pageRequest); break; default: @@ -99,8 +104,11 @@ public ReviewResDTO.Pagination getMemberReviewsOrderBySc //커서 없이 조회 reviewList = reviewRepository.findReviewsByMember_IdOrderByScoreDescIdDesc(memberId, pageRequest); } - //다음 커서 계산 - nextCursor = reviewList.getContent().getLast().getId() + ":" + reviewList.getContent().getLast().getId(); + 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(), @@ -109,4 +117,4 @@ public ReviewResDTO.Pagination getMemberReviewsOrderBySc reviewList.getSize() ); } -} +} \ No newline at end of file From 7cd6e5fc2620ef0a7be9566dcf2c0990c7c7fc48 Mon Sep 17 00:00:00 2001 From: Gibeom Date: Mon, 11 May 2026 18:51:47 +0900 Subject: [PATCH 16/20] =?UTF-8?q?docs=20:=20Stram=20API=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Gibeom/umc10th/keyword_summary/ch07.md | 132 +++++++++++++------------ 1 file changed, 71 insertions(+), 61 deletions(-) diff --git a/Gibeom/umc10th/keyword_summary/ch07.md b/Gibeom/umc10th/keyword_summary/ch07.md index b28a1fba..701095b1 100644 --- a/Gibeom/umc10th/keyword_summary/ch07.md +++ b/Gibeom/umc10th/keyword_summary/ch07.md @@ -1,80 +1,90 @@ -- JPA란? - - JPA(Java Persistence API)란, **자바 진영에서 ORM(Object-Relational Mapping) 기술의 표준스펙.** - - 실제 구현체는 Hibernatem EclipseLink 등이 있고, 스프링 부트에선 보통 Hibernate를 기본 구현체로 사용. - -- N+1 문제란? - - 1번의 쿼리로 N개의 데이터를 가져왔는데, 연관된 데이터를 채우기 위해 추가로 N번의 쿼리가 실행되는 현상을 말함 +- Page와 Slice + + Spring Data JPA는 페이징을 위해 두가지 객체를 제공한다. Slice & Page + + DB에 저장된 데이터들을 페이지에 맞춰 몇개씩 뿌릴 건지 알려주는 것 + + - Page + - pageable 객체를 사용해 오프셋 기반 페이지네이션을 구현할 수 있는 객체. + - Slice를 상속함 + - Slice + - Page 보다 좀 더 추상적. + - 커서 기반 페이지네이션을 구현할 때 사용할 수 있는 객체 + - Response에 총 데이터 갯수는 보내지 않음 + - Pageable 객체 + - JPA가 제공하는 페이지네이션을 위한 객체 + - 페이징 정보(페이지 번호, 페이지 크기, 정렬방식 등..)를 담고 있는 인터페이스 + - 스프링 Data JPA에서 제공하는 `PageRequest` 클래스를 통해 인스턴스화 가능 + - 사용 이유 + - 모든 데이터를 한번에 뿌리게 되면 성능 저하 등 문제가 발생 + - 페이지네이션을 사용해 적절한 수의 정보만 올려주면 프론트에서 적절한 처리가능. +- Java stream API - 발생과정 + 자바가 지원하는 함수형 프로그래밍 방식. - 1. 최초쿼리 (1) : `SELECT * FROM Member` 를 실행해 10명의 회원을 가져옴 - 2. 연관 데이터 조회(N) : 즉시 로딩 설정이 되어 있다면, JPA는 각 회원(10명)이 가진 팀(Team) 정보를 채우기 위해 회원별로 팀 조회 쿼리를 각각 날림 - 3. 결과 : 쿼리 1번으로 끝날일이 총 11번(1 + 10) 호출되어 DB 성능이 저하됨 + 함수형 프로그래밍이란? -- 지연로딩과 즉시로딩의 차이는? + - 코드가 “어떻게” 에서 “무엇을”로 포커싱이 옮겨간 프로그래밍 방식 + - 함수형 프로그래밍에서 함수는 `Int` ,`String` 같은 1급 객체로 취급. + - 이 특성을 활용하는 고차함수 : map, filter, reduce 등… + - (기존 객체의 상태를 변경하지 않고, 새로운 결과를 반환) + - 사용 이유 : 가독성, 불변성, 유지보수성, 병렬 처리성 (쓰레드 풀) - 지연 로딩 + 단점 - - 필요할 때 까지 미룬다 - - 엔티티 조회할 때 연관된 객체는 가져오지 않고, 실제 그 데이터를 사용하는 시점에 DB를 조회함 - - 동작 방식 : 조회 시점에 연관된 객체 자리에 프록시라는 가짜 객체를 넣어둠. 실제로 연관객체의 내부 데이터에 접근할 때 SQL이 실행. - - 어노테이션 : `ManyToOne(fetch=FetchType.LAZY)` - - 단점 : 연관된 데이터가 필요 없는 경우 불필요한 조인이나 쿼리를 줄여 성능 최적화 가능 + - for문 같은 단순 반복문에 익숙한 개발자들에게는 람다식과 스트림이 이해가 어려울 수 있음 + - 중단점을 걸어서 디버깅하기 어려움. + - for문 보다 오버헤드가 조금 더 큼. - 즉시 로딩 + 병렬 처리 - - 한번에 다 가져온다 - - 동작 방식 : JPA 구현체는 대개 SQL의 JOIN을 사용하여 한번의 쿼리로 모든 객체를 즉시 함께 조회 - - 어노테이션 : `@ManyToOne(fetch = FetchType.EAGER)` - - 단점 : 연관된 테이블이 많아질수록 쿼리가 복잡해지고, N + 1 문제를 발생시켜 성능 저하의 주범이 됨 + - 작업을 여러 스레드에게 분할해 병렬적으로 처리하는 방법 + - `parallelStram()` 키워드와 `ForkJoinPool()` 을 사용해 스레드 지정 가능 + - CommonPool : 미리 생성해 놓은 스레드를 모든 애플리케이션이 이용하는 방식의 풀 + - 장점 : 정해진 수의 스레드를 미리 생성해 놓고 사용하기 때문에 스레드 생성/삭제 오버헤드 적음. 적은 양의 처리에 유리 + - 단점 : 각각의 애플리케이션 마다 알맞은 처리를 제공하기 힘들 수 있음 + - (군대 배식 느낌) + - ThreadPool : 각각의 컴포넌트마다 설정할 수 있는 풀 + - 장점 : 성능 튜닝 가능 + - 메모리 관리 등 신경써야 할 것들이 있음. + - (오마카세 느낌) +- 객체 그래프 탐색 -- JPQL란? + 객체 그래프 탐색 (Object Graph Navigation) - 엔티티 객체를 대상으로 쿼리하는 객체지향 쿼리 언어(단순히 JPA SQL) + - 객체지향언어에서 참조를 사용해 연관된 객체를 타고 들어가 데이터를 조회하는 방식 + - ex) 멤버 객체에서 member.getTeam()처럼 연관관계를 통해 팀 정보를 가져오는 방식 - - 객체지향 쿼리 : 테이블 컬럼명이 아니라 엔티티의 필드(변수)이름을 사용해 쿼리를 작성함 + 특징 : 객체 간 연관관계를 통해 자유롭게 메모리상의 객체를 이동하며 탐색 - 왜 사용할까? + 장점 : SQL 직접 작성 시 필요한 조인 제약에서 벗어나 논리적인 도메인 모델 구조에 따라 데이터 조회 가능 - - JPA의 기본 메서드 (find())는 식별자로 조회할 때 유용하지만 복잡한 검색조건을 처리하기엔 한계가 있음. - - 코드는 자바객체인데 쿼리는 테이블 기준이면 싱크가 깨짐. JPQL은 자바 클래스와 필드 이름을 그대로 쓰기 때문에 훨씬 직관적 - - 이를 해결하기 위해 SQL의 장점을 가져오면서 객체 지향의 이점을 유지하는 JPQL을 사용. + JPA에서의 활용 : JPA는 연관된 객체를 처음부터 로딩하지 않고 실제 사용 시점에 조회하는 지연로딩을 사용해 객체그래프 탐색을 지원 -- Fetch Join란? + 주의점 : 객체 그래프를 무분별 탐색하는 경우, 하이버네이트 등에서 N+1 문제가 발생할 수 있음. - 연관된 엔티티나 컬렉션을 SQL 한번에 함께 조회하는 방법 + **내 요약** - 1. 일반 Join VS Fetch Join - - **일반 Join** - - 동작 : SQL 상에서는 조인이 일어나지만, JPQL은 SELECT절에 명시된 엔티티만 조회 - - 결과 : 연관된 엔티티는 프록시가 상태로 남아있어, 나중에 접근할 때 추가쿼리가 발생 - - **Fetch Join** - - 동작 : SQL 조인을 실행하면서 연관된 엔티티의 데이터까지 한번에 SELECT해서 가져옴. - - 결과 : 연관된 엔티티가 프록시가 아닌 실제 엔티티로 채워진 상태로 조회됨. 따라서 추가쿼리 없이 데이터를 바로 사용할 수 있음. + - Store store = member.getReviewList().get(0).getStore()와 같은 코드가 있다고 가정. + 1. 멤버의 리뷰리스트를 가져오고 + 2. 0번 객체를 가져오고 + 3. 그 리뷰의 가게를 가져오는 흐름을 표현 가능. + - 여기서 문제 : 어디까지 조회가 가능한가? + - 실행시 모든 데이터를 올려둘 수는 없음. 그렇다면 사용할 때만 필요한 정보를 가져오는 방식 Lazy-Loading(지연로딩)을 사용해 `.` 을 사용해 다음 객체를 불러올때마다 데이터를 가져옴. + - 만약 객체간 연결이 없거나, 다음 데이터를 조회할 수 없다면 null이 조회되거나 오류가 날 것. +- @Valid vs @Validated -- @EntityGraph란? + @Valid - Fetch 조인을 보완하기 위해 나온 기능. 쿼리 수행 시점에 연관된 엔티티들을 함께 조회하도록 설정하는 어노테이션 + - 컨트롤러 단에서 객체의 유효성을 검사할 때 사용 + - 계층 구조 검증이 가능 (User 안에 있는 Address 객체도 검사하고 싶으면 Address 필드 위에 @Valid 를 붙여야 함) + - 한계 : 그룹화 검증이 불가능 - - 사용 이유 - - JPQL의 Fetch Join은 쿼리문 안에 조인코드를 직접 작성해야 한다는 번거로움이 있음. - - 코드의 간결함 : 스프링 부트 JPA를 사용하면 메서드 위에 어노테이션 하나만 붙여서 N + 1 문제를 해결할 수 있음. - - 쿼리분리 : 똑같은 findAll()이라도 어떤 메서드에서는 연관데이터를 가져오고, 어떤 메서드에서는 안 가져오게끔 유연히 관리 가능 - -- commit과 flush 차이점은? - - Flush (플러시) : 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화하는 작업 - - 하는일 : 쓰기 지연 SQL 저장소에 쌓여있던 INSERT, UPDATE, DELETE 쿼리를 DB로 보냄 - - 영향 : 쿼리가 DB로 전송되지만, DB 입장에서는 아직 임시 쿼리 상태임. 다른 트랜잭션에선 이 데이터를 못 봄 - - 영속성 컨텍스트 유지 : 플러시가 일어나도 영속성 컨텍스트 내의 엔티티들은 지워지지 않고 그대로 유지. - - 발생시점 - 1. em.flush() 직접 호출 - 2. 트랜잭션 커밋 시 - 3. JPQL 쿼리 실행 시 - - Commit (커밋) : 데이터베이스의 트랜잭션을 종료하고 변경사항을 영구적으로 반영하는 작업 - - 하는일 : 내부적으로 em.flush()를 먼저 호출해 쿼리를 보낸 후 DB에 커밋 명령을 내림 - - 영향 : 데이터가 실제로 DB에 저장되며, 다른 사용자나 시스템에서도 변경된 데이터를 조회할 수 있게됨 - - 트랜잭션 종료 : 커밋이 완료되면 해당 트랜잭션이 완전히 종료. + @Validated + - 스프링에서 자바표준기능을 확장해 만든 어노테이션 + - 용도 : Service 나 Repository 등 Bean 계층에서 검증이 필요할 때, 혹은 그룹검증이 필요할 때 사용. + - 특징 + - 제약조건 그룹화 : “회원가입때는 이 필드 검사하고, 정보수정때는 하지마” 같은 상황을 `groups` 속성으로 지정가능 + - 클래스 레벨 선언 : 클래스 상단에 `@Validated` 어노테이션을 붙여야 해당 클래스 내부의 메서드 파라미터에 대한 검증이 작동 + - 한계 : 계층구조 검증은 직접적으로 지원하지 않아, 내부 객체에는 여전히 @Valid를 써야함. \ No newline at end of file From ed3252369b5aa75dfd662c03cfa5962ee68476a4 Mon Sep 17 00:00:00 2001 From: Gibeom Date: Tue, 12 May 2026 20:02:09 +0900 Subject: [PATCH 17/20] =?UTF-8?q?refactor=20:=20=EB=AF=B8=EC=85=98=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=EC=8B=9C=20Void=20=EB=B0=98=ED=99=98=20?= =?UTF-8?q?=EB=8C=80=EC=8B=A0=20=EC=83=9D=EC=84=B1=EB=90=9C=20=EB=AF=B8?= =?UTF-8?q?=EC=85=98=20=EC=A0=95=EB=B3=B4=20=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/mission/controller/MissionController.java | 8 +++++--- .../domain/mission/converter/MissionConverter.java | 11 +++++++++++ .../umc10th/domain/mission/dto/MissionReqDTO.java | 3 ++- .../umc10th/domain/mission/dto/MissionResDTO.java | 10 ++++++++++ .../domain/mission/service/MissionService.java | 12 ++++-------- 5 files changed, 32 insertions(+), 12 deletions(-) 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 36e7c085..03f5f536 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 @@ -11,6 +11,7 @@ import com.example.umc10th.global.apiPayload.code.BaseSuccessCode; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; @@ -33,9 +34,10 @@ public ApiResponse writeReview( BaseSuccessCode code = ReviewSuccessCode.WRITE_SUCCESS; return ApiResponse.onSuccess(code, missionService.writeReview(memberId, missionId, dto)); } + //가게 미션 생성 @PostMapping("v1/stores/{storeId}/missions") - public ApiResponse createMission( + public ApiResponse createMission( @PathVariable Long storeId, @RequestBody @Valid MissionReqDTO.CreateMission dto ){ @@ -43,9 +45,9 @@ public ApiResponse createMission( return ApiResponse.onSuccess(code, missionService.createMission(storeId, dto)); } - //가게 미션들 조회 + //가게 미션들 조회 (오프셋 기반) @GetMapping("v1/store/{storeId}/missions") - public ApiResponse> getMissions( + public ApiResponse> getMissions( @PathVariable Long storeId, @RequestParam Integer pageSize, @RequestParam Integer pageNumber, 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 938a879a..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 @@ -59,4 +59,15 @@ public static MissionResDTO.Pagination toPagination( .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 a9319844..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,6 +1,7 @@ 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; @@ -12,7 +13,7 @@ public record CreateMission( LocalDate deadLine, @NotNull(message = "미션 성공 포인트는 필수입니다.") Integer point, - @NotNull(message = "조건은 빈칸일 수 없습니다.") + @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 0c075bf1..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 @@ -3,6 +3,7 @@ import com.example.umc10th.domain.member.enums.MissionStatus; import lombok.Builder; +import java.time.LocalDate; import java.util.List; public class MissionResDTO { @@ -29,4 +30,13 @@ public record Pagination( 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/service/MissionService.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/mission/service/MissionService.java index 124d440c..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 @@ -69,7 +69,7 @@ public ReviewResDTO.WriteReviewResultDto writeReview(Long memberId, Long mission //가게 미션 생성 @Transactional - public Void createMission( + public MissionResDTO.CreateMissionResult createMission( Long storeId, MissionReqDTO.CreateMission dto ){ @@ -81,11 +81,11 @@ public Void createMission( //미션 DB에 미션 저장 missionRepository.save(mission); - return null; + return MissionConverter.toCreateMissionResult(mission); } //가게 미션 조회 - public MissionResDTO.Pagination getMissions( + public Page getMissions( Long storeId, Integer pageSize, Integer pageNumber, @@ -105,11 +105,7 @@ public MissionResDTO.Pagination getMissions( //가게 내 미션들 조회 Page missionList = missionRepository.findAllByStore_Id(storeId, pageRequest); - return MissionConverter.toPagination( - missionList.map(MissionConverter::toGetMission).toList(), - missionList.getNumber(), - missionList.getSize() - ); + return missionList.map(MissionConverter::toGetMission); } //유저가 진행중인 미션 조회하기 From 04c0f1dba379f3cafe5b9913a7b9a11fc98bc28f Mon Sep 17 00:00:00 2001 From: Gibeom Date: Tue, 12 May 2026 20:28:10 +0900 Subject: [PATCH 18/20] =?UTF-8?q?refactor=20:=20ApiResponse=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=20=EB=8C=80=EC=8B=A0=20ResponseEntity=20=EB=9E=98?= =?UTF-8?q?=ED=95=91=20=ED=9B=84=20=EB=B0=98=ED=99=98=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/controller/MemberController.java | 23 ++++++++++---- .../mission/controller/MissionController.java | 30 ++++++++++++++----- .../review/controller/ReviewController.java | 8 +++-- .../store/controller/StoreController.java | 12 +++++--- 4 files changed, 53 insertions(+), 20 deletions(-) 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 2343770c..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 @@ -8,6 +8,8 @@ import com.example.umc10th.global.apiPayload.code.BaseSuccessCode; 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.*; @@ -21,26 +23,32 @@ public class MemberController { private final MemberService memberService; //마이페이지 @GetMapping("/me") - public ApiResponse getInfo( + public ResponseEntity> getInfo( @AuthenticationPrincipal Long memberId ){ BaseSuccessCode code = MemberSuccessCode.OK; - return ApiResponse.onSuccess(code, memberService.getInfo(memberId)); + MemberResDTO.GetInfo result = memberService.getInfo(memberId); + return ResponseEntity + .status(code.getStatus()) + .body(ApiResponse.onSuccess(code, result)); } // 홈화면 @GetMapping("/home") - public ApiResponse getHome( + public ResponseEntity> getHome( @AuthenticationPrincipal Long memberId, @RequestParam(defaultValue = "0") int page ){ BaseSuccessCode code = MemberSuccessCode.OK; - return ApiResponse.onSuccess(code, memberService.getHome(memberId, page)); + MemberResDTO.HomeResultDto result = memberService.getHome(memberId, page); + return ResponseEntity + .status(code.getStatus()) + .body(ApiResponse.onSuccess(code, result)); } // 진행중/완료 미션 목록 조회 @GetMapping("/missions") - public ApiResponse> getMissionsByStatus( + public ResponseEntity>> getMissionsByStatus( @AuthenticationPrincipal Long memberId, @RequestParam MissionStatus status, @RequestParam Integer pageSize, @@ -48,7 +56,10 @@ public ApiResponse> getMissionsByStatus( @RequestParam (required = false) String sort ){ BaseSuccessCode code = MemberSuccessCode.OK; - return ApiResponse.onSuccess(code, memberService.getMissionsByStatus(memberId, status, pageSize, pageNum, sort)); + 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/mission/controller/MissionController.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/mission/controller/MissionController.java index 03f5f536..0aa3f8f1 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 @@ -12,6 +12,8 @@ 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.*; @@ -27,45 +29,57 @@ public class MissionController { //리뷰 작성 (완료된 미션에 한해서) @PostMapping("v1/missions/{missionId}/reviews") - public ApiResponse writeReview( + public ResponseEntity> writeReview( @PathVariable Long missionId, @AuthenticationPrincipal Long memberId, @RequestBody ReviewReqDTO.WriteReviewDto dto) { BaseSuccessCode code = ReviewSuccessCode.WRITE_SUCCESS; - return ApiResponse.onSuccess(code, missionService.writeReview(memberId, missionId, dto)); + ReviewResDTO.WriteReviewResultDto result = missionService.writeReview(memberId, missionId, dto); + return ResponseEntity + .status(code.getStatus()) + .body(ApiResponse.onSuccess(code, result)); } //가게 미션 생성 @PostMapping("v1/stores/{storeId}/missions") - public ApiResponse createMission( + public ResponseEntity> createMission( @PathVariable Long storeId, @RequestBody @Valid MissionReqDTO.CreateMission dto ){ MissionSuccessCode code = MissionSuccessCode.CREATED; - return ApiResponse.onSuccess(code, missionService.createMission(storeId, dto)); + MissionResDTO.CreateMissionResult result = missionService.createMission(storeId, dto); + return ResponseEntity + .status(code.getStatus()) + .body(ApiResponse.onSuccess(code, result)); } //가게 미션들 조회 (오프셋 기반) @GetMapping("v1/store/{storeId}/missions") - public ApiResponse> getMissions( + public ResponseEntity>> getMissions( @PathVariable Long storeId, @RequestParam Integer pageSize, @RequestParam Integer pageNumber, @RequestParam(required = false) String sort ){ BaseSuccessCode code = MissionSuccessCode.OK; - return ApiResponse.onSuccess(code, missionService.getMissions(storeId, pageSize, pageNumber, sort)); + Page result = missionService.getMissions(storeId, pageSize, pageNumber, sort); + return ResponseEntity + .status(code.getStatus()) + .body(ApiResponse.onSuccess(code, result)); } //내가 진행중인 미션 조회하기 @GetMapping("v1/members/{memberId}/missions") - public ApiResponse> getMemberMissions( + public ResponseEntity>> getMemberMissions( @PathVariable Long memberId, @RequestParam Integer pageSize, @RequestParam Integer pageNumber, @RequestParam(required = false) String sort ){ BaseSuccessCode code = MissionSuccessCode.OK; - return ApiResponse.onSuccess(code, missionService.getMemberMissions(memberId, pageSize, pageNumber, sort)); + 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/review/controller/ReviewController.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/review/controller/ReviewController.java index 351d0744..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 @@ -9,6 +9,7 @@ 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; @@ -22,14 +23,17 @@ public class ReviewController { //유저의 모든 리뷰 가져오기 (Cursor) @GetMapping("/v1/members/{memberId}/reviews") - public ApiResponse> getMemberReviews( + public ResponseEntity>> getMemberReviews( @PathVariable Long memberId, @RequestParam Integer pageSize, @RequestParam String cursor, @RequestParam String query ) { BaseSuccessCode code = ReviewSuccessCode.OK; - return ApiResponse.onSuccess(code, reviewService.getMemberReviewsOrderById(memberId,pageSize, cursor, query)); + 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/store/controller/StoreController.java b/Gibeom/umc10th/src/main/java/com/example/umc10th/domain/store/controller/StoreController.java index 386d4589..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 @@ -6,6 +6,7 @@ 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; @@ -20,11 +21,14 @@ public class StoreController { private final StoreService storeService; - @GetMapping("/v1/stores") // [수정] 경로 앞에 / 추가 - public ApiResponse> getStoreList( - @RequestParam Long regionId // [수정] Region 엔티티 → Long + @GetMapping("/v1/stores") + public ResponseEntity>> getStoreList( + @RequestParam Long regionId ){ BaseSuccessCode code = StoreSuccessCode.OK; - return ApiResponse.onSuccess(code, storeService.getStoreList(regionId)); + List result = storeService.getStoreList(regionId); + return ResponseEntity + .status(code.getStatus()) + .body(ApiResponse.onSuccess(code, result)); } } From c9f6174fc96819e3ca69e3ee44c4f142eb41bb9e Mon Sep 17 00:00:00 2001 From: Gibeom Date: Tue, 12 May 2026 21:11:20 +0900 Subject: [PATCH 19/20] =?UTF-8?q?refactor=20:=20controller=20=EA=B2=BD?= =?UTF-8?q?=EB=A1=9C=20=ED=86=B5=EC=9D=BC=20=EB=B0=8F=20review=20=EC=BB=A4?= =?UTF-8?q?=EC=84=9C=20=ED=8E=98=EC=9D=B4=EC=A7=80=EB=84=A4=EC=9D=B4?= =?UTF-8?q?=EC=85=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/mission/controller/MissionController.java | 2 +- .../umc10th/domain/review/repository/ReviewRepository.java | 6 +++--- .../umc10th/domain/review/service/ReviewService.java | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) 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 0aa3f8f1..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 @@ -54,7 +54,7 @@ public ResponseEntity> createMis } //가게 미션들 조회 (오프셋 기반) - @GetMapping("v1/store/{storeId}/missions") + @GetMapping("v1/stores/{storeId}/missions") public ResponseEntity>> getMissions( @PathVariable Long storeId, @RequestParam Integer pageSize, 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 57d1901e..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 @@ -14,7 +14,7 @@ public interface ReviewRepository extends JpaRepository { Page findByMember_Id(Long memberId, Pageable pageable); - List member(Member member); + List findByMember(Member member); // id 순 페이징 Slice findReviewsByMember_IdAndIdLessThanOrderByIdDesc(Long memberId, Long idCursor, Pageable pageable); @@ -28,8 +28,8 @@ public interface ReviewRepository extends JpaRepository { "ORDER BY r.score DESC, r.id DESC") Slice findReviewsByScoreCursor( @Param("memberId") Long memberId, - @Param("scoreCursor") int scoreCursor, - @Param("idCursor") int idCursor, + @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 21cd14e5..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 @@ -87,8 +87,8 @@ public ReviewResDTO.Pagination getMemberReviewsOrderBySc switch (query.toLowerCase()) { case "score": // 커서 타입 변환 - int scoreCursor = Integer.parseInt(cursorSplit[0]); - int idCursor = Integer.parseInt(cursorSplit[1]); + long scoreCursor = Long.parseLong(cursorSplit[0]); + long idCursor = Long.parseLong(cursorSplit[1]); //리뷰들 조회 & where절에 커서 값 기입 reviewList = reviewRepository.findReviewsByScoreCursor( From f4553da44a78a0c0e49196b2f91510e7ca2bf9ee Mon Sep 17 00:00:00 2001 From: Gibeom Date: Tue, 12 May 2026 21:16:12 +0900 Subject: [PATCH 20/20] =?UTF-8?q?docs=20:=20FeedBack=20=EC=A1=B0=EC=82=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../umc10th/feedback_summary/FeedBack_Study | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 Gibeom/umc10th/feedback_summary/FeedBack_Study 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