diff --git a/build.gradle b/build.gradle index ec40a3c..0abc4d1 100644 --- a/build.gradle +++ b/build.gradle @@ -43,6 +43,9 @@ dependencies { // aop implementation 'org.springframework.boot:spring-boot-starter-aop' + + // Validation + implementation 'org.springframework.boot:spring-boot-starter-validation' } tasks.named('test') { diff --git a/src/main/java/com/example/umc9th/domain/category/repository/CategoryRepository.java b/src/main/java/com/example/umc9th/domain/category/repository/CategoryRepository.java new file mode 100644 index 0000000..98189e1 --- /dev/null +++ b/src/main/java/com/example/umc9th/domain/category/repository/CategoryRepository.java @@ -0,0 +1,7 @@ +package com.example.umc9th.domain.category.repository; + +import com.example.umc9th.domain.category.entity.Category; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CategoryRepository extends JpaRepository { +} diff --git a/src/main/java/com/example/umc9th/domain/mission/controller/MissionController.java b/src/main/java/com/example/umc9th/domain/mission/controller/MissionController.java new file mode 100644 index 0000000..1bb7699 --- /dev/null +++ b/src/main/java/com/example/umc9th/domain/mission/controller/MissionController.java @@ -0,0 +1,37 @@ +package com.example.umc9th.domain.mission.controller; + +import com.example.umc9th.domain.mission.dto.MissionRequestDto; +import com.example.umc9th.domain.mission.dto.MissionResponseDto; +import com.example.umc9th.domain.mission.service.MissionService; +import com.example.umc9th.domain.review.dto.ReviewRequestDto; +import com.example.umc9th.domain.review.dto.ReviewResponseDto; +import com.example.umc9th.global.apiPayload.ApiResponse; +import com.example.umc9th.global.apiPayload.code.status.GeneralSuccessCode; +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/missions") +@Tag(name = "미션") +public class MissionController { + private final MissionService missionService; + + @Operation( + summary = "미션 추가", + description = "관리자용") + @PostMapping("/admin") + public ApiResponse addMission(@RequestBody MissionRequestDto.CreateMission req) { + return ApiResponse.onSuccess(GeneralSuccessCode._CREATED, missionService.createMission(req)); + } + + @Operation( + summary = "사용자 미션 추가", + description = "사용자가 가게 미션을 본인의 도전 미션에 추가합니다.") + @PostMapping("/{mission-id}") + public ApiResponse addMission(@RequestParam @PathVariable("mission-id")Long missionId) { + return ApiResponse.onSuccess(GeneralSuccessCode._CREATED, missionService.addUserMission(missionId)); + } +} diff --git a/src/main/java/com/example/umc9th/domain/mission/dto/MissionRequestDto.java b/src/main/java/com/example/umc9th/domain/mission/dto/MissionRequestDto.java new file mode 100644 index 0000000..4749504 --- /dev/null +++ b/src/main/java/com/example/umc9th/domain/mission/dto/MissionRequestDto.java @@ -0,0 +1,19 @@ +package com.example.umc9th.domain.mission.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +public class MissionRequestDto { + private MissionRequestDto() {} + + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class CreateMission{ + private Integer point; + private String content; + } +} diff --git a/src/main/java/com/example/umc9th/domain/mission/dto/MissionResponseDto.java b/src/main/java/com/example/umc9th/domain/mission/dto/MissionResponseDto.java new file mode 100644 index 0000000..5564066 --- /dev/null +++ b/src/main/java/com/example/umc9th/domain/mission/dto/MissionResponseDto.java @@ -0,0 +1,18 @@ +package com.example.umc9th.domain.mission.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +public class MissionResponseDto { + private MissionResponseDto() {} + + @Builder + @Getter + @AllArgsConstructor + public static class AddUserMission{ + private Long missionId; + private String content; + private String createdAt; + } +} diff --git a/src/main/java/com/example/umc9th/domain/mission/service/MissionService.java b/src/main/java/com/example/umc9th/domain/mission/service/MissionService.java new file mode 100644 index 0000000..477391e --- /dev/null +++ b/src/main/java/com/example/umc9th/domain/mission/service/MissionService.java @@ -0,0 +1,9 @@ +package com.example.umc9th.domain.mission.service; + +import com.example.umc9th.domain.mission.dto.MissionRequestDto; +import com.example.umc9th.domain.mission.dto.MissionResponseDto; + +public interface MissionService { + String createMission(MissionRequestDto.CreateMission req); + MissionResponseDto.AddUserMission addUserMission(Long missionId); +} diff --git a/src/main/java/com/example/umc9th/domain/mission/service/MissionServiceImpl.java b/src/main/java/com/example/umc9th/domain/mission/service/MissionServiceImpl.java new file mode 100644 index 0000000..5519327 --- /dev/null +++ b/src/main/java/com/example/umc9th/domain/mission/service/MissionServiceImpl.java @@ -0,0 +1,50 @@ +package com.example.umc9th.domain.mission.service; + +import com.example.umc9th.domain.mission.dto.MissionRequestDto; +import com.example.umc9th.domain.mission.dto.MissionResponseDto; +import com.example.umc9th.domain.mission.entity.Mission; +import com.example.umc9th.domain.mission.entity.UserMission; +import com.example.umc9th.domain.mission.repository.MissionRepository; +import com.example.umc9th.domain.mission.repository.UserMissionRepository; +import com.example.umc9th.domain.store.repository.StoreRepository; +import com.example.umc9th.domain.user.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class MissionServiceImpl implements MissionService{ + private final MissionRepository missionRepository; + private final StoreRepository storeRepository; + private final UserRepository userRepository; + private final UserMissionRepository userMissionRepository; + + @Override + public String createMission(MissionRequestDto.CreateMission req) { + Mission mission = Mission.builder() + .store(storeRepository.getReferenceById(1L)) + .content(req.getContent()) + .point(req.getPoint()) + .build(); + + missionRepository.save(mission); + + return mission.getId().toString(); + } + + @Override + public MissionResponseDto.AddUserMission addUserMission(Long missionId) { + UserMission um = UserMission.builder() + .user(userRepository.getReferenceById(1L)) + .mission(missionRepository.getReferenceById(missionId)) + .build(); + + userMissionRepository.save(um); + + return MissionResponseDto.AddUserMission.builder() + .missionId(missionId) + .content(um.getMission().getContent()) + .createdAt(um.getMission().getCreatedAt().toString()) + .build(); + } +} diff --git a/src/main/java/com/example/umc9th/domain/review/controller/ReviewController.java b/src/main/java/com/example/umc9th/domain/review/controller/ReviewController.java index d8245fd..cd994ca 100644 --- a/src/main/java/com/example/umc9th/domain/review/controller/ReviewController.java +++ b/src/main/java/com/example/umc9th/domain/review/controller/ReviewController.java @@ -1,5 +1,6 @@ package com.example.umc9th.domain.review.controller; +import com.example.umc9th.domain.review.dto.ReviewRequestDto; import com.example.umc9th.domain.review.dto.ReviewResponseDto; import com.example.umc9th.domain.review.service.ReviewService; import com.example.umc9th.global.apiPayload.ApiResponse; @@ -8,10 +9,7 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; @RestController @RequiredArgsConstructor @@ -39,4 +37,14 @@ public ApiResponse> getReviews( size)); } + @Operation( + summary = "리뷰 작성", + description = "해당 가게에 리뷰를 추가합니다.") + @PostMapping + public ApiResponse addReview(@RequestBody ReviewRequestDto.Create req) { + return ApiResponse.onSuccess(GeneralSuccessCode._CREATED,reviewService.createReview(req)); + } + + + } diff --git a/src/main/java/com/example/umc9th/domain/review/dto/ReviewRequestDto.java b/src/main/java/com/example/umc9th/domain/review/dto/ReviewRequestDto.java new file mode 100644 index 0000000..2bc6355 --- /dev/null +++ b/src/main/java/com/example/umc9th/domain/review/dto/ReviewRequestDto.java @@ -0,0 +1,23 @@ +package com.example.umc9th.domain.review.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + +public class ReviewRequestDto { + private ReviewRequestDto() {} + + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class Create{ + private Long storeId; + private String content; + private Float star; + private List reviewImages; + } +} diff --git a/src/main/java/com/example/umc9th/domain/review/dto/ReviewResponseDto.java b/src/main/java/com/example/umc9th/domain/review/dto/ReviewResponseDto.java index 0e45316..1a019a2 100644 --- a/src/main/java/com/example/umc9th/domain/review/dto/ReviewResponseDto.java +++ b/src/main/java/com/example/umc9th/domain/review/dto/ReviewResponseDto.java @@ -26,4 +26,14 @@ public static class Review{ private String reviewReplyContent; private String reviewReplyCreatedAt; } + + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class Created{ + private Long id; + private Long storeId; + private String createdAt; + } } diff --git a/src/main/java/com/example/umc9th/domain/review/service/ReviewService.java b/src/main/java/com/example/umc9th/domain/review/service/ReviewService.java index c126e67..d94191d 100644 --- a/src/main/java/com/example/umc9th/domain/review/service/ReviewService.java +++ b/src/main/java/com/example/umc9th/domain/review/service/ReviewService.java @@ -1,10 +1,11 @@ package com.example.umc9th.domain.review.service; +import com.example.umc9th.domain.review.dto.ReviewRequestDto; import com.example.umc9th.domain.review.dto.ReviewResponseDto; import com.example.umc9th.global.dto.CursorResponseDto; public interface ReviewService { - void createReview(String content, Long userId, Long storeId); + ReviewResponseDto.Created createReview(ReviewRequestDto.Create dto); CursorResponseDto getReviews(String storeName, Float minStar, diff --git a/src/main/java/com/example/umc9th/domain/review/service/ReviewServiceImpl.java b/src/main/java/com/example/umc9th/domain/review/service/ReviewServiceImpl.java index ae6559f..1afac4f 100644 --- a/src/main/java/com/example/umc9th/domain/review/service/ReviewServiceImpl.java +++ b/src/main/java/com/example/umc9th/domain/review/service/ReviewServiceImpl.java @@ -1,5 +1,6 @@ package com.example.umc9th.domain.review.service; +import com.example.umc9th.domain.review.dto.ReviewRequestDto; import com.example.umc9th.domain.review.dto.ReviewResponseDto; import com.example.umc9th.domain.review.entity.Review; import com.example.umc9th.domain.review.repository.ReviewPredicate; @@ -30,14 +31,21 @@ public class ReviewServiceImpl implements ReviewService{ @Override - public void createReview(String content, Long userId, Long storeId) { + public ReviewResponseDto.Created createReview(ReviewRequestDto.Create dto) { Review review = Review.builder() - .content(content) - .user(userRepository.getReferenceById(userId)) - .store(storeRepository.getReferenceById(storeId)) + .user(userRepository.getReferenceById(1L)) + .store(storeRepository.getReferenceById(dto.getStoreId())) + .content(dto.getContent()) + .star(dto.getStar()) + //.reviewImages(dto.getReviewImages()) todo: 이미지 리스트 .build(); reviewRepository.save(review); + return ReviewResponseDto.Created.builder() + .id(review.getId()) + .storeId(review.getStore().getId()) + .createdAt(review.getCreatedAt().toString()) + .build(); } @Override diff --git a/src/main/java/com/example/umc9th/global/annotation/ExistCategories.java b/src/main/java/com/example/umc9th/global/annotation/ExistCategories.java new file mode 100644 index 0000000..bb95fba --- /dev/null +++ b/src/main/java/com/example/umc9th/global/annotation/ExistCategories.java @@ -0,0 +1,23 @@ +package com.example.umc9th.global.annotation; + +import com.example.umc9th.global.validator.CategoryValidator; +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.*; + +/** + * @Documented : 사용자 정의 어노테이션 + * @Target : 어노테이션 적용 범위 + * @Retention : 어노테이션 생명주기 + */ +@Documented +@Constraint(validatedBy = CategoryValidator.class) +@Target( { ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER }) +@Retention(RetentionPolicy.RUNTIME) +public @interface ExistCategories { + //여기서 디폴트 메시지를 설정합니다. + String message() default "해당 음식이 존재하지 않습니다."; + Class[] groups() default {}; + Class[] payload() default {}; +} \ No newline at end of file diff --git a/src/main/java/com/example/umc9th/global/apiPayload/code/status/CategoryErrorCode.java b/src/main/java/com/example/umc9th/global/apiPayload/code/status/CategoryErrorCode.java new file mode 100644 index 0000000..b33a9c2 --- /dev/null +++ b/src/main/java/com/example/umc9th/global/apiPayload/code/status/CategoryErrorCode.java @@ -0,0 +1,16 @@ +package com.example.umc9th.global.apiPayload.code.status; + +import com.example.umc9th.global.apiPayload.code.BaseErrorCode; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum CategoryErrorCode implements BaseErrorCode { + CATEGORY_NOT_EXISTED(HttpStatus.NOT_FOUND,"CATEGORY_400","존재하지 않는 카테고리입니다."); + + private final HttpStatus httpStatus; + private final String code; + private final String message; +} diff --git a/src/main/java/com/example/umc9th/global/validator/CategoryValidator.java b/src/main/java/com/example/umc9th/global/validator/CategoryValidator.java new file mode 100644 index 0000000..3f75fc2 --- /dev/null +++ b/src/main/java/com/example/umc9th/global/validator/CategoryValidator.java @@ -0,0 +1,35 @@ +package com.example.umc9th.global.validator; + +import com.example.umc9th.domain.category.repository.CategoryRepository; +import com.example.umc9th.global.annotation.ExistCategories; +import com.example.umc9th.global.apiPayload.code.status.CategoryErrorCode; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +@RequiredArgsConstructor +public class CategoryValidator implements ConstraintValidator> { + + private final CategoryRepository categoryRepository; + + @Override + public void initialize(ExistCategories constraintAnnotation) { + ConstraintValidator.super.initialize(constraintAnnotation); + } + + @Override + public boolean isValid(List values, ConstraintValidatorContext context) { + boolean isValid = values.stream().allMatch(categoryRepository::existsById); + + if(!isValid){ + context.disableDefaultConstraintViolation(); // 디폴트 메세지 초기화 + context.buildConstraintViolationWithTemplate(CategoryErrorCode.CATEGORY_NOT_EXISTED.getMessage()) + .addConstraintViolation(); // 새로운 메세지로 덮어쓰기 + } + return isValid; + } +}