diff --git a/build.gradle b/build.gradle index 0dec725..2ced02b 100644 --- a/build.gradle +++ b/build.gradle @@ -36,7 +36,10 @@ dependencies { annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.13' + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-api:2.8.13' + implementation 'org.springframework.boot:spring-boot-starter-validation' // QueryDSL : OpenFeign implementation "io.github.openfeign.querydsl:querydsl-jpa:7.0" implementation "io.github.openfeign.querydsl:querydsl-core:7.0" diff --git a/src/main/java/com/example/umc_9th/domain/converter/MissionConverter.java b/src/main/java/com/example/umc_9th/domain/converter/MissionConverter.java new file mode 100644 index 0000000..b9b913d --- /dev/null +++ b/src/main/java/com/example/umc_9th/domain/converter/MissionConverter.java @@ -0,0 +1,24 @@ +package com.example.umc_9th.domain.converter; + +import com.example.umc_9th.domain.mission.dto.res.MissionResponseDTO; +import com.example.umc_9th.domain.mission.entity.Mission; +import com.example.umc_9th.domain.mission.mapping.UserMission; + +public class MissionConverter { + + public static MissionResponseDTO.ChallengeMissionResultDTO toChallengeMissionResultDTO( + UserMission userMission + ) { + Mission mission = userMission.getMission(); + + return MissionResponseDTO.ChallengeMissionResultDTO.builder() + .userMissionId(userMission.getId()) + .missionId(mission.getId()) + .missionTitle(mission.getTitle()) + .missionDescription(mission.getDescription()) + .storeName(mission.getStore().getName()) + .points(mission.getPoints()) + .status(userMission.getStatus()) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/umc_9th/domain/store/mapping/FoodCategory.java b/src/main/java/com/example/umc_9th/domain/food/entity/FoodCategory.java similarity index 78% rename from src/main/java/com/example/umc_9th/domain/store/mapping/FoodCategory.java rename to src/main/java/com/example/umc_9th/domain/food/entity/FoodCategory.java index cedbcfa..34d23b2 100644 --- a/src/main/java/com/example/umc_9th/domain/store/mapping/FoodCategory.java +++ b/src/main/java/com/example/umc_9th/domain/food/entity/FoodCategory.java @@ -1,18 +1,17 @@ -package com.example.umc_9th.domain.store.mapping; +package com.example.umc_9th.domain.food.entity; -import com.example.umc_9th.domain.store.Store; +import com.example.umc_9th.domain.store.entity.Store; +import com.example.umc_9th.grobal.BaseEntity; import jakarta.persistence.*; import lombok.*; -import java.util.List; - @Entity @Getter @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor -public class FoodCategory { +public class FoodCategory extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/com/example/umc_9th/domain/food/entity/MemberFood.java b/src/main/java/com/example/umc_9th/domain/food/entity/MemberFood.java new file mode 100644 index 0000000..9ff0670 --- /dev/null +++ b/src/main/java/com/example/umc_9th/domain/food/entity/MemberFood.java @@ -0,0 +1,28 @@ +package com.example.umc_9th.domain.food.entity; + +import com.example.umc_9th.domain.member.entity.Member; +import com.example.umc_9th.grobal.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class MemberFood extends BaseEntity { + + @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_category_id") + private FoodCategory foodCategory; + + +} \ No newline at end of file diff --git a/src/main/java/com/example/umc_9th/domain/food/exception/FoodException.java b/src/main/java/com/example/umc_9th/domain/food/exception/FoodException.java new file mode 100644 index 0000000..5884dc4 --- /dev/null +++ b/src/main/java/com/example/umc_9th/domain/food/exception/FoodException.java @@ -0,0 +1,11 @@ +package com.example.umc_9th.domain.food.exception; + +import com.example.umc_9th.grobal.apiPayload.code.BaseErrorCode; +import com.example.umc_9th.grobal.apiPayload.exception.GeneralException; + + +public class FoodException extends GeneralException { + public FoodException(BaseErrorCode code) { + super(code); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/umc_9th/domain/food/exception/code/FoodErrorCode.java b/src/main/java/com/example/umc_9th/domain/food/exception/code/FoodErrorCode.java new file mode 100644 index 0000000..063ac13 --- /dev/null +++ b/src/main/java/com/example/umc_9th/domain/food/exception/code/FoodErrorCode.java @@ -0,0 +1,20 @@ +package com.example.umc_9th.domain.food.exception.code; + +import com.example.umc_9th.grobal.apiPayload.code.BaseErrorCode; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum FoodErrorCode implements BaseErrorCode { + + NOT_FOUND(HttpStatus.NOT_FOUND, + "FOOD404_1", + "선호하는 음식을 찾을 수 없습니다."), + ; + + private final HttpStatus status; + private final String code; + private final String message; +} diff --git a/src/main/java/com/example/umc_9th/domain/food/repository/FoodRepository.java b/src/main/java/com/example/umc_9th/domain/food/repository/FoodRepository.java new file mode 100644 index 0000000..77a14e4 --- /dev/null +++ b/src/main/java/com/example/umc_9th/domain/food/repository/FoodRepository.java @@ -0,0 +1,10 @@ +package com.example.umc_9th.domain.food.repository; + +import com.example.umc_9th.domain.food.entity.FoodCategory; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface FoodRepository extends JpaRepository { + List findAllByIdIn(List ids); +} \ No newline at end of file diff --git a/src/main/java/com/example/umc_9th/domain/food/repository/MemberFoodRepository.java b/src/main/java/com/example/umc_9th/domain/food/repository/MemberFoodRepository.java new file mode 100644 index 0000000..db591a5 --- /dev/null +++ b/src/main/java/com/example/umc_9th/domain/food/repository/MemberFoodRepository.java @@ -0,0 +1,7 @@ +package com.example.umc_9th.domain.food.repository; + +import com.example.umc_9th.domain.food.entity.MemberFood; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface MemberFoodRepository extends JpaRepository { +} diff --git a/src/main/java/com/example/umc_9th/domain/member/controller/MemberController.java b/src/main/java/com/example/umc_9th/domain/member/controller/MemberController.java new file mode 100644 index 0000000..0b7a3d6 --- /dev/null +++ b/src/main/java/com/example/umc_9th/domain/member/controller/MemberController.java @@ -0,0 +1,37 @@ +package com.example.umc_9th.domain.member.controller; + +import com.example.umc_9th.domain.member.dto.req.MemberReqDTO; +import com.example.umc_9th.domain.member.dto.res.MemberResDTO; +import com.example.umc_9th.domain.member.exception.code.MemberSuccessCode; +import com.example.umc_9th.domain.member.service.MemberService; +import com.example.umc_9th.grobal.apiPayload.ApiResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +public class MemberController { + + private final MemberService memberService; + + // 회원가입 + @PostMapping("/sign-up") + public ApiResponse signUp( + @RequestBody MemberReqDTO.JoinDTO dto + ) { + return ApiResponse.success(MemberSuccessCode.FOUND, memberService.signup(dto)); + } + + //로그인 + @PostMapping("/login") + public ApiResponse login( + @RequestBody MemberReqDTO.LoginDTO dto + ) { + return ApiResponse.success(MemberSuccessCode.FOUND, memberService.login(dto)); + } + +} \ No newline at end of file diff --git a/src/main/java/com/example/umc_9th/domain/member/converter/MemberConverter.java b/src/main/java/com/example/umc_9th/domain/member/converter/MemberConverter.java new file mode 100644 index 0000000..937ce85 --- /dev/null +++ b/src/main/java/com/example/umc_9th/domain/member/converter/MemberConverter.java @@ -0,0 +1,32 @@ +package com.example.umc_9th.domain.member.converter; + +import com.example.umc_9th.domain.member.dto.req.MemberReqDTO; +import com.example.umc_9th.domain.member.dto.res.MemberResDTO; +import com.example.umc_9th.domain.member.entity.Member; + +public class MemberConverter { + + public static Member toMember(MemberReqDTO.JoinDTO dto) { + return Member.builder() + .email(dto.email()) + .password(dto.password()) + .phoneNumber(dto.phoneNumber()) + .name(dto.name()) + .gender(dto.gender()) + .birthDate(dto.birthDate()) + .address(dto.address()) + .status(dto.status()) + .points(0) + .build(); + } + + public static MemberResDTO.JoinDTO toJoinDTO(Member member) { + return MemberResDTO.JoinDTO.builder() + .memberId(member.getMemberId()) + .createAt(member.getCreatedAt()) + .build(); + } +} + + + diff --git a/src/main/java/com/example/umc_9th/domain/member/dto/req/MemberReqDTO.java b/src/main/java/com/example/umc_9th/domain/member/dto/req/MemberReqDTO.java new file mode 100644 index 0000000..5887eec --- /dev/null +++ b/src/main/java/com/example/umc_9th/domain/member/dto/req/MemberReqDTO.java @@ -0,0 +1,28 @@ +package com.example.umc_9th.domain.member.dto.req; + +import com.example.umc_9th.domain.member.Gender; + +import java.time.LocalDate; +import java.util.List; + +public class MemberReqDTO { + + public record JoinDTO( + String email, // 추가 + String password, // 추가 + String phoneNumber, + String name, + Gender gender, + LocalDate birthDate, + String address, + boolean status, + // 선호 음식 카테고리 + ListpreferCategory + ){} + + public record LoginDTO( + String email, + String password + ){} + +} \ No newline at end of file diff --git a/src/main/java/com/example/umc_9th/domain/member/dto/res/MemberResDTO.java b/src/main/java/com/example/umc_9th/domain/member/dto/res/MemberResDTO.java new file mode 100644 index 0000000..d3fc328 --- /dev/null +++ b/src/main/java/com/example/umc_9th/domain/member/dto/res/MemberResDTO.java @@ -0,0 +1,23 @@ +package com.example.umc_9th.domain.member.dto.res; + +import lombok.Builder; + +import java.time.LocalDateTime; + +public class MemberResDTO { + + @Builder + public record JoinDTO( + Long memberId, + LocalDateTime createAt + ){} + + @Builder + public record LoginDTO( + Long memberId, + String accessToken, + String refreshToken, + LocalDateTime loginAt + ){} + +} diff --git a/src/main/java/com/example/umc_9th/domain/member/entity/Member.java b/src/main/java/com/example/umc_9th/domain/member/entity/Member.java index 0c6eb96..0ee2c2a 100644 --- a/src/main/java/com/example/umc_9th/domain/member/entity/Member.java +++ b/src/main/java/com/example/umc_9th/domain/member/entity/Member.java @@ -1,5 +1,6 @@ package com.example.umc_9th.domain.member.entity; +import com.example.umc_9th.domain.food.entity.MemberFood; import com.example.umc_9th.domain.member.Gender; import com.example.umc_9th.grobal.BaseEntity; import jakarta.persistence.*; @@ -7,6 +8,8 @@ import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; @Entity @Getter @@ -46,26 +49,17 @@ public class Member extends BaseEntity { @Column(nullable = false, length = 255) private String password; // 비밀번호 (VARCHAR(255)) + @Column(length = 255) + String accessToken; + + @Column(length = 255) + String refreshToken; + + // 회원의 선호 카테고리 목록 (1:N 관계) + @OneToMany(mappedBy = "member", cascade = CascadeType.ALL) + private List memberPreferList = new ArrayList<>(); - //양방향 고려 - -// @OneToOne(fetch = FetchType.LAZY) // 동의 테이블과 1:1 관계 매핑 -// @JoinColumn(name = "agree_id") -// private Agree agree; -// -// @OneToMany(fetch = FetchType.LAZY) // 리뷰 테이블 N:1 관계 매핑 -// @JoinColumn(name = "review_id") -// private List reviews; -// -// @OneToMany(fetch = FetchType.LAZY) // 미션 테이블 N:1 관계 매핑 -// @JoinColumn(name = "userMission_id") -// private List userMissions ; -// -////Lazy : 프록시 객체로 채워두고 실제로 그 연관된 엔티티의 데이터를 사용하는 시점 -// @OneToOne(fetch = FetchType.LAZY) // 지역 테이블과 1:1 관계 매핑 -// @JoinColumn(name = "region_id") -// private Region Region; diff --git a/src/main/java/com/example/umc_9th/domain/member/exception/MemberException.java b/src/main/java/com/example/umc_9th/domain/member/exception/MemberException.java new file mode 100644 index 0000000..b273c77 --- /dev/null +++ b/src/main/java/com/example/umc_9th/domain/member/exception/MemberException.java @@ -0,0 +1,10 @@ +package com.example.umc_9th.domain.member.exception; + +import com.example.umc_9th.grobal.apiPayload.code.BaseErrorCode; +import com.example.umc_9th.grobal.apiPayload.exception.GeneralException; + +public class MemberException extends GeneralException { + public MemberException(BaseErrorCode code) { + super(code); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/umc_9th/domain/member/exception/code/MemberErrorCode.java b/src/main/java/com/example/umc_9th/domain/member/exception/code/MemberErrorCode.java new file mode 100644 index 0000000..3743b49 --- /dev/null +++ b/src/main/java/com/example/umc_9th/domain/member/exception/code/MemberErrorCode.java @@ -0,0 +1,21 @@ +package com.example.umc_9th.domain.member.exception.code; + +import com.example.umc_9th.grobal.apiPayload.code.BaseErrorCode; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum MemberErrorCode implements BaseErrorCode { + + NOT_FOUND(HttpStatus.NOT_FOUND, + "MEMBER404_1", + "해당 사용자를 찾지 못했습니다."), + ; + + private final HttpStatus status; + private final String code; + private final String message; + +} diff --git a/src/main/java/com/example/umc_9th/domain/member/exception/code/MemberSuccessCode.java b/src/main/java/com/example/umc_9th/domain/member/exception/code/MemberSuccessCode.java new file mode 100644 index 0000000..7304b4f --- /dev/null +++ b/src/main/java/com/example/umc_9th/domain/member/exception/code/MemberSuccessCode.java @@ -0,0 +1,22 @@ +package com.example.umc_9th.domain.member.exception.code; + +import com.example.umc_9th.grobal.apiPayload.code.BaseSuccessCode; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum MemberSuccessCode implements BaseSuccessCode { + + FOUND(HttpStatus.OK, + "MEMBER200_1", + "성공적으로 사용자를 조회했습니다."), + ; + + + + private final HttpStatus status; + private final String code; + private final String message; +} \ No newline at end of file diff --git a/src/main/java/com/example/umc_9th/domain/member/repository/MemberRepository.java b/src/main/java/com/example/umc_9th/domain/member/repository/MemberRepository.java index 3695f88..ae74591 100644 --- a/src/main/java/com/example/umc_9th/domain/member/repository/MemberRepository.java +++ b/src/main/java/com/example/umc_9th/domain/member/repository/MemberRepository.java @@ -14,5 +14,6 @@ public interface MemberRepository extends JpaRepository { Optional findById(Long id); + Optional findByEmail(String email); } \ No newline at end of file diff --git a/src/main/java/com/example/umc_9th/domain/member/service/MemberService.java b/src/main/java/com/example/umc_9th/domain/member/service/MemberService.java new file mode 100644 index 0000000..aff28ff --- /dev/null +++ b/src/main/java/com/example/umc_9th/domain/member/service/MemberService.java @@ -0,0 +1,75 @@ +package com.example.umc_9th.domain.member.service; + +import com.example.umc_9th.domain.food.entity.MemberFood; +import com.example.umc_9th.domain.food.exception.FoodException; +import com.example.umc_9th.domain.food.exception.code.FoodErrorCode; +import com.example.umc_9th.domain.food.repository.FoodRepository; +import com.example.umc_9th.domain.food.repository.MemberFoodRepository; +import com.example.umc_9th.domain.member.converter.MemberConverter; +import com.example.umc_9th.domain.member.dto.req.MemberReqDTO; +import com.example.umc_9th.domain.member.dto.res.MemberResDTO; +import com.example.umc_9th.domain.member.entity.Member; +import com.example.umc_9th.domain.member.exception.MemberException; +import com.example.umc_9th.domain.member.exception.code.MemberErrorCode; +import com.example.umc_9th.domain.member.repository.MemberRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; + + +@Service +@RequiredArgsConstructor +public class MemberService { + + private final MemberRepository memberRepository; + private final MemberFoodRepository memberFoodRepository; + private final FoodRepository foodRepository; + + + // 회원가입 + public MemberResDTO.JoinDTO signup(MemberReqDTO.JoinDTO dto){ + + // 멤버 객체 생성 + Member member = MemberConverter.toMember(dto); + // DB 적용 + memberRepository.save(member); + // 선호 음식 존재 여부 확인 + if (!dto.preferCategory().isEmpty()){ + List memberFoodList = dto.preferCategory().stream() + .map(id -> MemberFood.builder() + .member(member) + .foodCategory(foodRepository.findById(id) // 변경! + .orElseThrow(() -> new FoodException(FoodErrorCode.NOT_FOUND))) + .build() + ) + .collect(Collectors.toList()); + memberFoodRepository.saveAll(memberFoodList); + } + // 응답 DTO 생성 + return MemberConverter.toJoinDTO(member); + } + + //로그인 + public MemberResDTO.LoginDTO login(MemberReqDTO.LoginDTO dto){ + Member member =memberRepository.findByEmail(dto.email()) + .orElseThrow(() -> new MemberException(MemberErrorCode.NOT_FOUND)); + + + // 임시 토큰 부여 + String acceessToken="임시 엑세스 토큰 "; + String refreshToken="임시 리프레쉬 토큰"; + + // 응답 DTO 생성 + return MemberResDTO.LoginDTO.builder() + .memberId(member.getMemberId()) + .accessToken(acceessToken) + .refreshToken(refreshToken) + .loginAt(LocalDateTime.now()) + .build(); + } + + +} diff --git a/src/main/java/com/example/umc_9th/domain/mission/controller/MissionController.java b/src/main/java/com/example/umc_9th/domain/mission/controller/MissionController.java new file mode 100644 index 0000000..7c310b7 --- /dev/null +++ b/src/main/java/com/example/umc_9th/domain/mission/controller/MissionController.java @@ -0,0 +1,32 @@ +package com.example.umc_9th.domain.mission.controller; + +import com.example.umc_9th.domain.converter.MissionConverter; +import com.example.umc_9th.domain.mission.dto.res.MissionResponseDTO; +import com.example.umc_9th.domain.mission.mapping.UserMission; +import com.example.umc_9th.domain.mission.service.MissionService; +import com.example.umc_9th.grobal.ApiResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/stores") +@Tag(name = "Mission", description = "미션 관련 API") +public class MissionController { + + private final MissionService missionService; + + @PostMapping("/{storeId}/missions/{missionId}/challenge") + public ApiResponse challengeMission( + @PathVariable Long storeId, + @PathVariable Long missionId, + @RequestParam Long memberId // 추후 JWT에서 추출 + ) { + UserMission userMission = missionService.challengeMission(storeId, missionId, memberId); + return ApiResponse.onSuccess( + MissionConverter.toChallengeMissionResultDTO(userMission) + ); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/umc_9th/domain/mission/dto/res/MissionResponseDTO.java b/src/main/java/com/example/umc_9th/domain/mission/dto/res/MissionResponseDTO.java new file mode 100644 index 0000000..cc57d5a --- /dev/null +++ b/src/main/java/com/example/umc_9th/domain/mission/dto/res/MissionResponseDTO.java @@ -0,0 +1,19 @@ +package com.example.umc_9th.domain.mission.dto.res; + +import lombok.Builder; + +import java.time.LocalDateTime; + +public class MissionResponseDTO { + @Builder + public record ChallengeMissionResultDTO( + Long userMissionId, + Long missionId, + String missionTitle, + String missionDescription, + String storeName, + Integer points, + Boolean status, // true=진행중, false=완료 + LocalDateTime createdAt + ) {} +} diff --git a/src/main/java/com/example/umc_9th/domain/mission/entity/Mission.java b/src/main/java/com/example/umc_9th/domain/mission/entity/Mission.java index 5ef5141..f2847f9 100644 --- a/src/main/java/com/example/umc_9th/domain/mission/entity/Mission.java +++ b/src/main/java/com/example/umc_9th/domain/mission/entity/Mission.java @@ -1,7 +1,7 @@ package com.example.umc_9th.domain.mission.entity; import com.example.umc_9th.domain.review.entity.Review; -import com.example.umc_9th.domain.store.Store; +import com.example.umc_9th.domain.store.entity.Store; import com.example.umc_9th.grobal.BaseEntity; import jakarta.persistence.*; import lombok.*; @@ -31,12 +31,6 @@ public class Mission extends BaseEntity { // @ColumnDefault("0") private int points=0; //미션 포인트 - - //양방향 고려 -// @OneToMany(fetch = FetchType.LAZY) // 미션 테이블 N:1 관계 매핑 -// @JoinColumn(name = "userMission_id") -// private List userMissions ; - @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "store_id") diff --git a/src/main/java/com/example/umc_9th/domain/mission/execption/MissionException.java b/src/main/java/com/example/umc_9th/domain/mission/execption/MissionException.java new file mode 100644 index 0000000..2f1b9a0 --- /dev/null +++ b/src/main/java/com/example/umc_9th/domain/mission/execption/MissionException.java @@ -0,0 +1,10 @@ +package com.example.umc_9th.domain.mission.execption; + +import com.example.umc_9th.grobal.apiPayload.code.BaseErrorCode; +import com.example.umc_9th.grobal.apiPayload.exception.GeneralException; + +public class MissionException extends GeneralException { + public MissionException(BaseErrorCode code) { + super(code); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/umc_9th/domain/mission/execption/code/MissionErrorCode.java b/src/main/java/com/example/umc_9th/domain/mission/execption/code/MissionErrorCode.java new file mode 100644 index 0000000..1b5e905 --- /dev/null +++ b/src/main/java/com/example/umc_9th/domain/mission/execption/code/MissionErrorCode.java @@ -0,0 +1,21 @@ +package com.example.umc_9th.domain.mission.execption.code; + +import com.example.umc_9th.grobal.apiPayload.code.BaseErrorCode; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum MissionErrorCode implements BaseErrorCode { + + NOT_FOUND(HttpStatus.NOT_FOUND, "MISSION404_1", "해당 미션을 찾을 수 없습니다."), + ALREADY_CHALLENGING(HttpStatus.BAD_REQUEST, "MISSION400_1", "이미 도전 중인 미션입니다."), + STORE_MISMATCH(HttpStatus.BAD_REQUEST, "MISSION400_2", "해당 가게의 미션이 아닙니다."); + + + private final HttpStatus status; + private final String code; + private final String message; + +} diff --git a/src/main/java/com/example/umc_9th/domain/mission/execption/code/MissionSuccessCode.java b/src/main/java/com/example/umc_9th/domain/mission/execption/code/MissionSuccessCode.java new file mode 100644 index 0000000..ee47b3c --- /dev/null +++ b/src/main/java/com/example/umc_9th/domain/mission/execption/code/MissionSuccessCode.java @@ -0,0 +1,22 @@ +package com.example.umc_9th.domain.mission.execption.code; + +import com.example.umc_9th.grobal.apiPayload.code.BaseSuccessCode; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum MissionSuccessCode implements BaseSuccessCode { + + FOUND(HttpStatus.OK, + "MISSION200_1", + "성공적으로 미션을 조회했습니다."), + ; + + + + private final HttpStatus status; + private final String code; + private final String message; +} \ No newline at end of file diff --git a/src/main/java/com/example/umc_9th/domain/mission/mapping/UserMission.java b/src/main/java/com/example/umc_9th/domain/mission/mapping/UserMission.java index 170a2b5..382a8ce 100644 --- a/src/main/java/com/example/umc_9th/domain/mission/mapping/UserMission.java +++ b/src/main/java/com/example/umc_9th/domain/mission/mapping/UserMission.java @@ -40,7 +40,11 @@ public class UserMission { private Mission mission; - + // 미션 완료 처리 메서드 + public void complete() { + this.status = false; + this.completedAt = LocalDateTime.now(); + } } diff --git a/src/main/java/com/example/umc_9th/domain/mission/repository/MissionRepository.java b/src/main/java/com/example/umc_9th/domain/mission/repository/MissionRepository.java new file mode 100644 index 0000000..594016e --- /dev/null +++ b/src/main/java/com/example/umc_9th/domain/mission/repository/MissionRepository.java @@ -0,0 +1,8 @@ +package com.example.umc_9th.domain.mission.repository; + +import com.example.umc_9th.domain.mission.entity.Mission; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface MissionRepository extends JpaRepository { + +} diff --git a/src/main/java/com/example/umc_9th/domain/mission/repository/UserMissionRepository.java b/src/main/java/com/example/umc_9th/domain/mission/repository/UserMissionRepository.java index 576a720..182665b 100644 --- a/src/main/java/com/example/umc_9th/domain/mission/repository/UserMissionRepository.java +++ b/src/main/java/com/example/umc_9th/domain/mission/repository/UserMissionRepository.java @@ -1,5 +1,7 @@ package com.example.umc_9th.domain.mission.repository; +import com.example.umc_9th.domain.member.entity.Member; +import com.example.umc_9th.domain.mission.entity.Mission; import com.example.umc_9th.domain.mission.mapping.UserMission; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -7,6 +9,8 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import java.util.List; + public interface UserMissionRepository extends JpaRepository { @@ -21,5 +25,10 @@ public interface UserMissionRepository extends JpaRepository PagefindMyMission(@Param("memberId") Long memberId, Pageable pageable); + // 이미 도전 중인지 확인 (status=true가 진행중) + boolean existsByMemberAndMissionAndStatus(Member member, Mission mission, Boolean status); + + // 회원의 진행 중인 미션 목록 + List findByMemberAndStatus(Member member, Boolean status); } \ No newline at end of file diff --git a/src/main/java/com/example/umc_9th/domain/mission/service/MissionService.java b/src/main/java/com/example/umc_9th/domain/mission/service/MissionService.java new file mode 100644 index 0000000..d1c4304 --- /dev/null +++ b/src/main/java/com/example/umc_9th/domain/mission/service/MissionService.java @@ -0,0 +1,58 @@ +package com.example.umc_9th.domain.mission.service; + +import com.example.umc_9th.domain.member.entity.Member; +import com.example.umc_9th.domain.member.exception.MemberException; +import com.example.umc_9th.domain.member.exception.code.MemberErrorCode; +import com.example.umc_9th.domain.member.repository.MemberRepository; +import com.example.umc_9th.domain.mission.entity.Mission; +import com.example.umc_9th.domain.mission.execption.MissionException; +import com.example.umc_9th.domain.mission.execption.code.MissionErrorCode; +import com.example.umc_9th.domain.mission.mapping.UserMission; +import com.example.umc_9th.domain.mission.repository.MissionRepository; +import com.example.umc_9th.domain.mission.repository.UserMissionRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional +public class MissionService { + + private final MissionRepository missionRepository; + private final MemberRepository memberRepository; + private final UserMissionRepository userMissionRepository; + + public UserMission challengeMission(Long storeId, Long missionId, Long memberId) { + + // Member 조회 + Member member = memberRepository.findById(memberId) + .orElseThrow(() -> new MemberException(MemberErrorCode.NOT_FOUND)); + + // Mission 조회 + Mission mission = missionRepository.findById(missionId) + .orElseThrow(() -> new MissionException(MissionErrorCode.NOT_FOUND)); + + // 가게 ID 검증 + if (!mission.getStore().getId().equals(storeId)) { + throw new MissionException(MissionErrorCode.STORE_MISMATCH); + } + + // 4. 이미 도전 중인지 확인 (status=true가 진행중) , 리포지토리 접근 + boolean isAlreadyChallenging = userMissionRepository + .existsByMemberAndMissionAndStatus(member, mission, true); + + if (isAlreadyChallenging) { + throw new MissionException(MissionErrorCode.ALREADY_CHALLENGING); + } + + // 5. UserMission 생성 + UserMission userMission = UserMission.builder() + .member(member) + .mission(mission) + .status(true) // true = 진행중 + .build(); + + return userMissionRepository.save(userMission); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/umc_9th/domain/review/controller/Review7thController.java b/src/main/java/com/example/umc_9th/domain/review/controller/Review7thController.java new file mode 100644 index 0000000..4b08d13 --- /dev/null +++ b/src/main/java/com/example/umc_9th/domain/review/controller/Review7thController.java @@ -0,0 +1,38 @@ +package com.example.umc_9th.domain.review.controller; + +import com.example.umc_9th.domain.review.converter.ReviewConverter; +import com.example.umc_9th.domain.review.dto.req.ReviewRequestDTO.CreateReviewDTO; +import com.example.umc_9th.domain.review.dto.res.ReviewResponseDTO.CreateReviewResultDTO; +import com.example.umc_9th.domain.review.service.ReviewService; +import com.example.umc_9th.grobal.ApiResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/stores") +@Tag(name = "Review") +public class Review7thController { + + private final ReviewService reviewService; + + @PostMapping("/{storeId}/reviews") + @Operation(summary = "리뷰 작성") + public ApiResponse createReview( + @PathVariable Long storeId, + @RequestParam Long memberId, + @Valid @RequestBody CreateReviewDTO request + ) { + var review = reviewService.createReview(storeId, memberId, request); + return ApiResponse.onSuccess( + ReviewConverter.toCreateReviewResultDTO(review, request.getImageUrls()) + ); + } + + + + +} \ No newline at end of file diff --git a/src/main/java/com/example/umc_9th/domain/review/controller/ReviewController.java b/src/main/java/com/example/umc_9th/domain/review/controller/ReviewController.java index d26916d..b96a7c2 100644 --- a/src/main/java/com/example/umc_9th/domain/review/controller/ReviewController.java +++ b/src/main/java/com/example/umc_9th/domain/review/controller/ReviewController.java @@ -1,10 +1,12 @@ package com.example.umc_9th.domain.review.controller; import com.example.umc_9th.domain.review.dto.res.ReviewResponseDTO; -import com.example.umc_9th.domain.review.entity.Review; import com.example.umc_9th.domain.review.service.ReviewQueryService; import com.example.umc_9th.grobal.apiPayload.ApiResponse; import com.example.umc_9th.grobal.apiPayload.code.GeneralSuccessCode; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -15,21 +17,28 @@ @RestController @RequiredArgsConstructor +@Tag(name = "Review", description = "리뷰 관련 API") public class ReviewController { private final ReviewQueryService reviewQueryService; @GetMapping("/members/{memberId}/reviews/search") - public ApiResponse> searchMyReview( + @Operation( + summary = "내 리뷰 조회", + description = "회원이 작성한 리뷰를 조회합니다. storeId와 rating으로 필터링할 수 있습니다." + ) + public ApiResponse> searchMyReview( + @Parameter(description = "회원 ID", required = true) @PathVariable Long memberId, + + @Parameter(description = "가게 ID (선택)") @RequestParam(required = false) Long storeId, + + @Parameter(description = "별점 (1-5, 선택)") @RequestParam(required = false) Integer rating ) { - - - List reviewList = reviewQueryService.searchMyReviews(memberId, storeId, rating); + List reviewList = reviewQueryService.searchMyReviews(memberId, storeId, rating); return ApiResponse.success(GeneralSuccessCode.REVIEWS_FOUND, reviewList); } - -} +} \ No newline at end of file diff --git a/src/main/java/com/example/umc_9th/domain/review/converter/ReviewConverter.java b/src/main/java/com/example/umc_9th/domain/review/converter/ReviewConverter.java index 3513dff..b8a84d9 100644 --- a/src/main/java/com/example/umc_9th/domain/review/converter/ReviewConverter.java +++ b/src/main/java/com/example/umc_9th/domain/review/converter/ReviewConverter.java @@ -1,29 +1,71 @@ package com.example.umc_9th.domain.review.converter; +import com.example.umc_9th.domain.member.entity.Member; +import com.example.umc_9th.domain.review.dto.req.ReviewRequestDTO; import com.example.umc_9th.domain.review.dto.res.ReviewResponseDTO; import com.example.umc_9th.domain.review.entity.Review; +import com.example.umc_9th.domain.review.entity.ReviewImage; +import com.example.umc_9th.domain.store.entity.Store; import java.util.List; import java.util.stream.Collectors; public class ReviewConverter { - public static ReviewResponseDTO toReviewDTO(Review review) { - - - return ReviewResponseDTO.builder() + // 리뷰 조회용 DTO 변환 + public static ReviewResponseDTO.ReviewDTO toReviewDTO(Review review) { + return ReviewResponseDTO.ReviewDTO.builder() .reviewId(review.getId()) .memberName(review.getMember().getName()) .storeName(review.getStore().getName()) .rating(review.getRating()) .content(review.getContent()) + .createdAt(review.getCreatedAt()) .build(); } // Review 엔티티 리스트르르 DTO리스트로 변환 - public static List toReviewDTOList(List reviewList) { + public static List toReviewDTOList(List reviewList) { return reviewList.stream() .map(ReviewConverter::toReviewDTO) .collect(Collectors.toList()); } + + public static Review toReview(ReviewRequestDTO.CreateReviewDTO request, Member member, Store store) { + return Review.builder() + .rating(request.getRating()) + .content(request.getContent()) + .member(member) + .store(store) + .build(); + } + + public static ReviewImage toReviewImage(String imageUrl, Review review) { + return ReviewImage.builder() + .imageUrl(imageUrl) + .review(review) + .build(); + } + + public static List toReviewImageList(List imageUrls, Review review) { + if (imageUrls == null || imageUrls.isEmpty()) { + return List.of(); + } + + return imageUrls.stream() + .map(url -> toReviewImage(url, review)) + .collect(Collectors.toList()); + } + + public static ReviewResponseDTO.CreateReviewResultDTO toCreateReviewResultDTO(Review review, List imageUrls) { + return ReviewResponseDTO.CreateReviewResultDTO.builder() + .reviewId(review.getId()) + .memberId(review.getMember().getMemberId()) + .storeId(review.getStore().getId()) + .rating(review.getRating()) + .content(review.getContent()) + .imageUrls(imageUrls != null ? imageUrls : List.of()) + .createdAt(review.getCreatedAt()) + .build(); + } } \ No newline at end of file diff --git a/src/main/java/com/example/umc_9th/domain/review/dto/req/ReviewRequestDTO.java b/src/main/java/com/example/umc_9th/domain/review/dto/req/ReviewRequestDTO.java new file mode 100644 index 0000000..fac82b9 --- /dev/null +++ b/src/main/java/com/example/umc_9th/domain/review/dto/req/ReviewRequestDTO.java @@ -0,0 +1,41 @@ +package com.example.umc_9th.domain.review.dto.req; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.validator.constraints.URL; + +import java.time.LocalDateTime; +import java.util.List; + +public class ReviewRequestDTO { + + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + @Schema(description = "가게에 리뷰 추가하기 API") + public static class CreateReviewDTO { + + @NotNull(message = "별점은 필수입니다.") + @Min(value = 1, message = "별점은 1점 이상이어야 합니다.") + @Max(value = 5, message = "별점은 5점 이하여야 합니다.") + @Schema(description = "별점 1~5", required = true) + private Integer rating; + + @NotBlank(message = "리뷰 내용은 필수입니다.") + @Size(min = 10, max = 255, message = "리뷰 내용은 10자 이상 255자 이하여야 합니다.") + @Schema(description = "리뷰 내용", required = true) + private String content; + + @Schema(description = "리뷰 이미지 URL 리스트") + private List<@URL(message = "올바른 URL 형식이 아닙니다.") String> imageUrls; + } + + + + +} \ No newline at end of file diff --git a/src/main/java/com/example/umc_9th/domain/review/dto/res/ReviewResponseDTO.java b/src/main/java/com/example/umc_9th/domain/review/dto/res/ReviewResponseDTO.java index 6bddbc9..a6559e7 100644 --- a/src/main/java/com/example/umc_9th/domain/review/dto/res/ReviewResponseDTO.java +++ b/src/main/java/com/example/umc_9th/domain/review/dto/res/ReviewResponseDTO.java @@ -1,32 +1,75 @@ package com.example.umc_9th.domain.review.dto.res; import com.example.umc_9th.grobal.BaseEntity; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import java.time.LocalDateTime; +import java.util.List; -@Getter -@Builder -@NoArgsConstructor -@AllArgsConstructor public class ReviewResponseDTO extends BaseEntity { - private Long reviewId; + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + @Schema(description = "리뷰 작성 응답") + public static class CreateReviewResultDTO { - private String memberName; + @Schema(description = "리뷰 ID") + private Long reviewId; - private String storeName; + @Schema(description = "작성자 ID") + private Long memberId; - private Integer rating; + @Schema(description = "가게 ID") + private Long storeId; + @Schema(description = "별점") + private Integer rating; - private String content; + @Schema(description = "리뷰 내용") + private String content; + // 일대다 한 리뷰에 여러 이미지 + @Schema(description = "리뷰 이미지 URL 리스트") + private List imageUrls; + @Schema(description = "작성일시") + private LocalDateTime createdAt; + } + + + + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + @Schema(description = "리뷰 조회 응답") + public static class ReviewDTO { + + @Schema(description = "리뷰 ID") + private Long reviewId; + + @Schema(description = "작성자 이름") + private String memberName; + + @Schema(description = "가게 이름") + private String storeName; + + @Schema(description = "별점") + private Integer rating; + + @Schema(description = "리뷰 내용") + private String content; + + @Schema(description = "작성일시") + private LocalDateTime createdAt; + } } \ No newline at end of file diff --git a/src/main/java/com/example/umc_9th/domain/review/entity/Review.java b/src/main/java/com/example/umc_9th/domain/review/entity/Review.java index f189bb7..eb746ed 100644 --- a/src/main/java/com/example/umc_9th/domain/review/entity/Review.java +++ b/src/main/java/com/example/umc_9th/domain/review/entity/Review.java @@ -2,7 +2,7 @@ import com.example.umc_9th.domain.member.entity.Member; -import com.example.umc_9th.domain.store.Store; +import com.example.umc_9th.domain.store.entity.Store; import com.example.umc_9th.grobal.BaseEntity; import jakarta.persistence.*; import lombok.*; diff --git a/src/main/java/com/example/umc_9th/domain/review/repository/ReviewImageRepository.java b/src/main/java/com/example/umc_9th/domain/review/repository/ReviewImageRepository.java new file mode 100644 index 0000000..c9f16a7 --- /dev/null +++ b/src/main/java/com/example/umc_9th/domain/review/repository/ReviewImageRepository.java @@ -0,0 +1,9 @@ +package com.example.umc_9th.domain.review.repository; + +import com.example.umc_9th.domain.review.entity.ReviewImage; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface ReviewImageRepository extends JpaRepository { +} \ No newline at end of file diff --git a/src/main/java/com/example/umc_9th/domain/review/repository/ReviewRepository.java b/src/main/java/com/example/umc_9th/domain/review/repository/ReviewRepository.java index 48a5718..fd30ad1 100644 --- a/src/main/java/com/example/umc_9th/domain/review/repository/ReviewRepository.java +++ b/src/main/java/com/example/umc_9th/domain/review/repository/ReviewRepository.java @@ -2,7 +2,7 @@ import com.example.umc_9th.domain.member.entity.Member; import com.example.umc_9th.domain.review.entity.Review; -import com.example.umc_9th.domain.store.Store; +import com.example.umc_9th.domain.store.entity.Store; import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; diff --git a/src/main/java/com/example/umc_9th/domain/review/service/ReviewQueryService.java b/src/main/java/com/example/umc_9th/domain/review/service/ReviewQueryService.java index 9f04f95..108ebf7 100644 --- a/src/main/java/com/example/umc_9th/domain/review/service/ReviewQueryService.java +++ b/src/main/java/com/example/umc_9th/domain/review/service/ReviewQueryService.java @@ -1,5 +1,6 @@ package com.example.umc_9th.domain.review.service; +import com.example.umc_9th.domain.member.entity.Member; import com.example.umc_9th.domain.member.repository.MemberRepository; import com.example.umc_9th.domain.review.converter.ReviewConverter; import com.example.umc_9th.domain.review.dto.res.ReviewResponseDTO; @@ -15,17 +16,16 @@ @Service @RequiredArgsConstructor -@Transactional +@Transactional(readOnly = true) public class ReviewQueryService { private final ReviewRepository reviewRepository; private final MemberRepository memberRepository; - public List searchMyReviews(Long memberId, Long storeId, Integer rating) { + public List searchMyReviews(Long memberId, Long storeId, Integer rating) { - - // 회원 유효성 검사 - memberRepository.findById(memberId) + // 회원 유효성 검사 및 조회 + Member member = memberRepository.findById(memberId) .orElseThrow(() -> new ReviewException(ReviewErrorCode.MEMBER_NOT_FOUND)); // 평점값 유효성 검사 @@ -33,9 +33,14 @@ public List searchMyReviews(Long memberId, Long storeId, Inte throw new ReviewException(ReviewErrorCode.INVALID_RATING_VALUE); } - List reviewList = reviewRepository.findMyReviews(memberId, storeId, rating); + // 모든 리뷰 조회 후 필터링 (기존 Repository 메서드 활용) + List reviewList = reviewRepository.findAll(); - return ReviewConverter.toReviewDTOList(reviewList); + // memberId로 필터링 + reviewList = reviewList.stream() + .filter(review -> review.getMember().getMemberId().equals(memberId)) + .toList(); + return ReviewConverter.toReviewDTOList(reviewList); } -} +} \ No newline at end of file diff --git a/src/main/java/com/example/umc_9th/domain/review/service/ReviewService.java b/src/main/java/com/example/umc_9th/domain/review/service/ReviewService.java new file mode 100644 index 0000000..d8aaaf3 --- /dev/null +++ b/src/main/java/com/example/umc_9th/domain/review/service/ReviewService.java @@ -0,0 +1,8 @@ +package com.example.umc_9th.domain.review.service; + +import com.example.umc_9th.domain.review.dto.req.ReviewRequestDTO; +import com.example.umc_9th.domain.review.entity.Review; + +public interface ReviewService { + Review createReview(Long storeId, Long memberId, ReviewRequestDTO.CreateReviewDTO request); +} \ No newline at end of file diff --git a/src/main/java/com/example/umc_9th/domain/review/service/ReviewServiceImpl.java b/src/main/java/com/example/umc_9th/domain/review/service/ReviewServiceImpl.java new file mode 100644 index 0000000..6573fe1 --- /dev/null +++ b/src/main/java/com/example/umc_9th/domain/review/service/ReviewServiceImpl.java @@ -0,0 +1,52 @@ +package com.example.umc_9th.domain.review.service; + +import com.example.umc_9th.domain.member.entity.Member; +import com.example.umc_9th.domain.member.repository.MemberRepository; +import com.example.umc_9th.domain.review.converter.ReviewConverter; +import com.example.umc_9th.domain.review.dto.req.ReviewRequestDTO; +import com.example.umc_9th.domain.review.entity.Review; +import com.example.umc_9th.domain.review.entity.ReviewImage; +import com.example.umc_9th.domain.review.repository.ReviewImageRepository; +import com.example.umc_9th.domain.review.repository.ReviewRepository; +import com.example.umc_9th.domain.store.entity.Store; +import com.example.umc_9th.domain.store.repository.StoreRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class ReviewServiceImpl implements ReviewService { + + private final ReviewRepository reviewRepository; + private final ReviewImageRepository reviewImageRepository; + private final MemberRepository memberRepository; + private final StoreRepository storeRepository; + + @Override + @Transactional + public Review createReview(Long storeId, Long memberId, ReviewRequestDTO.CreateReviewDTO request) { + // 1. Member 조회 + Member member = memberRepository.findById(memberId) + .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 회원입니다.")); + + // 2. Store 조회 + Store store = storeRepository.findById(storeId) + .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 가게입니다.")); + + // 3. Review 생성 및 저장 + Review review = ReviewConverter.toReview(request, member, store); + Review savedReview = reviewRepository.save(review); + + // 4. ReviewImage 생성 및 저장 + if (request.getImageUrls() != null && !request.getImageUrls().isEmpty()) { + List reviewImages = ReviewConverter.toReviewImageList(request.getImageUrls(), savedReview); + reviewImageRepository.saveAll(reviewImages); + } + + return savedReview; + } +} diff --git a/src/main/java/com/example/umc_9th/domain/store/Store.java b/src/main/java/com/example/umc_9th/domain/store/entity/Store.java similarity index 89% rename from src/main/java/com/example/umc_9th/domain/store/Store.java rename to src/main/java/com/example/umc_9th/domain/store/entity/Store.java index d3634b2..c538f29 100644 --- a/src/main/java/com/example/umc_9th/domain/store/Store.java +++ b/src/main/java/com/example/umc_9th/domain/store/entity/Store.java @@ -1,7 +1,7 @@ -package com.example.umc_9th.domain.store; +package com.example.umc_9th.domain.store.entity; import com.example.umc_9th.domain.region.entity.Region; -import com.example.umc_9th.domain.store.mapping.FoodCategory; +import com.example.umc_9th.domain.food.entity.FoodCategory; import com.example.umc_9th.grobal.BaseEntity; import jakarta.persistence.*; import lombok.*; diff --git a/src/main/java/com/example/umc_9th/domain/store/mapping/MemberFoodCategory.java b/src/main/java/com/example/umc_9th/domain/store/mapping/MemberFoodCategory.java index 56c9cd8..06dfb71 100644 --- a/src/main/java/com/example/umc_9th/domain/store/mapping/MemberFoodCategory.java +++ b/src/main/java/com/example/umc_9th/domain/store/mapping/MemberFoodCategory.java @@ -1,6 +1,7 @@ package com.example.umc_9th.domain.store.mapping; +import com.example.umc_9th.domain.food.entity.FoodCategory; import com.example.umc_9th.domain.member.entity.Member; import jakarta.persistence.*; import lombok.*; diff --git a/src/main/java/com/example/umc_9th/domain/store/repository/StoreRepository.java b/src/main/java/com/example/umc_9th/domain/store/repository/StoreRepository.java new file mode 100644 index 0000000..f1fc875 --- /dev/null +++ b/src/main/java/com/example/umc_9th/domain/store/repository/StoreRepository.java @@ -0,0 +1,9 @@ +package com.example.umc_9th.domain.store.repository; + +import com.example.umc_9th.domain.store.entity.Store; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface StoreRepository extends JpaRepository { +} diff --git a/src/main/java/com/example/umc_9th/grobal/ApiResponse.java b/src/main/java/com/example/umc_9th/grobal/ApiResponse.java new file mode 100644 index 0000000..0db0b4e --- /dev/null +++ b/src/main/java/com/example/umc_9th/grobal/ApiResponse.java @@ -0,0 +1,37 @@ +package com.example.umc_9th.grobal; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +@Schema(description = "API 공통 응답") +public class ApiResponse { + + @JsonProperty("isSuccess") + @Schema(description = "성공 여부", example = "true") + private final Boolean isSuccess; + + @Schema(description = "응답 코드", example = "2000") + private final String code; + + @Schema(description = "응답 메시지", example = "성공하였습니다.") + private final String message; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @Schema(description = "응답 데이터") + private T result; + + // 성공한 경우 응답 생성 + public static ApiResponse onSuccess(T result) { + return new ApiResponse<>(true, "2000", "성공하였습니다.", result); + } + + // 실패한 경우 응답 생성 + public static ApiResponse onFailure(String code, String message, T data) { + return new ApiResponse<>(false, code, message, data); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/umc_9th/grobal/config/SwaggerConfig.java b/src/main/java/com/example/umc_9th/grobal/config/SwaggerConfig.java new file mode 100644 index 0000000..02aebef --- /dev/null +++ b/src/main/java/com/example/umc_9th/grobal/config/SwaggerConfig.java @@ -0,0 +1,35 @@ +package com.example.umc_9th.grobal.config; + +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import io.swagger.v3.oas.models.servers.Server; +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +@Configuration +public class SwaggerConfig { + + @Bean + public OpenAPI swagger() { + Info info = new Info().title("Project").description("Project Swagger").version("0.0.1"); + + // JWT 토큰 헤더 방식 + String securityScheme = "JWT TOKEN"; + SecurityRequirement securityRequirement = new SecurityRequirement().addList(securityScheme); + + Components components = new Components() + .addSecuritySchemes(securityScheme, new SecurityScheme() + .name(securityScheme) + .type(SecurityScheme.Type.HTTP) + .scheme("Bearer") + .bearerFormat("JWT")); + + return new OpenAPI() + .info(info) + .addServersItem(new Server().url("/")) + .addSecurityItem(securityRequirement) + .components(components); + } +} \ No newline at end of file diff --git a/src/main/resources/testFood.sql b/src/main/resources/testFood.sql new file mode 100644 index 0000000..da38368 --- /dev/null +++ b/src/main/resources/testFood.sql @@ -0,0 +1,8 @@ +INSERT INTO food_category (category) VALUES + ('한식'), + ('중식'), + ('일식'), + ('양식'), + ('분식'), + ('카페/디저트'), + ('패스트푸드'); \ No newline at end of file