diff --git a/leeseo/umc10th/build.gradle b/leeseo/umc10th/build.gradle index 93fc11f..fba59ff 100644 --- a/leeseo/umc10th/build.gradle +++ b/leeseo/umc10th/build.gradle @@ -32,6 +32,9 @@ dependencies { // Swagger implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:3.0.1' implementation 'org.springdoc:springdoc-openapi-starter-webmvc-api:3.0.1' + + // Validation + implementation 'org.springframework.boot:spring-boot-starter-validation' } tasks.named('test') { diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/Umc10thApplication.java b/leeseo/umc10th/src/main/java/com/example/umc10th/Umc10thApplication.java index 35d830f..330e630 100644 --- a/leeseo/umc10th/src/main/java/com/example/umc10th/Umc10thApplication.java +++ b/leeseo/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/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/controller/MemberController.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/controller/MemberController.java index 1666933..6c73c24 100644 --- a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/controller/MemberController.java +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/controller/MemberController.java @@ -1,10 +1,13 @@ 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.exception.code.MemberErrorCode; import com.example.umc10th.domain.member.exception.code.MemberSuccessCode; +import com.example.umc10th.domain.member.service.MemberService; import com.example.umc10th.global.apiPayload.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; @@ -14,6 +17,8 @@ @Tag(name = "Member", description = "회원 관련 API") public class MemberController implements MemberControllerDocs{ + private final MemberService memberService; + @PostMapping("/me/term") public ApiResponse saveTermAgreement( @RequestBody MemberReqDto.TermList dto @@ -22,19 +27,27 @@ public ApiResponse saveTermAgreement( return ApiResponse.onSuccess(MemberSuccessCode.TERM_POST_OK, null); } + @GetMapping("/me/profile") + public ApiResponse getProfile( + @RequestParam Long id + ) { + MemberResDto.Profile response = memberService.getProfile(id); + return ApiResponse.onSuccess(MemberSuccessCode.PROFILE_GET_OK, response); + } + @PatchMapping("/me/profile") public ApiResponse updateProfile( - @RequestBody MemberReqDto.Profile dto + @RequestBody @Valid MemberReqDto.Profile dto ) { - // 서비스 로직 + memberService.updateProfile(dto); return ApiResponse.onSuccess(MemberSuccessCode.PROFILE_PATCH_OK, null); } @PatchMapping("/me/nickname") public ApiResponse updateNickname( - @RequestBody MemberReqDto.Nickname dto + @RequestBody @Valid MemberReqDto.Nickname dto ) { - // 서비스 로직 + memberService.updateNickname(dto); return ApiResponse.onSuccess(MemberSuccessCode.NICKNAME_PATCH_OK, null); } } diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/controller/MemberControllerDocs.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/controller/MemberControllerDocs.java index b441049..28022fa 100644 --- a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/controller/MemberControllerDocs.java +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/controller/MemberControllerDocs.java @@ -1,10 +1,12 @@ 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.global.apiPayload.ApiResponse; import io.swagger.v3.oas.annotations.Operation; -import org.springframework.web.bind.annotation.PatchMapping; +import jakarta.validation.Valid; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; public interface MemberControllerDocs { @@ -16,20 +18,27 @@ public ApiResponse saveTermAgreement( @RequestBody MemberReqDto.TermList dto ); + @Operation( + summary = "회원정보 조회", + description = "회원 프로필 정보를 조회합니다." + ) + public ApiResponse getProfile( + @RequestParam Long id + ); + @Operation( summary = "회원정보 저장", description = "회원 프로필 정보를 저장합니다." ) public ApiResponse updateProfile( - @RequestBody MemberReqDto.Profile dto + @RequestBody @Valid MemberReqDto.Profile dto ); @Operation( summary = "닉네임 수정", description = "닉네임을 수정합니다." ) - @PatchMapping("/me/nickname") public ApiResponse updateNickname( - @RequestBody MemberReqDto.Nickname dto + @RequestBody @Valid MemberReqDto.Nickname dto ); } diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/converter/MemberConverter.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/converter/MemberConverter.java index 36fbf58..7e73c88 100644 --- a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/converter/MemberConverter.java +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/converter/MemberConverter.java @@ -1,4 +1,33 @@ package com.example.umc10th.domain.member.converter; +import com.example.umc10th.domain.member.dto.MemberResDto; +import com.example.umc10th.domain.member.entity.Food; +import com.example.umc10th.domain.member.entity.Member; +import com.example.umc10th.domain.member.entity.mapping.MemberFood; + public class MemberConverter { + + public static MemberResDto.Profile toProfile( + Member member + ) { + return MemberResDto.Profile.builder() + .id(member.getId()) + .nickname(member.getNickname()) + .birth(member.getBirth()) + .address(member.getAddress()) + .fullAddress(member.getFullAddress()) + .gender(member.getGender()) + .food(member.getMemberFoodList().stream().map(MemberFood::getFood).toList()) + .build(); + } + + public static MemberFood toMemberFood( + Food food, + Member member + ) { + return MemberFood.builder() + .member(member) + .food(food) + .build(); + } } diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/dto/MemberReqDto.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/dto/MemberReqDto.java index a89a927..daf2e1a 100644 --- a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/dto/MemberReqDto.java +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/dto/MemberReqDto.java @@ -1,8 +1,11 @@ package com.example.umc10th.domain.member.dto; -import com.example.umc10th.domain.member.entity.Food; +import com.example.umc10th.domain.member.enums.FoodType; import com.example.umc10th.domain.member.enums.Gender; import com.example.umc10th.domain.mission.enums.Address; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import java.time.LocalDate; import java.util.List; @@ -13,21 +16,29 @@ public record TermList ( List terms ) {} + @Schema(name = "Term", description = "약관별 동의 여부를 저장합니다.") public record Term ( Long termId, Boolean agreed ) {} + @Schema(name = "Profile", description = "수정할 프로필 정보를 저장합합니다.") public record Profile ( - String nickname, - Gender gender, - LocalDate birth, - Address address, - String fullAddress, - List food + @NotNull(message = "사용자 ID는 필수입니다.") + Long id, + String nickname, + Gender gender, + LocalDate birth, + Address address, + String fullAddress, + List food ) {} + @Schema(name = "Nickname", description = "수정할 닉네임을 저장합합니다.") public record Nickname ( - String nickname + @NotNull(message = "사용자 ID는 필수입니다.") + Long id, + @NotBlank(message = "닉네임 입력은 필수입니다.") + String nickname ) {} } diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/dto/MemberResDto.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/dto/MemberResDto.java index 49125e5..126d6b3 100644 --- a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/dto/MemberResDto.java +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/dto/MemberResDto.java @@ -1,4 +1,23 @@ package com.example.umc10th.domain.member.dto; +import com.example.umc10th.domain.member.entity.Food; +import com.example.umc10th.domain.member.enums.Gender; +import com.example.umc10th.domain.mission.enums.Address; +import lombok.Builder; + +import java.time.LocalDate; +import java.util.List; + public class MemberResDto { + + @Builder + public record Profile ( + Long id, + String nickname, + Gender gender, + LocalDate birth, + Address address, + String fullAddress, + List food + ) {} } diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/entity/Food.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/entity/Food.java index 68d1c4d..bb9f2bd 100644 --- a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/entity/Food.java +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/entity/Food.java @@ -1,4 +1,32 @@ package com.example.umc10th.domain.member.entity; +import com.example.umc10th.domain.member.entity.mapping.MemberFood; +import com.example.umc10th.domain.member.enums.FoodType; +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 = "food") public class Food { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "name", nullable = false) + @Enumerated(EnumType.STRING) + private FoodType name; + + @OneToMany(mappedBy = "food") + private List memberFoodList = new ArrayList<>(); } diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/entity/Member.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/entity/Member.java index 50a2c72..8d08be7 100644 --- a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/entity/Member.java +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/entity/Member.java @@ -1,4 +1,124 @@ package com.example.umc10th.domain.member.entity; -public class Member { +import com.example.umc10th.domain.member.converter.MemberConverter; +import com.example.umc10th.domain.member.entity.mapping.MemberFood; +import com.example.umc10th.domain.member.entity.mapping.MemberTerm; +import com.example.umc10th.domain.member.enums.FoodType; +import com.example.umc10th.domain.member.enums.Gender; +import com.example.umc10th.domain.member.enums.SocialType; +import com.example.umc10th.domain.member.enums.Status; +import com.example.umc10th.domain.mission.entity.mapping.MemberMission; +import com.example.umc10th.domain.mission.enums.Address; +import com.example.umc10th.domain.review.entity.Review; +import com.example.umc10th.global.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; + +@Entity +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Table(name = "member") +public class Member extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "nickname", unique = true) + private String nickname; + + @Builder.Default + @Column(name = "gender") + @Enumerated(EnumType.STRING) + private Gender gender = Gender.NONE; + + @Column(name = "birth") + private LocalDate birth; + + @Column(name = "email", unique = true) + private String email; + + @Column(name = "phone_number", unique = true) + private String phoneNumber; + + @Column(name = "address") + @Enumerated(EnumType.STRING) + private Address address; + + @Column(name = "full_address") + private String fullAddress; + + @Column(name = "profile_image") + private String profileImage; + + @Column(name = "social_type", nullable = false) + @Enumerated(EnumType.STRING) + private SocialType socialType; + + @Column(name = "social_id", unique = true, nullable = false) + private String socialId; + + @Builder.Default + @Column(name = "point", nullable = false) + private Integer point = 0; + + @Builder.Default + @Column(name = "status", nullable = false) + @Enumerated(EnumType.STRING) + private Status status = Status.ACTIVATE; + + @Builder.Default + @Column(name = "is_owner", nullable = false) + private Boolean isOwner = false; + + @OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true) + private List memberFoodList = new ArrayList<>(); + + @OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true) + private List memberTermList = new ArrayList<>(); + + @OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true) + private List memberMissionList = new ArrayList<>(); + + @OneToMany(mappedBy = "member") + private List reviewList = new ArrayList<>(); + + public void updateNickname(String nickname) { + this.nickname = nickname; + } + + public void updateGender(Gender gender) { + this.gender = gender; + } + + public void updateBirth(LocalDate birth) { + this.birth = birth; + } + + public void updateAddress(Address address) { + this.address = address; + } + + public void updateFullAddress(String fullAddress) { + this.fullAddress = fullAddress; + } + + public void updateFoodList(List foodList) { + List list = new ArrayList<>(); + for (Food food : foodList) { + list.add(MemberConverter.toMemberFood(food, this)); + } + this.memberFoodList.clear(); + this.memberFoodList.addAll(list); + } + } diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/entity/Term.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/entity/Term.java index 9897e2e..740c525 100644 --- a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/entity/Term.java +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/entity/Term.java @@ -1,4 +1,34 @@ package com.example.umc10th.domain.member.entity; +import com.example.umc10th.domain.member.entity.mapping.MemberTerm; +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 = "term") public class Term { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "title", nullable = false) + private String title; + + @Column(name = "required", nullable = false) + private Boolean required; + + @OneToMany(mappedBy = "term") + private List memberTermList = new ArrayList<>(); } + diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/entity/mapping/MemberFood.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/entity/mapping/MemberFood.java index aeaf994..f974fbd 100644 --- a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/entity/mapping/MemberFood.java +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/entity/mapping/MemberFood.java @@ -1,4 +1,32 @@ package com.example.umc10th.domain.member.entity.mapping; +import com.example.umc10th.domain.member.entity.Food; +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; + +@Entity +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Table(name = "member_food") public class MemberFood { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id") + private Member member; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "food_id") + private Food food; + } diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/entity/mapping/MemberTerm.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/entity/mapping/MemberTerm.java index b944787..20e7f8f 100644 --- a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/entity/mapping/MemberTerm.java +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/entity/mapping/MemberTerm.java @@ -1,4 +1,34 @@ package com.example.umc10th.domain.member.entity.mapping; +import com.example.umc10th.domain.member.entity.Member; +import com.example.umc10th.domain.member.entity.Term; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Table(name = "member_term") public class MemberTerm { -} + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "agreement", nullable = false) + private boolean agreement; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id") + private Member member; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "term_id") + private Term term; + +} \ No newline at end of file diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/enums/FoodType.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/enums/FoodType.java new file mode 100644 index 0000000..7f41963 --- /dev/null +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/enums/FoodType.java @@ -0,0 +1,5 @@ +package com.example.umc10th.domain.member.enums; + +public enum FoodType { + CHINESE, KOREAN, ITALIAN, JAPANESE +} diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/enums/Gender.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/enums/Gender.java index 6cbfe0c..6d75097 100644 --- a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/enums/Gender.java +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/enums/Gender.java @@ -1,5 +1,5 @@ package com.example.umc10th.domain.member.enums; public enum Gender { - MALE, FEMALE + MALE, FEMALE, NONE } diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/enums/Status.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/enums/Status.java new file mode 100644 index 0000000..267c125 --- /dev/null +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/enums/Status.java @@ -0,0 +1,5 @@ +package com.example.umc10th.domain.member.enums; + +public enum Status { + ACTIVATE, INACTIVATE, DELETED +} diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/exception/FoodException.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/exception/FoodException.java new file mode 100644 index 0000000..9362295 --- /dev/null +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/exception/FoodException.java @@ -0,0 +1,10 @@ +package com.example.umc10th.domain.member.exception; + +import com.example.umc10th.global.apiPayload.code.BaseErrorCode; +import com.example.umc10th.global.apiPayload.exception.BaseException; + +public class FoodException extends BaseException { + public FoodException(BaseErrorCode code) { + super(code); + } +} diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/exception/MemberException.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/exception/MemberException.java index 296482e..66b60d6 100644 --- a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/exception/MemberException.java +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/exception/MemberException.java @@ -1,7 +1,10 @@ package com.example.umc10th.domain.member.exception; -public class MemberException extends RuntimeException { - public MemberException(String message) { - super(message); +import com.example.umc10th.global.apiPayload.code.BaseErrorCode; +import com.example.umc10th.global.apiPayload.exception.BaseException; + +public class MemberException extends BaseException { + public MemberException(BaseErrorCode code) { + super(code); } } diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/exception/code/FoodErrorCode.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/exception/code/FoodErrorCode.java new file mode 100644 index 0000000..64123af --- /dev/null +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/exception/code/FoodErrorCode.java @@ -0,0 +1,18 @@ +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; + +@Getter +@RequiredArgsConstructor +public enum FoodErrorCode implements BaseErrorCode { + FOOD_TYPE_NOT_FOUND(HttpStatus.NOT_FOUND, + "FOOD404_1", + "해당 음식 타입이 존재하지 않습니다."); + + private final HttpStatus status; + private final String code; + private final String message; +} diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/exception/code/MemberErrorCode.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/exception/code/MemberErrorCode.java index c7684d2..17d5597 100644 --- a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/exception/code/MemberErrorCode.java +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/exception/code/MemberErrorCode.java @@ -1,4 +1,18 @@ package com.example.umc10th.domain.member.exception.code; -public enum MemberErrorCode { +import com.example.umc10th.global.apiPayload.code.BaseErrorCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@Getter +@RequiredArgsConstructor +public enum MemberErrorCode implements BaseErrorCode { + MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, + "MEMBER404_1", + "해당 아이디의 회원이 존재하지 않습니다."); + + private final HttpStatus status; + private final String code; + private final String message; } diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/exception/code/MemberSuccessCode.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/exception/code/MemberSuccessCode.java index 5b176bc..01974a5 100644 --- a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/exception/code/MemberSuccessCode.java +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/exception/code/MemberSuccessCode.java @@ -19,7 +19,11 @@ public enum MemberSuccessCode implements BaseSuccessCode { NICKNAME_PATCH_OK(HttpStatus.OK, "MEMBER200_2", - "닉네임이 수정되었습니다."); + "닉네임이 수정되었습니다."), + + PROFILE_GET_OK(HttpStatus.OK, + "MEMBER200_3", + "프로필 정보가 조회되었습니다."); private final HttpStatus status; private final String code; diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/repository/FoodRepository.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/repository/FoodRepository.java new file mode 100644 index 0000000..bfc40ee --- /dev/null +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/repository/FoodRepository.java @@ -0,0 +1,12 @@ +package com.example.umc10th.domain.member.repository; + +import com.example.umc10th.domain.member.entity.Food; +import com.example.umc10th.domain.member.enums.FoodType; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface FoodRepository extends JpaRepository { + + Optional findByName(FoodType foodType); +} diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/repository/MemberRepository.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/repository/MemberRepository.java index d37a7cb..7357c02 100644 --- a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/repository/MemberRepository.java +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/repository/MemberRepository.java @@ -1,4 +1,18 @@ 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; +import org.springframework.data.jpa.repository.Query; + +import java.util.Optional; + +public interface MemberRepository extends JpaRepository { + Optional findMemberById(Long id); + @Query(""" + select m from Member m + join fetch m.memberFoodList mf + join fetch mf.food + where m.id = :id + """) + Optional findMemberWithFoods(Long id); } diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/service/MemberService.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/service/MemberService.java index 60923ed..af33653 100644 --- a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/service/MemberService.java +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/member/service/MemberService.java @@ -1,4 +1,85 @@ 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.Food; +import com.example.umc10th.domain.member.entity.Member; +import com.example.umc10th.domain.member.enums.FoodType; +import com.example.umc10th.domain.member.exception.FoodException; +import com.example.umc10th.domain.member.exception.MemberException; +import com.example.umc10th.domain.member.exception.code.FoodErrorCode; +import com.example.umc10th.domain.member.exception.code.MemberErrorCode; +import com.example.umc10th.domain.member.repository.FoodRepository; +import com.example.umc10th.domain.member.repository.MemberRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; + +@Service +@RequiredArgsConstructor public class MemberService { + + private final MemberRepository memberRepository; + private final FoodRepository foodRepository; + + public MemberResDto.Profile getProfile(Long id) { + Member memberWithFood = memberRepository.findMemberWithFoods(id) + .orElseThrow(() -> new MemberException(MemberErrorCode.MEMBER_NOT_FOUND)); + return MemberConverter.toProfile(memberWithFood); + } + + @Transactional + public void updateProfile(MemberReqDto.Profile dto) { + Member member = getMemberById(dto.id()); + + if (dto.nickname() != null) { + member.updateNickname(dto.nickname()); + } + + if (dto.gender() != null) { + member.updateGender(dto.gender()); + } + + if (dto.birth() != null) { + member.updateBirth(dto.birth()); + } + + if (dto.address() != null) { + member.updateAddress(dto.address()); + } + + if (dto.fullAddress() != null) { + member.updateFullAddress(dto.fullAddress()); + } + + if (dto.food() != null) { + List list = new ArrayList<>(); + for (FoodType f : dto.food()) { + Food food = foodRepository.findByName(f) + .orElseThrow(() -> new FoodException(FoodErrorCode.FOOD_TYPE_NOT_FOUND)); + list.add(food); + } + + member.updateFoodList(list); + } + } + + @Transactional + public void updateNickname(MemberReqDto.Nickname dto) { + Member member = getMemberById(dto.id()); + + if (dto.nickname() != null) { + member.updateNickname(dto.nickname()); + } + } + + private Member getMemberById(Long id) { + Member member = memberRepository.findMemberById(id) + .orElseThrow(() -> new MemberException(MemberErrorCode.MEMBER_NOT_FOUND)); + return member; + } } diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/controller/MissionController.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/controller/MissionController.java index 6cc4678..4d5432e 100644 --- a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/controller/MissionController.java +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/controller/MissionController.java @@ -1,74 +1,79 @@ package com.example.umc10th.domain.mission.controller; -import com.example.umc10th.domain.mission.dto.MissionResDto; +import com.example.umc10th.domain.mission.dto.*; import com.example.umc10th.domain.mission.enums.Address; import com.example.umc10th.domain.mission.enums.Status; -import com.example.umc10th.domain.mission.enums.StoreType; import com.example.umc10th.domain.mission.exception.code.MissionSuccessCode; +import com.example.umc10th.domain.mission.service.MissionService; import com.example.umc10th.global.apiPayload.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.persistence.criteria.CriteriaBuilder; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Slice; import org.springframework.web.bind.annotation.*; -import java.time.LocalDate; -import java.util.List; - @RequiredArgsConstructor -@RestController("/api/vi") +@RestController +@RequestMapping("/api/v1") @Tag(name = "Mission", description = "미션 관련 API") public class MissionController implements MissionControllerDocs{ + private final MissionService missionService; + @GetMapping("/missions/achievement-rate") - public ApiResponse getMissionAchievement( - @RequestParam Address location + public ApiResponse getMissionAchievement( + @RequestParam Address location, + @RequestParam Long memberId ) { - // 서비스 로직 - MissionResDto.Achievement response = new MissionResDto.Achievement(10,7); + Achievement response = missionService.getMissionAchievement(location, memberId); return ApiResponse.onSuccess(MissionSuccessCode.ACHIEVEMENT_GET_OK, response); } @GetMapping("/missions") - public ApiResponse getMissionList( - @RequestParam Address location + public ApiResponse> getMissionList( + @RequestParam Address location, + @RequestParam Integer pageSize, + @RequestParam Integer pageNumber, + @RequestParam (required = false) String sort ) { - // 서비스 로직 - MissionResDto.StoreInfo store = new MissionResDto.StoreInfo("반이학생 마라탕", StoreType.CHINESE, LocalDate.now(), 10000, 500); - MissionResDto.StoreList response = new MissionResDto.StoreList(List.of(store)); + MissionResDto.Pagination response = missionService.getMissionList(location, pageSize, pageNumber, sort); return ApiResponse.onSuccess(MissionSuccessCode.MISSION_LIST_GET_OK, response); } @PostMapping("/members/me/missions/{missionId}") public ApiResponse saveMemberMission( - @PathVariable Long missionId + @PathVariable Long missionId, + @RequestParam Long memberId ) { - // 서비스 로직 + missionService.saveMemberMission(missionId,memberId); return ApiResponse.onSuccess(MissionSuccessCode.MEMBER_MISSION_POST_OK,null); } @GetMapping("/members/me/missions") - public ApiResponse getMyMissions( - @RequestParam Status status + public ApiResponse> getMyMissions( + @RequestParam Status status, + @RequestParam Long memberId, + @RequestParam Integer pageSize, + @RequestParam Integer pageNumber, + @RequestParam (required = false) String sort ) { - // 서비스 로직 - MissionResDto.StoreInfo store = new MissionResDto.StoreInfo("반이학생 마라탕", StoreType.CHINESE, LocalDate.now(), 10000, 500); - MissionResDto.StoreList response = new MissionResDto.StoreList(List.of(store)); + MissionResDto.Pagination response = missionService.getMyMissionList(status, memberId, pageSize, pageNumber, sort); return ApiResponse.onSuccess(MissionSuccessCode.MY_MISSION_LIST_GET_OK, response); } - @PostMapping("/missions/{missionId}/success") + @PostMapping("/missions/{memberMissionId}/success") public ApiResponse postSuccessRequest( - @PathVariable Long missionId + @PathVariable Long memberMissionId ) { - // 서비스 로직 + missionService.postSuccessRequest(memberMissionId); return ApiResponse.onSuccess(MissionSuccessCode.MISSION_SUCCESS_REQUEST_OK, null); } - @GetMapping("/missions/{missionId}/success") - public ApiResponse getOwnerNumber( - @PathVariable Long missionId + @GetMapping("/missions/{memberMissionId}/success") + public ApiResponse getOwnerNumber( + @PathVariable Long memberMissionId ) { - // 서비스 로직 - MissionResDto.OwnerNumber response = new MissionResDto.OwnerNumber(1234232); + OwnerNumber response = missionService.getOwnerNumber(memberMissionId); return ApiResponse.onSuccess(MissionSuccessCode.OWNER_NUMBER_GET_OK, response); } diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/controller/MissionControllerDocs.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/controller/MissionControllerDocs.java index 3e39416..0cfa2f5 100644 --- a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/controller/MissionControllerDocs.java +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/controller/MissionControllerDocs.java @@ -1,6 +1,6 @@ package com.example.umc10th.domain.mission.controller; -import com.example.umc10th.domain.mission.dto.MissionResDto; +import com.example.umc10th.domain.mission.dto.*; import com.example.umc10th.domain.mission.enums.Address; import com.example.umc10th.domain.mission.enums.Status; import com.example.umc10th.global.apiPayload.ApiResponse; @@ -14,16 +14,20 @@ public interface MissionControllerDocs { summary = "지역별 미션 달성률 조회", description = "해당 지역의 미션 달성률을 조회합니다." ) - public ApiResponse getMissionAchievement( - @RequestParam Address location + public ApiResponse getMissionAchievement( + @RequestParam Address location, + @RequestParam Long memberId ); @Operation( summary = "지역별 미션 조회", description = "해당 지역의 미션 목록을 조회합니다." ) - public ApiResponse getMissionList( - @RequestParam Address location + public ApiResponse> getMissionList( + @RequestParam Address location, + @RequestParam Integer pageSize, + @RequestParam Integer pageNumber, + @RequestParam (required = false) String sort ); @Operation( @@ -31,15 +35,20 @@ public ApiResponse getMissionList( description = "해당 미션 도전을 생성합니다." ) public ApiResponse saveMemberMission( - @PathVariable Long missionId + @PathVariable Long missionId, + @RequestParam Long memberId ); @Operation( summary = "진행중/진행완료 미션 조회", description = "내 미션 목록을 진행 상태별로 조회합니다." ) - public ApiResponse getMyMissions( - @RequestParam Status status + public ApiResponse> getMyMissions( + @RequestParam Status status, + @RequestParam Long memberId, + @RequestParam Integer pageSize, + @RequestParam Integer pageNumber, + @RequestParam (required = false) String sort ); @Operation( @@ -47,14 +56,14 @@ public ApiResponse getMyMissions( description = "미션 성공 요청을 합니다." ) public ApiResponse postSuccessRequest( - @PathVariable Long missionId + @PathVariable Long memberMissionId ); @Operation( summary = "사장님 구분번호 조회", description = "사장님 구분번호를 조회 합니다." ) - public ApiResponse getOwnerNumber( - @PathVariable Long missionId + public ApiResponse getOwnerNumber( + @PathVariable Long memberMissionId ); } diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/converter/MissionConverter.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/converter/MissionConverter.java index 4a187ff..df94b10 100644 --- a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/converter/MissionConverter.java +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/converter/MissionConverter.java @@ -1,4 +1,42 @@ package com.example.umc10th.domain.mission.converter; +import com.example.umc10th.domain.member.dto.MemberResDto; +import com.example.umc10th.domain.member.entity.Member; +import com.example.umc10th.domain.mission.dto.MissionResDto; +import com.example.umc10th.domain.mission.dto.OwnerNumber; +import com.example.umc10th.domain.mission.entity.Mission; +import com.example.umc10th.domain.mission.entity.mapping.MemberMission; + +import java.util.List; + public class MissionConverter { + public static MemberMission toMemberMission( + Member member, + Mission mission + ) { + return MemberMission.builder() + .member(member) + .mission(mission) + .build(); + } + + public static OwnerNumber toOwnerNumber ( + MemberMission mission + ) { + return OwnerNumber.builder() + .ownerNumber(mission.getSuccessNumber()) + .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/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/dto/Achievement.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/dto/Achievement.java new file mode 100644 index 0000000..ddde807 --- /dev/null +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/dto/Achievement.java @@ -0,0 +1,6 @@ +package com.example.umc10th.domain.mission.dto; + +public record Achievement( + Long totalCount, + Long successCount +) {} \ No newline at end of file diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/dto/MemberMissionInfo.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/dto/MemberMissionInfo.java new file mode 100644 index 0000000..114e9d1 --- /dev/null +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/dto/MemberMissionInfo.java @@ -0,0 +1,17 @@ +package com.example.umc10th.domain.mission.dto; + +import com.example.umc10th.domain.member.enums.FoodType; +import com.example.umc10th.domain.mission.enums.Status; + +import java.time.LocalDate; + +public record MemberMissionInfo( + Long missionId, + Long memberMissionId, + String storeName, + FoodType storeType, + LocalDate deadline, + Integer minCost, + Integer point, + Status status +) {} diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/dto/MissionInfo.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/dto/MissionInfo.java new file mode 100644 index 0000000..48644a8 --- /dev/null +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/dto/MissionInfo.java @@ -0,0 +1,14 @@ +package com.example.umc10th.domain.mission.dto; + +import com.example.umc10th.domain.member.enums.FoodType; + +import java.time.LocalDate; + +public record MissionInfo( + Long missionId, + String storeName, + FoodType storeType, + LocalDate deadline, + Integer minCost, + Integer point +) {} diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/dto/MissionReqDto.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/dto/MissionReqDto.java index a688215..280bf67 100644 --- a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/dto/MissionReqDto.java +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/dto/MissionReqDto.java @@ -1,4 +1,13 @@ package com.example.umc10th.domain.mission.dto; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; + public class MissionReqDto { + + @Schema(name = "MemberID", description = "회원 아이디를 입력합니다.") + public record MemberId( + @NotNull + Long id + ){} } diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/dto/MissionResDto.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/dto/MissionResDto.java index 379d6d7..1cd694a 100644 --- a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/dto/MissionResDto.java +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/dto/MissionResDto.java @@ -1,30 +1,15 @@ package com.example.umc10th.domain.mission.dto; -import com.example.umc10th.domain.mission.enums.StoreType; +import lombok.Builder; -import java.time.LocalDate; import java.util.List; public class MissionResDto { - public record Achievement( - Integer totalCount, - Integer successCount - ) {} - - public record StoreList( - List stores - ) {} - - public record StoreInfo( - String storeName, - StoreType storeType, - LocalDate deadline, - Integer minCost, - Integer point - ) {} - - public record OwnerNumber( - Integer ownerNumber - ) {} + @Builder + public record Pagination( + List data, + Integer pageNumber, + Integer pageSize + ){} } diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/dto/OwnerNumber.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/dto/OwnerNumber.java new file mode 100644 index 0000000..799e535 --- /dev/null +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/dto/OwnerNumber.java @@ -0,0 +1,8 @@ +package com.example.umc10th.domain.mission.dto; + +import lombok.Builder; + +@Builder +public record OwnerNumber( + String ownerNumber +) {} diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/entity/Location.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/entity/Location.java index 3fe23f3..00f3e1c 100644 --- a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/entity/Location.java +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/entity/Location.java @@ -1,4 +1,26 @@ package com.example.umc10th.domain.mission.entity; +import com.example.umc10th.domain.mission.enums.Address; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Table(name = "location") public class Location { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "name", nullable = false) + @Enumerated(EnumType.STRING) + private Address name; + } diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/entity/Mission.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/entity/Mission.java index f587dc0..f956542 100644 --- a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/entity/Mission.java +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/entity/Mission.java @@ -1,4 +1,44 @@ package com.example.umc10th.domain.mission.entity; +import com.example.umc10th.domain.mission.entity.mapping.MemberMission; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; + +@Entity +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Table(name = "mission") public class Mission { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "min_cost", nullable = false) + private Integer minCost; + + @Column(name = "point_ratio", nullable = false) + private Long pointRatio; + + @Column(name = "deadline", nullable = false) + private LocalDate deadline; + + @Column(name = "rewards_point", nullable = false) + private Integer rewardsPoint; + + @OneToMany(mappedBy = "mission", cascade = CascadeType.ALL, orphanRemoval = true) + private List memberMissionList = new ArrayList<>(); + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "store_id") + private Store store; } diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/entity/PointHistory.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/entity/PointHistory.java new file mode 100644 index 0000000..ea061cb --- /dev/null +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/entity/PointHistory.java @@ -0,0 +1,41 @@ +package com.example.umc10th.domain.mission.entity; + +import com.example.umc10th.domain.member.entity.Member; +import com.example.umc10th.domain.mission.entity.mapping.MemberMission; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDate; + +@Entity +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Table(name = "point_history") +public class PointHistory { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "rewards_point", nullable = false) + private Integer rewardsPoint; + + @Column(name = "accumulation_date", nullable = false) + private LocalDate accumulationDate; + + @Column(name = "total_point", nullable = false) + private Integer totalPoint; + + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_mission_id") + private MemberMission memberMission; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id") + private Member member; +} diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/entity/Store.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/entity/Store.java index d97a2bb..42b0d85 100644 --- a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/entity/Store.java +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/entity/Store.java @@ -1,4 +1,51 @@ package com.example.umc10th.domain.mission.entity; +import com.example.umc10th.domain.member.enums.FoodType; +import com.example.umc10th.domain.mission.enums.Address; +import com.example.umc10th.domain.review.entity.Review; +import com.example.umc10th.domain.review.entity.ReviewPhoto; +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 = "store") public class Store { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "name", nullable = false) + private String name; + + @Column(name = "store_type", nullable = false) + @Enumerated(EnumType.STRING) + private FoodType storeType; + + @Column(name = "address", nullable = false) + @Enumerated(EnumType.STRING) + private Address address; + + @Column(name = "full_address") + private String fullAddress; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "location_id") + private Location location; + + @OneToMany(mappedBy = "store", cascade = CascadeType.ALL, orphanRemoval = true) + private List reviewList = new ArrayList<>(); + + @OneToMany(mappedBy = "store", cascade = CascadeType.ALL, orphanRemoval = true) + private List reviewPhotoList = new ArrayList<>(); } diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/entity/mapping/MemberMission.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/entity/mapping/MemberMission.java index f68da40..1197f29 100644 --- a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/entity/mapping/MemberMission.java +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/entity/mapping/MemberMission.java @@ -1,4 +1,59 @@ package com.example.umc10th.domain.mission.entity.mapping; -public class MemberMission { +import com.example.umc10th.domain.member.entity.Member; +import com.example.umc10th.domain.mission.entity.Mission; +import com.example.umc10th.domain.mission.enums.Status; +import com.example.umc10th.global.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDate; +import java.util.UUID; + +@Entity +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Table(name = "member_mission") +public class MemberMission extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Builder.Default + @Column(name = "status", nullable = false) + @Enumerated(EnumType.STRING) + private Status status = Status.STARTED; + + @Column(name = "success_number") + private String successNumber; + + @Column(name = "success_date") + private LocalDate successDate; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id") + private Member member; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "mission_id") + private Mission mission; + + public void updateStatus(Status status) { + this.status = status; + } + + public void updateSuccessDate(LocalDate successDate) { + this.successDate = successDate; + } + + public void updateSuccessNumber (Member member) { + this.successNumber = UUID.randomUUID() + member.getSocialId(); + } + } diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/enums/Status.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/enums/Status.java index ad0df22..06d32ba 100644 --- a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/enums/Status.java +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/enums/Status.java @@ -1,5 +1,5 @@ package com.example.umc10th.domain.mission.enums; public enum Status { - STARTED, FINISHED, NOT_STARTED + STARTED, SUCCESS, FAIL } diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/enums/StoreType.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/enums/StoreType.java deleted file mode 100644 index c98dd92..0000000 --- a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/enums/StoreType.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.example.umc10th.domain.mission.enums; - -public enum StoreType { - CHINESE, KOREAN, ITALIAN, JAPANESE -} diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/exception/MissionException.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/exception/MissionException.java index 71391da..902c2a6 100644 --- a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/exception/MissionException.java +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/exception/MissionException.java @@ -1,7 +1,10 @@ package com.example.umc10th.domain.mission.exception; -public class MissionException extends RuntimeException { - public MissionException(String message) { - super(message); +import com.example.umc10th.global.apiPayload.code.BaseErrorCode; +import com.example.umc10th.global.apiPayload.exception.BaseException; + +public class MissionException extends BaseException { + public MissionException(BaseErrorCode code) { + super(code); } } diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/exception/StoreException.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/exception/StoreException.java new file mode 100644 index 0000000..252d2cd --- /dev/null +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/exception/StoreException.java @@ -0,0 +1,10 @@ +package com.example.umc10th.domain.mission.exception; + +import com.example.umc10th.global.apiPayload.code.BaseErrorCode; +import com.example.umc10th.global.apiPayload.exception.BaseException; + +public class StoreException extends BaseException { + public StoreException(BaseErrorCode code) { + super(code); + } +} diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/exception/code/MissionErrorCode.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/exception/code/MissionErrorCode.java index 93ae26b..db12069 100644 --- a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/exception/code/MissionErrorCode.java +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/exception/code/MissionErrorCode.java @@ -1,4 +1,22 @@ package com.example.umc10th.domain.mission.exception.code; -public enum MissionErrorCode { +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 { + MISSION_NOT_FOUND(HttpStatus.NOT_FOUND, + "MISSION404_1", + "해당 아이디의 미션이 존재하지 않습니다."), + + MEMBER_MISSION_NOT_FOUND(HttpStatus.NOT_FOUND, + "MISSION404_1", + "해당 아이디의 유저미션이 존재하지 않습니다."); + + private final HttpStatus status; + private final String code; + private final String message; } diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/exception/code/StoreErrorCode.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/exception/code/StoreErrorCode.java new file mode 100644 index 0000000..bcb232e --- /dev/null +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/exception/code/StoreErrorCode.java @@ -0,0 +1,18 @@ +package com.example.umc10th.domain.mission.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 StoreErrorCode implements BaseErrorCode { + STORE_NOT_FOUND(HttpStatus.NOT_FOUND, + "STORE404_1", + "해당 아이디의 가게가 존재하지 않습니다."); + + private final HttpStatus status; + private final String code; + private final String message; +} diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/repository/MemberMissionRepository.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/repository/MemberMissionRepository.java new file mode 100644 index 0000000..613ae48 --- /dev/null +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/repository/MemberMissionRepository.java @@ -0,0 +1,45 @@ +package com.example.umc10th.domain.mission.repository; + +import com.example.umc10th.domain.member.entity.Member; +import com.example.umc10th.domain.mission.dto.Achievement; +import com.example.umc10th.domain.mission.dto.MemberMissionInfo; +import com.example.umc10th.domain.mission.dto.MissionInfo; +import com.example.umc10th.domain.mission.entity.mapping.MemberMission; +import com.example.umc10th.domain.mission.enums.Address; +import com.example.umc10th.domain.mission.enums.Status; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +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 java.util.Optional; + +public interface MemberMissionRepository extends JpaRepository { + + Optional findMemberMissionById(Long id); + + @Query(""" + SELECT new com.example.umc10th.domain.mission.dto.Achievement( + COUNT(mm), + SUM(CASE WHEN mm.status = 'SUCCESS' THEN 1 ELSE 0 END) + ) + FROM MemberMission mm + WHERE mm.member = :member + AND mm.mission.store.location.name = :address + """) + Achievement getTotalCountAndSuccessCount(Address address, Member member); + + @Query(""" + SELECT new com.example.umc10th.domain.mission.dto.MemberMissionInfo( + m.id, mm.id, s.name, s.storeType, m.deadline, m.minCost, m.rewardsPoint, mm.status + ) + FROM MemberMission mm + JOIN mm.mission m + JOIN m.store s + WHERE mm.status = :status + AND mm.member.id = :memberId + """) + Page getMissionByStatus(Status status, Long memberId, PageRequest pageRequest); +} diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/repository/MissionRepository.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/repository/MissionRepository.java index c31476b..6c5bd08 100644 --- a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/repository/MissionRepository.java +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/repository/MissionRepository.java @@ -1,4 +1,26 @@ package com.example.umc10th.domain.mission.repository; -public interface MissionRepository { +import com.example.umc10th.domain.mission.dto.MissionInfo; +import com.example.umc10th.domain.mission.entity.Mission; +import com.example.umc10th.domain.mission.enums.Address; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +import java.util.Optional; + +public interface MissionRepository extends JpaRepository { + + Optional findMissionById(Long id); + + @Query(""" + SELECT new com.example.umc10th.domain.mission.dto.MissionInfo( + m.id, s.name, s.storeType, m.deadline, m.minCost, m.rewardsPoint + ) + FROM Mission m + JOIN m.store s + WHERE s.address = :address + """) + Page getMissionByAddress(Address address, PageRequest pageRequest); } diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/repository/StoreRepository.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/repository/StoreRepository.java new file mode 100644 index 0000000..f208723 --- /dev/null +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/repository/StoreRepository.java @@ -0,0 +1,10 @@ +package com.example.umc10th.domain.mission.repository; + +import com.example.umc10th.domain.mission.entity.Store; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface StoreRepository extends JpaRepository { + Optional findStoreById(Long id); +} diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/service/MissionService.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/service/MissionService.java index 218d438..626fbbe 100644 --- a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/service/MissionService.java +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/mission/service/MissionService.java @@ -1,4 +1,114 @@ package com.example.umc10th.domain.mission.service; +import com.example.umc10th.domain.member.entity.Member; +import com.example.umc10th.domain.member.exception.MemberException; +import com.example.umc10th.domain.member.exception.code.MemberErrorCode; +import com.example.umc10th.domain.member.repository.MemberRepository; +import com.example.umc10th.domain.mission.converter.MissionConverter; +import com.example.umc10th.domain.mission.dto.*; +import com.example.umc10th.domain.mission.entity.Mission; +import com.example.umc10th.domain.mission.entity.mapping.MemberMission; +import com.example.umc10th.domain.mission.enums.Address; +import com.example.umc10th.domain.mission.enums.Status; +import com.example.umc10th.domain.mission.exception.MissionException; +import com.example.umc10th.domain.mission.exception.code.MissionErrorCode; +import com.example.umc10th.domain.mission.repository.MemberMissionRepository; +import com.example.umc10th.domain.mission.repository.MissionRepository; +import com.example.umc10th.global.apiPayload.ApiResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.*; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDate; + +@Service +@RequiredArgsConstructor public class MissionService { + + private final MemberRepository memberRepository; + private final MemberMissionRepository memberMissionRepository; + private final MissionRepository missionRepository; + + public Achievement getMissionAchievement(Address address, Long memberId) { + Member member = getMemberById(memberId); + + return memberMissionRepository.getTotalCountAndSuccessCount(address, member); + } + + private Member getMemberById(Long memberId) { + Member member = memberRepository.findMemberById(memberId) + .orElseThrow(() -> new MemberException(MemberErrorCode.MEMBER_NOT_FOUND)); + return member; + } + + public MissionResDto.Pagination getMissionList( + Address address, + Integer pageSize, + Integer pageNumber, + String sort + ) { + Sort sortInfo; + if (sort != null){ + sortInfo = Sort.by(sort); + } else { + sortInfo = Sort.by("id").descending(); + } + + PageRequest pageRequest = PageRequest.of(pageNumber, pageSize, sortInfo); + Page missionList = missionRepository.getMissionByAddress(address, pageRequest); + return MissionConverter.toPagination( + missionList.stream().toList(), + missionList.getNumber(), + missionList.getSize() + ); + } + + @Transactional + public void saveMemberMission(Long missionId, Long memberId) { + Member member = getMemberById(memberId); + Mission mission = missionRepository.findMissionById(missionId) + .orElseThrow(() -> new MissionException(MissionErrorCode.MISSION_NOT_FOUND)); + MemberMission memberMission = MissionConverter.toMemberMission(member, mission); + memberMissionRepository.save(memberMission); + } + + public MissionResDto.Pagination getMyMissionList( + Status status, + 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.of(pageNumber, pageSize, sortInfo); + Page missionList = memberMissionRepository.getMissionByStatus(status, memberId, pageRequest); + return MissionConverter.toPagination( + missionList.stream().toList(), + missionList.getNumber(), + missionList.getSize() + ); + } + + @Transactional + public void postSuccessRequest(Long memberMissionId) { + MemberMission memberMission = memberMissionRepository.findMemberMissionById(memberMissionId) + .orElseThrow(() -> new MissionException(MissionErrorCode.MEMBER_MISSION_NOT_FOUND)); + // 성공 확인 로직 + memberMission.updateStatus(Status.SUCCESS); + memberMission.updateSuccessDate(LocalDate.now()); + memberMission.updateSuccessNumber(memberMission.getMember()); + } + + public OwnerNumber getOwnerNumber(Long memberMissionId) { + MemberMission memberMission = memberMissionRepository.findMemberMissionById(memberMissionId) + .orElseThrow(() -> new MissionException(MissionErrorCode.MEMBER_MISSION_NOT_FOUND)); + return MissionConverter.toOwnerNumber(memberMission); + } } diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/controller/ReviewController.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/controller/ReviewController.java index 7161a97..b11203f 100644 --- a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/controller/ReviewController.java +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/controller/ReviewController.java @@ -1,46 +1,56 @@ package com.example.umc10th.domain.review.controller; +import com.example.umc10th.domain.review.dto.ReviewInfo; +import com.example.umc10th.domain.review.dto.ReviewPhotoUrl; 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 io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; -import java.time.LocalDate; import java.util.List; -@RestController("/api/v1") +@RestController +@RequestMapping("/api/v1") @RequiredArgsConstructor @Tag(name = "Review", description = "리뷰 관련 API") public class ReviewController implements ReviewControllerDocs{ + private final ReviewService reviewService; + @PostMapping("/stores/{storeId}/reviews") public ApiResponse saveReview( @PathVariable Long storeId, - @RequestBody ReviewReqDto.Review dto + @RequestBody @Valid ReviewReqDto.Review dto, + @RequestParam Long memberId ) { - // 서비스 로직 + reviewService.saveReview(storeId, memberId, dto); return ApiResponse.onSuccess(ReviewSuccessCode.REVIEW_POST_OK, null); } @GetMapping("/stores/{storeId}/reviews") - public ApiResponse> getReviewList( - @PathVariable Long storeId + public ApiResponse> getReviewList( + @PathVariable Long storeId, + @RequestParam Integer pageSize, + @RequestParam (required = false) String cursor, + @RequestParam String query ) { - // 서비스 로직 - List photos = List.of(new ReviewReqDto.ReviewPhoto("sss")); - List response = List.of(new ReviewResDto.Review((long) 4.5, "맛있어요", photos, "이서", LocalDate.now())); + ReviewResDto.Pagination response = reviewService.getReviewList(storeId, pageSize, cursor, query); return ApiResponse.onSuccess(ReviewSuccessCode.REVIEW_LIST_GET_OK, response); } @GetMapping("/stores/{storeId}/reviews/photos") - public ApiResponse> getReviewPhotoList( - @PathVariable Long storeId + public ApiResponse> getReviewPhotoList( + @PathVariable @Valid Long storeId, + @RequestParam Integer pageSize, + @RequestParam (required = false) String cursor, + @RequestParam String query ) { - // 서비스 로직 - List response = List.of(new ReviewReqDto.ReviewPhoto("sss")); + ReviewResDto.Pagination response = reviewService.getReviewPhotoList(storeId, pageSize, cursor, query); return ApiResponse.onSuccess(ReviewSuccessCode.REVIEW_PHOTO_LIST_GET_OK, response); } diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/controller/ReviewControllerDocs.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/controller/ReviewControllerDocs.java index 51854e3..3d72714 100644 --- a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/controller/ReviewControllerDocs.java +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/controller/ReviewControllerDocs.java @@ -1,11 +1,14 @@ package com.example.umc10th.domain.review.controller; +import com.example.umc10th.domain.review.dto.ReviewInfo; +import com.example.umc10th.domain.review.dto.ReviewPhotoUrl; import com.example.umc10th.domain.review.dto.ReviewReqDto; import com.example.umc10th.domain.review.dto.ReviewResDto; import com.example.umc10th.global.apiPayload.ApiResponse; import io.swagger.v3.oas.annotations.Operation; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; import java.util.List; @@ -17,22 +20,29 @@ public interface ReviewControllerDocs { ) public ApiResponse saveReview( @PathVariable Long storeId, - @RequestBody ReviewReqDto.Review dto + @RequestBody ReviewReqDto.Review dto, + @RequestParam Long memberId ); @Operation( summary = "리뷰 목록 조회", description = "리뷰 목록을 조회합니다." ) - public ApiResponse> getReviewList( - @PathVariable Long storeId - ); + public ApiResponse> getReviewList( + @PathVariable Long storeId, + @RequestParam Integer pageSize, + @RequestParam String cursor, + @RequestParam String query + ) ; @Operation( summary = "리뷰 사진 목록 조회", description = "리뷰 사진 목록을 조회합니다." ) - public ApiResponse> getReviewPhotoList( - @PathVariable Long storeId + public ApiResponse> getReviewPhotoList( + @PathVariable Long storeId, + @RequestParam Integer pageSize, + @RequestParam String cursor, + @RequestParam String query ); } diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/converter/ReviewConverter.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/converter/ReviewConverter.java index 83f9b66..359bb32 100644 --- a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/converter/ReviewConverter.java +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/converter/ReviewConverter.java @@ -1,4 +1,65 @@ package com.example.umc10th.domain.review.converter; +import com.example.umc10th.domain.member.entity.Member; +import com.example.umc10th.domain.mission.entity.Store; +import com.example.umc10th.domain.review.dto.ReviewInfo; +import com.example.umc10th.domain.review.dto.ReviewPhotoUrl; +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.entity.ReviewPhoto; + +import java.util.List; + public class ReviewConverter { + + public static Review toReview( + Member member, + Store store, + ReviewReqDto.Review dto + ) { + return Review.builder() + .member(member) + .store(store) + .content(dto.content()) + .rate(dto.rate()) + .build(); + } + + public static ReviewInfo toReviewInfo( + Review review + ) { + return ReviewInfo.builder() + .reviewId(review.getId()) + .rate(review.getRate()) + .content(review.getContent()) + .reviewerNickname(review.getMember().getNickname()) + .reviewPhotoList(review.getReviewPhotoList().stream().map(ReviewConverter::toReviewPhotoUrl).toList()) + .createdAt(review.getCreatedAt()) + .build(); + } + + public static ReviewPhotoUrl toReviewPhotoUrl( + ReviewPhoto reviewPhoto + ) { + return ReviewPhotoUrl.builder() + .reviewPhotoId(reviewPhoto.getId()) + .reviewRate(reviewPhoto.getReview().getRate()) + .reviewPhoto(reviewPhoto.getImageUrl()) + .build(); + } + + public static ReviewResDto.Pagination toPagination( + List data, + boolean hasNext, + String nextCursor, + Integer pageSize + ) { + return ReviewResDto.Pagination.builder() + .data(data) + .hasNext(hasNext) + .pageSize(pageSize) + .nextCursor(nextCursor) + .build(); + } } diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/dto/ReviewInfo.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/dto/ReviewInfo.java new file mode 100644 index 0000000..9275035 --- /dev/null +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/dto/ReviewInfo.java @@ -0,0 +1,16 @@ +package com.example.umc10th.domain.review.dto; + +import lombok.Builder; + +import java.time.LocalDateTime; +import java.util.List; + +@Builder +public record ReviewInfo( + Long reviewId, + double rate, + String content, + List reviewPhotoList, + String reviewerNickname, + LocalDateTime createdAt +) {} \ No newline at end of file diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/dto/ReviewPhotoUrl.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/dto/ReviewPhotoUrl.java new file mode 100644 index 0000000..169d88c --- /dev/null +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/dto/ReviewPhotoUrl.java @@ -0,0 +1,10 @@ +package com.example.umc10th.domain.review.dto; + +import lombok.Builder; + +@Builder +public record ReviewPhotoUrl( + Long reviewPhotoId, + Double reviewRate, + String reviewPhoto +) {} diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/dto/ReviewReqDto.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/dto/ReviewReqDto.java index 0c1edd1..a8afe30 100644 --- a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/dto/ReviewReqDto.java +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/dto/ReviewReqDto.java @@ -1,16 +1,26 @@ package com.example.umc10th.domain.review.dto; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + import java.util.List; public class ReviewReqDto { + @Schema(name = "Review", description = "등록할 리뷰 정보를 저장합합니다.") public record Review( - Long rate, - String content, - List reviewPhotoList + @NotNull + Long rate, + @NotBlank + String content, + @NotNull + List reviewPhotoList ) {} + @Schema(name = "ReviewPhoto", description = "등록할 리뷰 사진들을 저장합합니다.") public record ReviewPhoto( - String reviewPhoto + @NotBlank + String reviewPhoto ) {} } diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/dto/ReviewResDto.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/dto/ReviewResDto.java index 77bd9a8..fd77259 100644 --- a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/dto/ReviewResDto.java +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/dto/ReviewResDto.java @@ -1,20 +1,16 @@ package com.example.umc10th.domain.review.dto; -import java.time.LocalDate; +import lombok.Builder; + import java.util.List; public class ReviewResDto { - public record Review( - Long rate, - String content, - List reviewPhotoList, - String reviewerNickname, - LocalDate createdAt - ) {} - - public record ReviewPhoto( - String reviewPhoto - ) {} - + @Builder + public record Pagination( + List data, + Boolean hasNext, + String nextCursor, + Integer pageSize + ){} } diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/entity/Reply.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/entity/Reply.java index 8f7a2c4..e8b108d 100644 --- a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/entity/Reply.java +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/entity/Reply.java @@ -1,4 +1,26 @@ package com.example.umc10th.domain.review.entity; -public class Reply { -} +import com.example.umc10th.domain.mission.enums.Address; +import com.example.umc10th.global.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Table(name = "reply") +public class Reply extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "content", nullable = false) + private String content; + +} \ No newline at end of file diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/entity/Review.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/entity/Review.java index ac958a3..4839aad 100644 --- a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/entity/Review.java +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/entity/Review.java @@ -1,4 +1,48 @@ package com.example.umc10th.domain.review.entity; -public class Review { +import com.example.umc10th.domain.member.entity.Member; +import com.example.umc10th.domain.mission.entity.Store; +import com.example.umc10th.domain.mission.enums.Address; +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; + + @Column(name = "rate", nullable = false) + private double rate; + + @Column(name = "content", nullable = false) + private String content; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id") + private Member member; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "store_id") + private Store store; + + @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true) + @JoinColumn(name = "reply_id") + private Reply reply; + + @OneToMany(mappedBy = "review", cascade = CascadeType.ALL, orphanRemoval = true) + List reviewPhotoList = new ArrayList<>(); } diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/entity/ReviewPhoto.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/entity/ReviewPhoto.java index b644a3a..72b64e7 100644 --- a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/entity/ReviewPhoto.java +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/entity/ReviewPhoto.java @@ -1,4 +1,33 @@ package com.example.umc10th.domain.review.entity; +import com.example.umc10th.domain.mission.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 +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Table(name = "review_photo") public class ReviewPhoto { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "image_url", nullable = false) + private String imageUrl; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "review_id") + private Review review; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "store_id") + private Store store; } diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/exception/ReviewException.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/exception/ReviewException.java index f55abb1..ca5c935 100644 --- a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/exception/ReviewException.java +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/exception/ReviewException.java @@ -1,7 +1,11 @@ package com.example.umc10th.domain.review.exception; -public class ReviewException extends RuntimeException { - public ReviewException(String message) { - super(message); +import com.example.umc10th.global.apiPayload.code.BaseErrorCode; +import com.example.umc10th.global.apiPayload.exception.BaseException; + +public class ReviewException extends BaseException { + + public ReviewException(BaseErrorCode errorCode) { + super(errorCode); } } diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/exception/code/ReviewErrorCode.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/exception/code/ReviewErrorCode.java index c3ff795..6053ac8 100644 --- a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/exception/code/ReviewErrorCode.java +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/exception/code/ReviewErrorCode.java @@ -1,4 +1,18 @@ 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 { + INVALID_QUERY(HttpStatus.BAD_REQUEST, + "REVIEW400_1", + "잘못된 쿼리 입니다."); + + private final HttpStatus status; + private final String code; + private final String message; } diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/exception/code/ReviewSuccessCode.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/exception/code/ReviewSuccessCode.java index 387dc3e..75cb716 100644 --- a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/exception/code/ReviewSuccessCode.java +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/exception/code/ReviewSuccessCode.java @@ -9,15 +9,15 @@ @RequiredArgsConstructor public enum ReviewSuccessCode implements BaseSuccessCode { REVIEW_POST_OK(HttpStatus.OK, - "REVIEW200_1", + "REVIEW201_1", "리뷰가 작성되었습니다."), REVIEW_LIST_GET_OK(HttpStatus.OK, - "REVIEW200_2", + "REVIEW200_1", "리뷰 목록이 조회되었습니다."), REVIEW_PHOTO_LIST_GET_OK(HttpStatus.OK, - "REVIEW200_3", + "REVIEW200_2", "리뷰 사진 목록이 조회되었습니다."); private final HttpStatus status; diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/repository/ReviewPhotoRepository.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/repository/ReviewPhotoRepository.java new file mode 100644 index 0000000..b46997d --- /dev/null +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/repository/ReviewPhotoRepository.java @@ -0,0 +1,47 @@ +package com.example.umc10th.domain.review.repository; + +import com.example.umc10th.domain.review.entity.Review; +import com.example.umc10th.domain.review.entity.ReviewPhoto; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Slice; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +import java.util.List; + +public interface ReviewPhotoRepository extends JpaRepository { + + @Query(""" + SELECT rp + FROM ReviewPhoto rp + JOIN rp.store s + WHERE s.id = :storeId + """) + List getReviewPhotoByStore(Long storeId); + + Slice findReviewPhotosByStore_IdAndIdLessThanOrderByIdDesc(Long storeId, Long idCursor, PageRequest pageRequest); + Slice findReviewPhotosByStore_IdOrderByIdDesc(Long storeId, PageRequest pageRequest); + + @Query(""" + SELECT rp + FROM ReviewPhoto rp + JOIN rp.store s + JOIN rp.review r + WHERE s.id = :storeId + AND ( + r.rate < :rateCursor + OR (r.rate = :rateCursor AND rp.id < :idCursor) + ) + ORDER BY r.rate DESC, rp.id DESC + """) + Slice findReviewPhotosByRateCursor(Long storeId, Double rateCursor, Long idCursor, PageRequest pageRequest); + @Query(""" + SELECT rp + FROM ReviewPhoto rp + JOIN rp.store s + JOIN rp.review r + WHERE s.id = :storeId + ORDER BY r.rate DESC, rp.id DESC + """) + Slice findReviewPhotosOrderByRate(Long storeId, PageRequest pageRequest); +} diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/repository/ReviewRepository.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/repository/ReviewRepository.java index dfc708e..2c8486f 100644 --- a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/repository/ReviewRepository.java +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/repository/ReviewRepository.java @@ -1,4 +1,44 @@ package com.example.umc10th.domain.review.repository; -public interface ReviewRepository { +import com.example.umc10th.domain.mission.entity.Store; +import com.example.umc10th.domain.review.entity.Review; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Slice; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +import java.util.List; + +public interface ReviewRepository extends JpaRepository { + @Query(""" + select distinct r from Review r + left join fetch r.reviewPhotoList rp + where r.store = :store + """) + List getReviewsByStore(Store store); + + Store store(Store store); + + Slice findReviewsByStore_IdAndIdLessThanOrderByIdDesc(Long storeId, Long idCursor, PageRequest pageRequest); + Slice findReviewsByStore_IdOrderByIdDesc(Long storeId, PageRequest pageRequest); + + @Query(""" + SELECT r + FROM Review r + WHERE r.store.id = :storeId + AND ( + r.rate < :rateCursor + OR (r.rate = :rateCursor AND r.id < :idCursor) + ) + ORDER BY r.rate DESC, r.id DESC + """) + Slice findReviewsByRateCursor(Long storeId, Double rateCursor, Long idCursor, PageRequest pageRequest); + + @Query(""" + SELECT r + FROM Review r + WHERE r.store.id = :storeId + ORDER BY r.rate DESC, r.id DESC + """) + Slice findReviewsOrderByRate(Long storeId, PageRequest pageRequest); } diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/service/ReviewService.java b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/service/ReviewService.java index 36fed64..dd849dd 100644 --- a/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/service/ReviewService.java +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/domain/review/service/ReviewService.java @@ -1,4 +1,170 @@ package com.example.umc10th.domain.review.service; +import com.example.umc10th.domain.member.entity.Member; +import com.example.umc10th.domain.member.exception.MemberException; +import com.example.umc10th.domain.member.exception.code.MemberErrorCode; +import com.example.umc10th.domain.member.repository.MemberRepository; +import com.example.umc10th.domain.mission.entity.Store; +import com.example.umc10th.domain.mission.exception.StoreException; +import com.example.umc10th.domain.mission.exception.code.StoreErrorCode; +import com.example.umc10th.domain.mission.repository.StoreRepository; +import com.example.umc10th.domain.review.converter.ReviewConverter; +import com.example.umc10th.domain.review.dto.ReviewInfo; +import com.example.umc10th.domain.review.dto.ReviewPhotoUrl; +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.entity.ReviewPhoto; +import com.example.umc10th.domain.review.exception.ReviewException; +import com.example.umc10th.domain.review.exception.code.ReviewErrorCode; +import com.example.umc10th.domain.review.repository.ReviewPhotoRepository; +import com.example.umc10th.domain.review.repository.ReviewRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Slice; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@RequiredArgsConstructor public class ReviewService { + + private final MemberRepository memberRepository; + private final StoreRepository storeRepository; + private final ReviewRepository reviewRepository; + private final ReviewPhotoRepository reviewPhotoRepository; + + @Transactional + public void saveReview(Long storeId, Long memberId, ReviewReqDto.Review dto) { + Member member = memberRepository.findMemberById(memberId) + .orElseThrow(() -> new MemberException(MemberErrorCode.MEMBER_NOT_FOUND)); + Store store = storeRepository.findStoreById(storeId).orElseThrow(() -> new StoreException(StoreErrorCode.STORE_NOT_FOUND)); + Review review = ReviewConverter.toReview(member, store, dto); + reviewRepository.save(review); + } + + public ReviewResDto.Pagination getReviewList(Long storeId, Integer pageSize, String cursor, String query) { + if (cursor == null) cursor = "-1"; + PageRequest pageRequest = PageRequest.of(0, pageSize); + long idCursor; + double rateCursor; + Slice reviewList; + String nextCursor; + + if (!cursor.equals("-1")){ + String[] cursorSplit = cursor.split(":"); + switch (query.toLowerCase()){ + case "id": + idCursor = Long.parseLong(cursorSplit[1]); + reviewList = reviewRepository.findReviewsByStore_IdAndIdLessThanOrderByIdDesc(storeId, idCursor, pageRequest); + break; + case "rate": + rateCursor = Double.parseDouble(cursorSplit[0]); + idCursor = Long.parseLong(cursorSplit[1]); + reviewList = reviewRepository.findReviewsByRateCursor(storeId, rateCursor, idCursor, pageRequest); + break; + default: + throw new ReviewException(ReviewErrorCode.INVALID_QUERY); + } + } else { + switch (query.toLowerCase()){ + case "id": + reviewList = reviewRepository.findReviewsByStore_IdOrderByIdDesc(storeId, pageRequest); + break; + case "rate": + reviewList = reviewRepository.findReviewsOrderByRate(storeId, pageRequest); + break; + default: + throw new ReviewException(ReviewErrorCode.INVALID_QUERY); + } + } + nextCursor = "-1"; + + if (!reviewList.isEmpty()) { + + Review lastReview = reviewList.getContent() + .get(reviewList.getNumberOfElements() - 1); + + switch (query.toLowerCase()) { + + case "id": + nextCursor = lastReview.getId() + ":" + lastReview.getId(); + break; + + case "rate": + nextCursor = lastReview.getRate() + ":" + lastReview.getId(); + break; + } + } + + return ReviewConverter.toPagination( + reviewList.map(ReviewConverter::toReviewInfo).toList(), + reviewList.hasNext(), + nextCursor, + reviewList.getSize()); + } + + public ReviewResDto.Pagination getReviewPhotoList(Long storeId, Integer pageSize, String cursor, String query) { + if (cursor == null) cursor = "-1"; + PageRequest pageRequest = PageRequest.of(0, pageSize); + + long idCursor; + double rateCursor; + Slice reviewPhotoList; + String nextCursor; + + if (!cursor.equals("-1")){ + String[] cursorSplit = cursor.split(":"); + switch (query.toLowerCase()){ + case "id": + idCursor = Long.parseLong(cursorSplit[1]); + reviewPhotoList = reviewPhotoRepository.findReviewPhotosByStore_IdAndIdLessThanOrderByIdDesc(storeId, idCursor, pageRequest); + break; + case "rate": + rateCursor = Double.parseDouble(cursorSplit[0]); + idCursor = Long.parseLong(cursorSplit[1]); + reviewPhotoList = reviewPhotoRepository.findReviewPhotosByRateCursor(storeId, rateCursor, idCursor, pageRequest); + break; + default: + throw new ReviewException(ReviewErrorCode.INVALID_QUERY); + } + } else { + switch (query.toLowerCase()){ + case "id": + reviewPhotoList = reviewPhotoRepository.findReviewPhotosByStore_IdOrderByIdDesc(storeId, pageRequest); + break; + case "rate": + reviewPhotoList = reviewPhotoRepository.findReviewPhotosOrderByRate(storeId, pageRequest); + break; + default: + throw new ReviewException(ReviewErrorCode.INVALID_QUERY); + } + } + nextCursor = "-1"; + + if (!reviewPhotoList.isEmpty()) { + + ReviewPhoto lastReviewPhoto = reviewPhotoList.getContent() + .get(reviewPhotoList.getNumberOfElements() - 1); + + switch (query.toLowerCase()) { + + case "id": + nextCursor = lastReviewPhoto.getId() + ":" + lastReviewPhoto.getId(); + break; + + case "rate": + nextCursor = lastReviewPhoto.getReview().getRate() + ":" + lastReviewPhoto.getId(); + break; + } + } + + return ReviewConverter.toPagination( + reviewPhotoList.map(ReviewConverter::toReviewPhotoUrl).toList(), + reviewPhotoList.hasNext(), + nextCursor, + reviewPhotoList.getSize()); + } } diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/global/apiPayload/code/GeneralErrorCode.java b/leeseo/umc10th/src/main/java/com/example/umc10th/global/apiPayload/code/GeneralErrorCode.java index 6e030e6..411526c 100644 --- a/leeseo/umc10th/src/main/java/com/example/umc10th/global/apiPayload/code/GeneralErrorCode.java +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/global/apiPayload/code/GeneralErrorCode.java @@ -22,7 +22,11 @@ public enum GeneralErrorCode implements BaseErrorCode{ NOT_FOUND(HttpStatus.NOT_FOUND, "COMMON404_1", - "해당 리소스를 찾을 수 없습니다."); + "해당 리소스를 찾을 수 없습니다."), + + INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, + "COMMON500_1", + "서버가 응답하지 않습니다."); private final HttpStatus status; private final String code; diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/global/apiPayload/handler/GeneralExceptionAdvice.java b/leeseo/umc10th/src/main/java/com/example/umc10th/global/apiPayload/handler/GeneralExceptionAdvice.java new file mode 100644 index 0000000..4ac3ada --- /dev/null +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/global/apiPayload/handler/GeneralExceptionAdvice.java @@ -0,0 +1,66 @@ +package com.example.umc10th.global.apiPayload.handler; + +import com.example.umc10th.global.apiPayload.ApiResponse; +import com.example.umc10th.global.apiPayload.code.BaseErrorCode; +import com.example.umc10th.global.apiPayload.code.GeneralErrorCode; +import com.example.umc10th.global.apiPayload.exception.BaseException; +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; + +@RestControllerAdvice +public class GeneralExceptionAdvice { + + // 애플리케이션에서 발생하는 커스텀 예외를 처리 + @ExceptionHandler(BaseException.class) + public ResponseEntity> handleException( + BaseException ex + ) { + + return ResponseEntity.status(ex.getErrorCode().getStatus()) + .body(ApiResponse.onFailure( + ex.getErrorCode(), + null + ) + ); + } + + // Valid 오류 + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity>> handleValidationException( + MethodArgumentNotValidException e + ) { + + Map errors = new HashMap<>(); + + e.getBindingResult() + .getFieldErrors() + .forEach(error -> + errors.put( + error.getField(), + error.getDefaultMessage() + ) + ); + BaseErrorCode code = GeneralErrorCode.BAD_REQUEST; + return ResponseEntity.badRequest().body(ApiResponse.onFailure(code, errors)); + } + + // 그 외의 정의되지 않은 모든 예외 처리 + @ExceptionHandler(Exception.class) + public ResponseEntity> handleException( + Exception ex + ) { + + BaseErrorCode code = GeneralErrorCode.INTERNAL_SERVER_ERROR; + return ResponseEntity.status(code.getStatus()) + .body(ApiResponse.onFailure( + code, + ex.getMessage() + ) + ); + } +} diff --git a/leeseo/umc10th/src/main/java/com/example/umc10th/global/entity/BaseEntity.java b/leeseo/umc10th/src/main/java/com/example/umc10th/global/entity/BaseEntity.java new file mode 100644 index 0000000..1c02c70 --- /dev/null +++ b/leeseo/umc10th/src/main/java/com/example/umc10th/global/entity/BaseEntity.java @@ -0,0 +1,29 @@ +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; + +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +@Getter +public abstract class BaseEntity { + + @CreatedDate + @Column(name = "created_at", nullable = false) + private LocalDateTime createdAt; + + @LastModifiedDate + @Column(name = "updated_at", nullable = false) + private LocalDateTime updatedAt; + + @Column(name = "deleted_at") + private LocalDateTime deletedAt; + +} diff --git a/leeseo/umc10th/src/main/resources/application.yml b/leeseo/umc10th/src/main/resources/application.yml index f249756..5678389 100644 --- a/leeseo/umc10th/src/main/resources/application.yml +++ b/leeseo/umc10th/src/main/resources/application.yml @@ -7,12 +7,12 @@ spring: username: ${DB_USER} password: ${DB_PW} - jpa: - database: mysql - database-platform: org.hibernate.dialect.MySQLDialect - show-sql: true + jpa: + database: mysql + database-platform: org.hibernate.dialect.MySQLDialect + show-sql: true + hibernate: + ddl-auto: update + properties: hibernate: - ddl-auto: update - properties: - hibernate: - format_sql: true \ No newline at end of file + format_sql: true \ No newline at end of file