diff --git a/src/main/java/Journey/Together/domain/member/validator/MemberValidator.java b/src/main/java/Journey/Together/domain/member/validator/MemberValidator.java new file mode 100644 index 0000000..3df19e6 --- /dev/null +++ b/src/main/java/Journey/Together/domain/member/validator/MemberValidator.java @@ -0,0 +1,17 @@ +package Journey.Together.domain.member.validator; + +import Journey.Together.domain.member.repository.MemberRepository; +import Journey.Together.global.exception.ApplicationException; +import Journey.Together.global.exception.ErrorCode; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class MemberValidator { + private final MemberRepository memberRepository; + + public void validateExistsAndActive(String email) { + memberRepository.findMemberByEmailAndDeletedAtIsNull(email).orElseThrow(() -> new ApplicationException(ErrorCode.NOT_FOUND_EXCEPTION)); + } +} diff --git a/src/main/java/Journey/Together/domain/place/service/query/PlaceQueryService.java b/src/main/java/Journey/Together/domain/place/service/query/PlaceQueryService.java new file mode 100644 index 0000000..d1e74dc --- /dev/null +++ b/src/main/java/Journey/Together/domain/place/service/query/PlaceQueryService.java @@ -0,0 +1,42 @@ +package Journey.Together.domain.place.service.query; + +import Journey.Together.domain.place.entity.Place; +import Journey.Together.domain.place.repository.PlaceRepository; +import Journey.Together.domain.plan.dto.PlaceInfo; +import Journey.Together.domain.plan.dto.PlaceInfoPageRes; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class PlaceQueryService { + + private final PlaceRepository placeRepository; + + @Transactional(readOnly = true) + public PlaceInfoPageRes searchPlace(String word, Pageable page) { + Pageable pageable = PageRequest.of(page.getPageNumber(), page.getPageSize(), Sort.by("createdAt").descending()); + Page placePage = placeRepository.findAllByNameContainsOrderByCreatedAtDesc(word, pageable); + List placeInfoList = placePage.getContent().stream() + .map(PlaceInfo::of) + .collect(Collectors.toList()); + + return PlaceInfoPageRes.of( + placeInfoList, + placePage.getNumber(), + placePage.getSize(), + placePage.getTotalPages(), + placePage.isLast(), + placePage.getTotalElements() + ); + } +} + diff --git a/src/main/java/Journey/Together/domain/plan/controller/PlanController.java b/src/main/java/Journey/Together/domain/plan/controller/PlanController.java index 4ea9568..fe5f1ef 100644 --- a/src/main/java/Journey/Together/domain/plan/controller/PlanController.java +++ b/src/main/java/Journey/Together/domain/plan/controller/PlanController.java @@ -1,7 +1,11 @@ package Journey.Together.domain.plan.controller; +import Journey.Together.domain.place.service.query.PlaceQueryService; import Journey.Together.domain.plan.dto.*; +import Journey.Together.domain.plan.service.PlanReviewService; import Journey.Together.domain.plan.service.PlanService; +import Journey.Together.domain.plan.service.query.PlanQueryService; +import Journey.Together.domain.plan.service.query.PlanReviewQueryService; import Journey.Together.global.common.ApiResponse; import Journey.Together.global.exception.Success; import Journey.Together.global.security.PrincipalDetails; @@ -21,6 +25,11 @@ @Tag(name = "Plan", description = "일정 관련 API") public class PlanController { private final PlanService planService; + private final PlanReviewQueryService planReviewQueryService; + private final PlanReviewService planReviewService; + private final PlanQueryService planQueryService; + private final PlaceQueryService placeQueryService; + @PostMapping("") public ApiResponse savePlan(@AuthenticationPrincipal PrincipalDetails principalDetails,@RequestBody PlanReq planReq){ planService.savePlan(principalDetails.getMember(),planReq); @@ -51,40 +60,40 @@ public ApiResponse updatePlanIsPublic(@AuthenticationPrincipal PrincipalDetails @GetMapping("/search") public ApiResponse searchPlace(@RequestParam String word, @PageableDefault(size = 6,page = 0) Pageable pageable){ - return ApiResponse.success(Success.SEARCH_SUCCESS,planService.searchPlace(word,pageable)); + return ApiResponse.success(Success.SEARCH_SUCCESS,placeQueryService.searchPlace(word,pageable)); } @PostMapping("/review/{plan_id}") public ApiResponse savePlanReview(@AuthenticationPrincipal PrincipalDetails principalDetails, @PathVariable("plan_id")Long planId, @RequestPart(required = false) List images, @RequestPart PlanReviewReq planReviewReq){ - planService.savePlanReview(principalDetails.getMember(),planId,planReviewReq,images); + planReviewService.savePlanReview(principalDetails.getMember(),planId,planReviewReq,images); return ApiResponse.success(Success.CREATE_REVIEW_SUCCESS); } @GetMapping("/review/{plan_id}") public ApiResponse findPlanReview(@AuthenticationPrincipal PrincipalDetails principalDetails, @PathVariable("plan_id")Long planId){ - return ApiResponse.success(Success.GET_REVIEW_SUCCESS,planService.findPlanReview(principalDetails.getMember(),planId)); + return ApiResponse.success(Success.GET_REVIEW_SUCCESS,planReviewQueryService.getReview(principalDetails.getMember(),planId)); } @PatchMapping("/review/{review_id}") public ApiResponse updatePlanReview(@AuthenticationPrincipal PrincipalDetails principalDetails, @PathVariable("review_id")Long reviewId, @RequestPart(required = false) List images, @RequestPart(required = false) UpdatePlanReviewReq planReviewReq){ - planService.updatePlanReview(principalDetails.getMember(),reviewId,planReviewReq,images); + planReviewService.updatePlanReview(principalDetails.getMember(),reviewId,planReviewReq,images); return ApiResponse.success(Success.UPDATE_REVIEW_SUCCESS); } @DeleteMapping("/review/{review_id}") public ApiResponse deletePlanReview(@AuthenticationPrincipal PrincipalDetails principalDetails, @PathVariable("review_id")Long reviewId){ - planService.deletePlanReview(principalDetails.getMember(),reviewId); + planReviewService.deletePlanReview(principalDetails.getMember(),reviewId); return ApiResponse.success(Success.DELETE_PLAN_REVIEW_SUCCESS); } @GetMapping("/guest/review/{plan_id}") public ApiResponse findPlanReviewGuest(@PathVariable("plan_id")Long planId){ - return ApiResponse.success(Success.GET_REVIEW_SUCCESS,planService.findPlanReview(null,planId)); + return ApiResponse.success(Success.GET_REVIEW_SUCCESS,planReviewQueryService.getReview(null,planId)); } @GetMapping("/open") public ApiResponse findOpenPlans(@PageableDefault(size = 6) Pageable pageable){ - return ApiResponse.success(Success.SEARCH_SUCCESS,planService.findOpenPlans(pageable)); + return ApiResponse.success(Success.SEARCH_SUCCESS,planQueryService.findOpenPlans(pageable)); } @GetMapping("/detail/{plan_id}") @@ -99,16 +108,16 @@ public ApiResponse findPalnDetailInfo(@PathVariable("plan_id")Lon @GetMapping("/my") public ApiResponse> findMyPlans(@AuthenticationPrincipal PrincipalDetails principalDetails) { - return ApiResponse.success(Success.GET_MYPLAN_SUCCESS,planService.findMyPlans(principalDetails.getMember())); + return ApiResponse.success(Success.GET_MYPLAN_SUCCESS,planQueryService.findMyPlans(principalDetails.getMember())); } @GetMapping("/my/not-complete") public ApiResponse findNotComplete(@AuthenticationPrincipal PrincipalDetails principalDetails,@PageableDefault(size = 6,page = 0) Pageable pageable){ - return ApiResponse.success(Success.SEARCH_SUCCESS,planService.findIsCompelete(principalDetails.getMember(),pageable,false)); + return ApiResponse.success(Success.SEARCH_SUCCESS,planQueryService.findIsCompelete(principalDetails.getMember(),pageable,false)); } @GetMapping("/my/complete") public ApiResponse findComplete(@AuthenticationPrincipal PrincipalDetails principalDetails,@PageableDefault(size = 6) Pageable pageable){ - return ApiResponse.success(Success.SEARCH_SUCCESS,planService.findIsCompelete(principalDetails.getMember(),pageable,true)); + return ApiResponse.success(Success.SEARCH_SUCCESS,planQueryService.findIsCompelete(principalDetails.getMember(),pageable,true)); } } diff --git a/src/main/java/Journey/Together/domain/plan/service/PlanPlaceService.java b/src/main/java/Journey/Together/domain/plan/service/PlanPlaceService.java new file mode 100644 index 0000000..cdf540f --- /dev/null +++ b/src/main/java/Journey/Together/domain/plan/service/PlanPlaceService.java @@ -0,0 +1,35 @@ +package Journey.Together.domain.plan.service; + +import Journey.Together.domain.member.entity.Member; +import Journey.Together.domain.place.entity.Place; +import Journey.Together.domain.place.repository.PlaceRepository; +import Journey.Together.domain.plan.dto.DailyPlace; +import Journey.Together.domain.plan.entity.Day; +import Journey.Together.domain.plan.entity.Plan; +import Journey.Together.domain.plan.service.factory.PlanFactory; +import Journey.Together.domain.plan.repository.DayRepository; +import Journey.Together.global.exception.ApplicationException; +import Journey.Together.global.exception.ErrorCode; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class PlanPlaceService { + private final PlaceRepository placeRepository; + private final DayRepository dayRepository; + private final PlanFactory planFactory; + + public void savePlacesByDay(List places, Member member, Plan plan) { + for (DailyPlace dailyPlace : places) { + for (Long placeId : dailyPlace.places()) { + Place place = placeRepository.findById(placeId) + .orElseThrow(() -> new ApplicationException(ErrorCode.NOT_FOUND_EXCEPTION)); + Day day = planFactory.createDay(member, plan, place, dailyPlace.date()); + dayRepository.save(day); + } + } + } +} diff --git a/src/main/java/Journey/Together/domain/plan/service/PlanReviewImageService.java b/src/main/java/Journey/Together/domain/plan/service/PlanReviewImageService.java new file mode 100644 index 0000000..a3eb2ba --- /dev/null +++ b/src/main/java/Journey/Together/domain/plan/service/PlanReviewImageService.java @@ -0,0 +1,56 @@ +package Journey.Together.domain.plan.service; + +import Journey.Together.domain.plan.entity.PlanReview; +import Journey.Together.domain.plan.entity.PlanReviewImage; +import Journey.Together.domain.plan.repository.PlanReviewImageRepository; +import Journey.Together.domain.plan.service.factory.PlanReviewImageFactory; +import Journey.Together.global.util.S3Client; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class PlanReviewImageService { + private final S3Client s3Client; + private final PlanReviewImageRepository planReviewImageRepository; + private final PlanReviewImageFactory planReviewImageFactory; + + public void uploadAndSaveImages(List images, PlanReview planReview, String profileUuid) { + for (MultipartFile file : images) { + String uuid = UUID.randomUUID().toString(); + String url = s3Client.upload(file, profileUuid, uuid); + PlanReviewImage image = planReviewImageFactory.createPlanReviewImage(planReview,url); + planReviewImageRepository.save(image); + } + } + + public List getImageUrls(PlanReview planReview) { + return planReviewImageRepository.findAllByPlanReviewAndDeletedAtIsNull(planReview) + .stream() + .map(PlanReviewImage::getImageUrl) + .collect(Collectors.toList()); + } + + public void deleteImages(List deleteImgUrls) { + for (String url : deleteImgUrls) { + planReviewImageRepository.deletePlanReviewImageByImageUrl(url); + s3Client.delete(StringUtils.substringAfter(url, "com/")); + } + } + + public void deleteAllImages(PlanReview planReview) { + List images = planReviewImageRepository.findAllByPlanReviewAndDeletedAtIsNull(planReview); + + for (PlanReviewImage image : images) { + String filename = image.getImageUrl().replace(s3Client.baseUrl(), ""); + s3Client.delete(filename); // S3에서 이미지 삭제 + planReviewImageRepository.deletePlanReviewImageByPlanReviewImageId(image.getPlanReviewImageId()); + } + } +} diff --git a/src/main/java/Journey/Together/domain/plan/service/PlanReviewService.java b/src/main/java/Journey/Together/domain/plan/service/PlanReviewService.java new file mode 100644 index 0000000..d908257 --- /dev/null +++ b/src/main/java/Journey/Together/domain/plan/service/PlanReviewService.java @@ -0,0 +1,81 @@ +package Journey.Together.domain.plan.service; + +import Journey.Together.domain.member.entity.Member; +import Journey.Together.domain.plan.dto.PlanReviewReq; +import Journey.Together.domain.plan.dto.UpdatePlanReviewReq; +import Journey.Together.domain.plan.entity.Plan; +import Journey.Together.domain.plan.entity.PlanReview; +import Journey.Together.domain.plan.repository.PlanRepository; +import Journey.Together.domain.plan.repository.PlanReviewRepository; +import Journey.Together.domain.plan.service.factory.PlanReviewFactory; +import Journey.Together.domain.plan.service.modifier.PlanModifier; +import Journey.Together.domain.plan.service.modifier.PlanReviewModifier; +import Journey.Together.domain.plan.service.validator.PlanReviewValidator; +import Journey.Together.domain.plan.service.validator.PlanValidator; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import java.time.LocalDate; +import java.util.List; + +@Service +@RequiredArgsConstructor +public class PlanReviewService { + private final PlanRepository planRepository; + private final PlanReviewRepository planReviewRepository; + private final PlanReviewImageService planReviewImageService; + private final PlanReviewModifier planReviewModifier; + private final PlanModifier planModifier; + private final PlanReviewValidator planReviewValidator; + private final PlanValidator planValidator; + private final PlanReviewFactory planReviewFactory; + + @Transactional + public void savePlanReview(Member member, Long planId, PlanReviewReq planReviewReq, List images) { + // Validation + Plan plan = planRepository.findPlanByMemberAndPlanIdAndEndDateIsBeforeAndDeletedAtIsNull(member, planId, LocalDate.now()); + planValidator.validateExists(plan); + planReviewValidator.validateExists(plan); + + //Business + PlanReview planReview = planReviewFactory.createPlanReview(member, plan, planReviewReq); + planReviewRepository.save(planReview); + + if (images != null) { + planReviewImageService.uploadAndSaveImages(images, planReview, member.getProfileUuid()); + } + + planModifier.updateIsPublic(plan, planReviewReq.isPublic()); + } + + @Transactional + public void updatePlanReview(Member member, Long reviewId, UpdatePlanReviewReq req, List images) { + PlanReview planReview = planReviewRepository.findPlanReviewByPlanReviewIdAndDeletedAtIsNull(reviewId); + planReviewValidator.validateWriter(member, planReview); + + // 이미지 업로드 + if (images != null && !images.isEmpty()) { + planReviewImageService.uploadAndSaveImages(images, planReview, member.getProfileUuid()); + } + + // 이미지 삭제 + if (req.deleteImgUrls() != null) { + planReviewImageService.deleteImages(req.deleteImgUrls()); + } + + // 내용 수정 + planReviewModifier.update(planReview, req); + } + + @Transactional + public void deletePlanReview(Member member, Long reviewId) { + PlanReview planReview = planReviewRepository.findPlanReviewByPlanReviewIdAndDeletedAtIsNull(reviewId); + planReviewValidator.validateWriter(member, planReview); + + planReviewImageService.deleteAllImages(planReview); // 이미지 삭제 책임 위임 + + planReviewRepository.deletePlanReviewByPlanReviewId(planReview.getPlanReviewId()); + } +} diff --git a/src/main/java/Journey/Together/domain/plan/service/PlanService.java b/src/main/java/Journey/Together/domain/plan/service/PlanService.java index 7c4a214..76b0975 100644 --- a/src/main/java/Journey/Together/domain/plan/service/PlanService.java +++ b/src/main/java/Journey/Together/domain/plan/service/PlanService.java @@ -1,430 +1,116 @@ package Journey.Together.domain.plan.service; -import Journey.Together.domain.place.entity.PlaceReviewImg; -import Journey.Together.domain.plan.dto.*; -import Journey.Together.domain.plan.entity.Day; +import Journey.Together.domain.member.entity.Member; +import Journey.Together.domain.member.validator.MemberValidator; +import Journey.Together.domain.place.repository.PlaceRepository; +import Journey.Together.domain.plan.dto.PlanDetailRes; +import Journey.Together.domain.plan.dto.PlanReq; +import Journey.Together.domain.plan.dto.PlanRes; import Journey.Together.domain.plan.entity.Plan; -import Journey.Together.domain.plan.entity.PlanReview; -import Journey.Together.domain.plan.entity.PlanReviewImage; -import Journey.Together.domain.plan.repository.DayRepository; import Journey.Together.domain.plan.repository.PlanRepository; import Journey.Together.domain.plan.repository.PlanReviewImageRepository; import Journey.Together.domain.plan.repository.PlanReviewRepository; -import Journey.Together.domain.member.entity.Member; -import Journey.Together.domain.member.repository.MemberRepository; -import Journey.Together.domain.place.entity.Place; -import Journey.Together.domain.place.repository.DisabilityPlaceCategoryRepository; -import Journey.Together.domain.place.repository.PlaceRepository; -import Journey.Together.global.exception.ApplicationException; -import Journey.Together.global.exception.ErrorCode; +import Journey.Together.domain.plan.service.deleter.PlanDeleter; +import Journey.Together.domain.plan.service.factory.PlanFactory; +import Journey.Together.domain.plan.service.factory.PlanReviewFactory; +import Journey.Together.domain.plan.service.modifier.PlanModifier; +import Journey.Together.domain.plan.service.query.PlanDetailQueryService; +import Journey.Together.domain.plan.service.query.PlanQueryService; +import Journey.Together.domain.plan.service.validator.PlanReviewValidator; +import Journey.Together.domain.plan.service.validator.PlanValidator; import Journey.Together.global.util.S3Client; import lombok.RequiredArgsConstructor; -import org.apache.commons.lang3.StringUtils; -import org.elasticsearch.monitor.os.OsStats; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.multipart.MultipartFile; import java.time.LocalDate; -import java.time.Period; -import java.time.temporal.ChronoUnit; -import java.util.*; -import java.util.stream.Collectors; @Service @Transactional(readOnly = true) @RequiredArgsConstructor public class PlanService { - private final MemberRepository memberRepository; private final PlanRepository planRepository; - private final DayRepository dayRepository; private final PlaceRepository placeRepository; private final PlanReviewRepository planReviewRepository; private final PlanReviewImageRepository planReviewImageRepository; - private final DisabilityPlaceCategoryRepository disabilityPlaceCategoryRepository; - private final S3Client s3Client; - @Transactional - public void savePlan(Member member, PlanReq planReq){ - // Validation - memberRepository.findMemberByEmailAndDeletedAtIsNull(member.getEmail()).orElseThrow(()->new ApplicationException(ErrorCode.NOT_FOUND_EXCEPTION)); - //Buisness - Plan plan = Plan.builder() - .member(member) - .title(planReq.title()) - .startDate(planReq.startDate()) - .endDate(planReq.endDate()) - .isPublic(false) - .build(); - planRepository.save(plan); - //날짜별 장소 정보 저장 - savePlaceByDay(planReq.dailyplace(),member,plan); - } + private final PlanPlaceService planPlaceService; + private final PlanReviewImageService planReviewImageService; - @Transactional - public void updatePlan(Member member,Long planId,PlanReq planReq){ - // Validation - Plan plan = planRepository.findPlanByMemberAndPlanIdAndDeletedAtIsNull(member,planId); - if(plan == null){ - throw new ApplicationException(ErrorCode.NOT_FOUND_EXCEPTION); - } - //Business - dayRepository.deleteAllByMemberAndPlan(member,plan); + private final PlanQueryService planQueryService; + private final PlanDetailQueryService planDetailQueryService; - plan.updatePlan(planReq.title(),planReq.startDate(),planReq.endDate()); - planRepository.save(plan); + private final PlanFactory planFactory; + private final PlanReviewFactory planReviewFactory; + private final PlanModifier planModifier; + private final PlanDeleter planDeleter; - savePlaceByDay(planReq.dailyplace(),member,plan); + private final PlanValidator planValidator; + private final PlanReviewValidator planReviewValidator; + private final MemberValidator memberValidator; + + private final S3Client s3Client; - } - @Transactional - public PlanRes findPlan(Member member,Long planId) { - // Validation - Plan plan = planRepository.findPlanByMemberAndPlanIdAndDeletedAtIsNull(member, planId); - if (plan == null) { - throw new ApplicationException(ErrorCode.NOT_FOUND_EXCEPTION); - } - //Buisness - String image = getPlaceFirstImage(plan); - //Response - return PlanRes.of(plan,image,null,null); - } @Transactional - public void deletePlan(Member member,Long planId){ + public void savePlan(Member member, PlanReq planReq) { // Validation - Plan plan = planRepository.findPlanByMemberAndPlanIdAndDeletedAtIsNull(member,planId); - if(plan == null){ - throw new ApplicationException(ErrorCode.NOT_FOUND_EXCEPTION); - } - PlanReview planReview = planReviewRepository.findPlanReviewByPlanAndDeletedAtIsNull(plan); + memberValidator.validateExistsAndActive(member.getEmail()); //Buisness - dayRepository.deleteAllByMemberAndPlan(member,plan); - if(planReview!=null){ - deletePlanReview(member,planReview.getPlanReviewId()); - } - planRepository.deletePlanByPlanId(planId); + Plan plan = planFactory.createPlan(member, planReq); + planRepository.save(plan); + //날짜별 장소 정보 저장 + planPlaceService.savePlacesByDay(planReq.dailyplace(), member, plan); } @Transactional - public PlanDetailRes findPlanDetail(Member member, Long planId){ + public void updatePlan(Member member, Long planId, PlanReq planReq) { // Validation - Plan plan = planRepository.findPlanByPlanIdAndDeletedAtIsNull(planId); - if(plan == null){ - throw new ApplicationException(ErrorCode.NOT_FOUND_EXCEPTION); - } - - //Buisness - boolean isWriter; - List dailyLists = new ArrayList<>(); - List dayList = dayRepository.findAllByMemberAndPlanOrderByDateAsc(plan.getMember(),plan); - List imageUrls = getPlaceImageList(plan); - - Map> groupedByDate = dayList.stream() - .collect(Collectors.groupingBy(Day::getDate)); - - LocalDate startDate = plan.getStartDate(); - LocalDate endDate = plan.getEndDate(); - - // startDate부터 endDate까지의 날짜들을 순회 - for (LocalDate date = startDate; !date.isAfter(endDate); date = date.plusDays(1)) { - List days = groupedByDate.get(date); - List dailyPlaceInfoList = new ArrayList<>(); - if (days != null) { - for (Day day : days) { - List disabilityCategoryList = disabilityPlaceCategoryRepository.findDisabilityCategoryIds(day.getPlace().getId()); - DailyPlaceInfo dailyPlaceInfo = DailyPlaceInfo.of(day.getPlace(), disabilityCategoryList); - dailyPlaceInfoList.add(dailyPlaceInfo); - } - } - DailyList dailyList = DailyList.of(date, dailyPlaceInfoList); - dailyLists.add(dailyList); - } - - // 날짜 순으로 정렬 - dailyLists.sort(Comparator.comparing(DailyList::getDate)); - - if (member ==null){ - isWriter = false; - }else { - isWriter = plan.getMember().getMemberId().equals(member.getMemberId()); - } - String remainDate = isBetween(plan.getStartDate(),plan.getEndDate()); + Plan plan = planRepository.findPlanByMemberAndPlanIdAndDeletedAtIsNull(member, planId); + planValidator.validateExists(plan); + //Business + planModifier.modifyPlan(member, plan, planReq); - //Response - return PlanDetailRes.of(imageUrls,dailyLists,isWriter,plan,remainDate); + planPlaceService.savePlacesByDay(planReq.dailyplace(), member, plan); } @Transactional - public Boolean updatePlanIsPublic(Member member,Long planId){ + public PlanRes findPlan(Member member, Long planId) { // Validation - Plan plan = planRepository.findPlanByMemberAndPlanIdAndEndDateIsBeforeAndDeletedAtIsNull(member,planId,LocalDate.now()); - if(plan == null){ - throw new ApplicationException(ErrorCode.NOT_FOUND_EXCEPTION); - } - if(!Objects.equals(plan.getMember().getMemberId(), member.getMemberId())){ - throw new ApplicationException(ErrorCode.UNAUTHORIZED_EXCEPTION); - } - - //Business - plan.setIsPublic(!plan.getIsPublic()); - + Plan plan = planRepository.findPlanByMemberAndPlanIdAndDeletedAtIsNull(member, planId); + planValidator.validateExists(plan); + //Buisness + String image = planQueryService.getFirstPlaceImageOfPlan(plan); //Response - return plan.getIsPublic(); + return PlanRes.of(plan, image, null, null); } @Transactional - public void savePlanReview(Member member, Long planId, PlanReviewReq planReviewReq,List images){ + public void deletePlan(Member member, Long planId) { // Validation - Plan plan = planRepository.findPlanByMemberAndPlanIdAndEndDateIsBeforeAndDeletedAtIsNull(member,planId,LocalDate.now()); - if(plan == null){ - throw new ApplicationException(ErrorCode.NOT_FOUND_EXCEPTION); - } - if(planReviewRepository.existsAllByPlanAndDeletedAtIsNull(plan)){ - throw new ApplicationException(ErrorCode.ALREADY_EXIST_EXCEPTION); - } - //Business - PlanReview planReview = PlanReview.builder() - .member(member) - .grade(planReviewReq.grade()) - .content(planReviewReq.content()) - .plan(plan) - .build(); - planReviewRepository.save(planReview); - - if(images!=null){ - for(MultipartFile file : images){ - String uuid = UUID.randomUUID().toString(); - String url = s3Client.upload(file,member.getProfileUuid(),uuid); - PlanReviewImage planReviewImage = PlanReviewImage.builder() - .planReview(planReview) - .imageUrl(url) - .build(); - planReviewImageRepository.save(planReviewImage); - } - } + Plan plan = planRepository.findPlanByMemberAndPlanIdAndDeletedAtIsNull(member, planId); + planValidator.validateExists(plan); - plan.setIsPublic(planReviewReq.isPublic()); + planDeleter.delete(member, plan); } @Transactional - public PlanReviewRes findPlanReview(Member member,long planId){ + public PlanDetailRes findPlanDetail(Member member, Long planId) { // Validation Plan plan = planRepository.findPlanByPlanIdAndDeletedAtIsNull(planId); - if(plan == null){ - throw new ApplicationException(ErrorCode.NOT_FOUND_EXCEPTION); - } - PlanReview planReview = planReviewRepository.findPlanReview(plan); - //Buisness - boolean isWriter; - if (member ==null){ - isWriter = false; - }else { - isWriter = plan.getMember().getMemberId().equals(member.getMemberId()); - } - String profileUrl = s3Client.baseUrl()+plan.getMember().getProfileUuid()+"/profile_"+plan.getMember().getProfileUuid(); - if(planReview==null){ - //리뷰가 없을 경우 - return PlanReviewRes.of(null,null,null,isWriter,false,null,profileUrl,null); - }else { - List imageList = getReviewImageList(planReview); - //리뷰가 있고 신고 없을 경우 - if(planReview.getReport()==null){ - return PlanReviewRes.of(planReview.getPlanReviewId(),planReview.getContent(),planReview.getGrade(),isWriter,true,imageList,profileUrl,null); - } - //리뷰가 있고 신고 비승인 - return PlanReviewRes.of(planReview.getPlanReviewId(),planReview.getContent(),planReview.getGrade(),isWriter,true,imageList,profileUrl,planReview.getReport()); - } + planValidator.validateExists(plan); + return planDetailQueryService.getDetail(member, plan); } @Transactional - public void updatePlanReview(Member member, Long reviewId, UpdatePlanReviewReq updatePlanReviewReq, List images) { + public Boolean updatePlanIsPublic(Member member, Long planId) { // Validation - PlanReview planReview = planReviewRepository.findPlanReviewByPlanReviewIdAndDeletedAtIsNull(reviewId); - if(!Objects.equals(planReview.getPlan().getMember().getMemberId(), member.getMemberId())){ - throw new ApplicationException(ErrorCode.UNAUTHORIZED_EXCEPTION); - } - //Business - if (images != null) { - try { - for(MultipartFile file : images) { - String uuid = UUID.randomUUID().toString(); - String url = s3Client.upload(file,member.getProfileUuid(),uuid); - PlanReviewImage planReviewImage = PlanReviewImage.builder() - .planReview(planReview) - .imageUrl(url) - .build(); - planReviewImageRepository.save(planReviewImage); - } - } catch (RuntimeException e) { - throw new RuntimeException(e.getMessage()); - } - } - if (updatePlanReviewReq != null) { - if (updatePlanReviewReq.grade() != null) { - planReview.setGrade(updatePlanReviewReq.grade()); - } - if (updatePlanReviewReq.content() != null) { - planReview.setContent(updatePlanReviewReq.content()); - } - if (updatePlanReviewReq.deleteImgUrls() != null) { - updatePlanReviewReq.deleteImgUrls().forEach( - deleteImg -> { - planReviewImageRepository.deletePlanReviewImageByImageUrl(deleteImg); - s3Client.delete(StringUtils.substringAfter(deleteImg, "com/")); - } - ); - } - } - - } - - @Transactional - public void deletePlanReview(Member member,Long reviewId){ - //Vailda - PlanReview planReview = planReviewRepository.findPlanReviewByPlanReviewIdAndDeletedAtIsNull(reviewId); - if(!Objects.equals(planReview.getPlan().getMember().getMemberId(), member.getMemberId())){ - throw new ApplicationException(ErrorCode.UNAUTHORIZED_EXCEPTION); - } - List planReviewImageList = planReviewImageRepository.findAllByPlanReviewAndDeletedAtIsNull(planReview); - if(planReviewImageList!=null){ - for(PlanReviewImage planReviewImage : planReviewImageList){ - String filename = planReviewImage.getImageUrl().replace(s3Client.baseUrl(), ""); - s3Client.delete(filename); - planReviewImageRepository.deletePlanReviewImageByPlanReviewImageId(planReviewImage.getPlanReviewImageId()); - } - } - planReviewRepository.deletePlanReviewByPlanReviewId(planReview.getPlanReviewId()); - } - - @Transactional - public List findMyPlans(Member member) { - //Vaildation - List list = planRepository.findAllByMemberAndDeletedAtIsNull(member); - //Business - List top3list = list.stream() - .sorted(Comparator.comparingLong(plan -> Math.abs(ChronoUnit.DAYS.between(LocalDate.now(), plan.getStartDate())))) - .limit(3) - .toList(); - List myPlanResList = new ArrayList<>(); - for(Plan plan : top3list){ - String image = getPlaceFirstImage(plan); - String remainDate = null; - Boolean hasReview = null; - if (LocalDate.now().isAfter(plan.getEndDate())){ - hasReview = planReviewRepository.existsAllByPlanAndDeletedAtIsNull(plan); - }else if ((LocalDate.now().isEqual(plan.getStartDate()) || LocalDate.now().isAfter(plan.getStartDate())) && (LocalDate.now().isEqual(plan.getEndDate()) || LocalDate.now().isBefore(plan.getEndDate()))){ - remainDate="D-DAY"; - }else if (LocalDate.now().isBefore(plan.getStartDate())){ - Period period = Period.between(LocalDate.now(),plan.getStartDate()); - remainDate="D-"+period.getDays(); - } - MyPlanRes myPlanRes = MyPlanRes.of(plan,image,remainDate,hasReview); - myPlanResList.add(myPlanRes); - } + Plan plan = planRepository.findPlanByMemberAndPlanIdAndEndDateIsBeforeAndDeletedAtIsNull(member, planId, LocalDate.now()); + planValidator.validateExists(plan); + planValidator.validateWriter(member, plan); //Response - return myPlanResList; - } - - @Transactional - public PlaceInfoPageRes searchPlace(String word, Pageable page){ - Pageable pageable = PageRequest.of(page.getPageNumber(), page.getPageSize(), Sort.by("createdAt").descending()); - Page placePage = placeRepository.findAllByNameContainsOrderByCreatedAtDesc(word,pageable); - List placeInfoList = placePage.getContent().stream() - .map(PlaceInfo::of) - .collect(Collectors.toList()); - return PlaceInfoPageRes.of(placeInfoList, placePage.getNumber(), placePage.getSize(), placePage.getTotalPages(), placePage.isLast(),placePage.getTotalElements()); - } - - @Transactional - public PlanPageRes findIsCompelete(Member member, Pageable page, Boolean compelete){ - Pageable pageable = PageRequest.of(page.getPageNumber(), page.getPageSize(), Sort.by("createdAt").descending()); - Page planPage; - List planResList; - if(compelete){ - planPage = planRepository.findAllByMemberAndEndDateBeforeAndDeletedAtIsNull(member,LocalDate.now(),pageable); - planResList = planPage.getContent().stream() - .map(plan -> PlanRes.of(plan,getPlaceFirstImage(plan),null,planReviewRepository.existsAllByPlanAndReportFilter(plan))) - .collect(Collectors.toList()); - }else { - planPage = planRepository.findAllByMemberAndEndDateGreaterThanEqualAndDeletedAtIsNull(member,LocalDate.now(),pageable); - planResList = planPage.getContent().stream() - .map(plan -> PlanRes.of(plan,getPlaceFirstImage(plan),isBetween(plan.getStartDate(),plan.getEndDate()),null)) - .collect(Collectors.toList()); - } - - return PlanPageRes.of(planResList,planPage.getNumber(),planPage.getSize(),planPage.getTotalPages(),planPage.isLast()); - } - - @Transactional - public OpenPlanPageRes findOpenPlans(Pageable page){ - Pageable pageable = PageRequest.of(page.getPageNumber(), page.getPageSize(), Sort.by("createdAt").descending()); - Page planPage = planRepository.findOpenPlans(LocalDate.now(),pageable); - List openPlanResList = planPage.getContent().stream() - .map(plan -> OpenPlanRes.of(plan, s3Client.baseUrl()+plan.getMember().getProfileUuid()+"/profile_"+plan.getMember().getProfileUuid(),getPlaceFirstImage(plan))) - .collect(Collectors.toList()); - return OpenPlanPageRes.of(openPlanResList,planPage.getNumber(),planPage.getSize(),planPage.getTotalPages(),planPage.isLast()); - } - - public void savePlaceByDay(List places, Member member,Plan plan){ - for(DailyPlace dailyPlace : places){ - for(Long placeId : dailyPlace.places()){ - Place place = placeRepository.findById(placeId).orElseThrow(()->new ApplicationException(ErrorCode.NOT_FOUND_EXCEPTION)); - Day day = Day.builder() - .member(member) - .plan(plan) - .place(place) - .date(dailyPlace.date()) - .build(); - dayRepository.save(day); - } - } - } - - public String isBetween(LocalDate startDate,LocalDate endDate){ - if ((LocalDate.now().isEqual(startDate) || LocalDate.now().isAfter(startDate) && (LocalDate.now().isEqual(endDate) || LocalDate.now().isBefore(endDate)))){ - return "D-DAY"; - }else if (LocalDate.now().isBefore(startDate)){ - Period period = Period.between(LocalDate.now(),startDate); - return "D-"+ period.getDays(); - } - return null; - } - - public String getPlaceFirstImage(Plan plan){ - List dayList = dayRepository.findByPlanOrderByCreatedAtDesc(plan); - if(!dayList.isEmpty()){ - String placeImageUrl = dayList.get(0).getPlace().getFirstImg(); - if(placeImageUrl.isEmpty()){ - return null; - } - return dayList.get(0).getPlace().getFirstImg(); - } - return null; - } - - public List getPlaceImageList(Plan plan){ - List dayList = dayRepository.findByPlanOrderByCreatedAtDesc(plan); - List list = new ArrayList<>(); - if(!dayList.isEmpty()){ - dayList.forEach(day ->{list.add(day.getPlace().getFirstImg());}); - return list; - } - return null; - } - - public List getReviewImageList(PlanReview planReview){ - List list = new ArrayList<>(); - List planReviewImageList = planReviewImageRepository.findAllByPlanReviewAndDeletedAtIsNull(planReview); - for(PlanReviewImage planReviewImage : planReviewImageList){ - list.add(planReviewImage.getImageUrl()); - } - return list; + return planModifier.togglePublic(plan); } } diff --git a/src/main/java/Journey/Together/domain/plan/service/deleter/PlanDeleter.java b/src/main/java/Journey/Together/domain/plan/service/deleter/PlanDeleter.java new file mode 100644 index 0000000..74c10c6 --- /dev/null +++ b/src/main/java/Journey/Together/domain/plan/service/deleter/PlanDeleter.java @@ -0,0 +1,30 @@ +package Journey.Together.domain.plan.service.deleter; + +import Journey.Together.domain.member.entity.Member; +import Journey.Together.domain.plan.entity.Plan; +import Journey.Together.domain.plan.entity.PlanReview; +import Journey.Together.domain.plan.repository.DayRepository; +import Journey.Together.domain.plan.repository.PlanRepository; +import Journey.Together.domain.plan.repository.PlanReviewRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class PlanDeleter { + + private final DayRepository dayRepository; + private final PlanReviewRepository planReviewRepository; + private final PlanRepository planRepository; + + public void delete(Member member, Plan plan) { + dayRepository.deleteAllByMemberAndPlan(member, plan); + + PlanReview planReview = planReviewRepository.findPlanReviewByPlanAndDeletedAtIsNull(plan); + if (planReview != null) { + planReviewRepository.delete(planReview); + } + + planRepository.deletePlanByPlanId(plan.getPlanId()); + } +} diff --git a/src/main/java/Journey/Together/domain/plan/service/factory/PlanFactory.java b/src/main/java/Journey/Together/domain/plan/service/factory/PlanFactory.java new file mode 100644 index 0000000..093a06c --- /dev/null +++ b/src/main/java/Journey/Together/domain/plan/service/factory/PlanFactory.java @@ -0,0 +1,32 @@ +package Journey.Together.domain.plan.service.factory; + +import Journey.Together.domain.member.entity.Member; +import Journey.Together.domain.place.entity.Place; +import Journey.Together.domain.plan.dto.PlanReq; +import Journey.Together.domain.plan.entity.Day; +import Journey.Together.domain.plan.entity.Plan; +import org.springframework.stereotype.Component; + +import java.time.LocalDate; + +@Component +public class PlanFactory { + public Plan createPlan(Member member, PlanReq planReq) { + return Plan.builder() + .member(member) + .title(planReq.title()) + .startDate(planReq.startDate()) + .endDate(planReq.endDate()) + .isPublic(false) + .build(); + } + + public Day createDay(Member member, Plan plan, Place place, LocalDate date) { + return Day.builder() + .member(member) + .plan(plan) + .place(place) + .date(date) + .build(); + } +} diff --git a/src/main/java/Journey/Together/domain/plan/service/factory/PlanReviewFactory.java b/src/main/java/Journey/Together/domain/plan/service/factory/PlanReviewFactory.java new file mode 100644 index 0000000..f02fa71 --- /dev/null +++ b/src/main/java/Journey/Together/domain/plan/service/factory/PlanReviewFactory.java @@ -0,0 +1,19 @@ +package Journey.Together.domain.plan.service.factory; + +import Journey.Together.domain.member.entity.Member; +import Journey.Together.domain.plan.dto.PlanReviewReq; +import Journey.Together.domain.plan.entity.Plan; +import Journey.Together.domain.plan.entity.PlanReview; +import org.springframework.stereotype.Component; + +@Component +public class PlanReviewFactory { + public PlanReview createPlanReview(Member member, Plan plan, PlanReviewReq planReviewReq) { + return PlanReview.builder() + .member(member) + .grade(planReviewReq.grade()) + .content(planReviewReq.content()) + .plan(plan) + .build(); + } +} diff --git a/src/main/java/Journey/Together/domain/plan/service/factory/PlanReviewImageFactory.java b/src/main/java/Journey/Together/domain/plan/service/factory/PlanReviewImageFactory.java new file mode 100644 index 0000000..3c3fa6e --- /dev/null +++ b/src/main/java/Journey/Together/domain/plan/service/factory/PlanReviewImageFactory.java @@ -0,0 +1,15 @@ +package Journey.Together.domain.plan.service.factory; + +import Journey.Together.domain.plan.entity.PlanReview; +import Journey.Together.domain.plan.entity.PlanReviewImage; +import org.springframework.stereotype.Component; + +@Component +public class PlanReviewImageFactory { + public PlanReviewImage createPlanReviewImage(PlanReview planReview, String url) { + return PlanReviewImage.builder() + .planReview(planReview) + .imageUrl(url) + .build(); + } +} diff --git a/src/main/java/Journey/Together/domain/plan/service/modifier/PlanModifier.java b/src/main/java/Journey/Together/domain/plan/service/modifier/PlanModifier.java new file mode 100644 index 0000000..c18894e --- /dev/null +++ b/src/main/java/Journey/Together/domain/plan/service/modifier/PlanModifier.java @@ -0,0 +1,36 @@ +package Journey.Together.domain.plan.service.modifier; + +import Journey.Together.domain.member.entity.Member; +import Journey.Together.domain.plan.dto.PlanReq; +import Journey.Together.domain.plan.entity.Plan; +import Journey.Together.domain.plan.repository.DayRepository; +import Journey.Together.domain.plan.repository.PlanRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class PlanModifier { + private final DayRepository dayRepository; + private final PlanRepository planRepository; + + public void modifyPlan(Member member, Plan plan, PlanReq planReq) { + // 기존 Day 삭제 + dayRepository.deleteAllByMemberAndPlan(member, plan); + + // Plan 수정 + plan.updatePlan(planReq.title(), planReq.startDate(), planReq.endDate()); + + // save는 변경 감지 안 될 경우에만 필요 + planRepository.save(plan); + } + + public Boolean togglePublic(Plan plan) { + plan.setIsPublic(!plan.getIsPublic()); + return plan.getIsPublic(); + } + + public void updateIsPublic(Plan plan, boolean isPublic) { + plan.setIsPublic(isPublic); + } +} diff --git a/src/main/java/Journey/Together/domain/plan/service/modifier/PlanReviewModifier.java b/src/main/java/Journey/Together/domain/plan/service/modifier/PlanReviewModifier.java new file mode 100644 index 0000000..81be4e3 --- /dev/null +++ b/src/main/java/Journey/Together/domain/plan/service/modifier/PlanReviewModifier.java @@ -0,0 +1,18 @@ +package Journey.Together.domain.plan.service.modifier; + +import Journey.Together.domain.plan.dto.UpdatePlanReviewReq; +import Journey.Together.domain.plan.entity.PlanReview; +import org.springframework.stereotype.Component; + +@Component +public class PlanReviewModifier { + + public void update(PlanReview review, UpdatePlanReviewReq req) { + if (req.grade() != null) { + review.setGrade(req.grade()); + } + if (req.content() != null) { + review.setContent(req.content()); + } + } +} diff --git a/src/main/java/Journey/Together/domain/plan/service/query/PlanDetailQueryService.java b/src/main/java/Journey/Together/domain/plan/service/query/PlanDetailQueryService.java new file mode 100644 index 0000000..1e69cda --- /dev/null +++ b/src/main/java/Journey/Together/domain/plan/service/query/PlanDetailQueryService.java @@ -0,0 +1,62 @@ +package Journey.Together.domain.plan.service.query; + +import Journey.Together.domain.member.entity.Member; +import Journey.Together.domain.place.repository.DisabilityPlaceCategoryRepository; +import Journey.Together.domain.plan.dto.DailyList; +import Journey.Together.domain.plan.dto.DailyPlaceInfo; +import Journey.Together.domain.plan.dto.PlanDetailRes; +import Journey.Together.domain.plan.entity.Day; +import Journey.Together.domain.plan.entity.Plan; +import Journey.Together.domain.plan.repository.DayRepository; +import Journey.Together.domain.plan.util.DateUtil; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class PlanDetailQueryService { + + private final DayRepository dayRepository; + private final DisabilityPlaceCategoryRepository disabilityPlaceCategoryRepository; + private final PlanQueryService planQueryService; + + public PlanDetailRes getDetail(Member member, Plan plan) { + boolean isWriter = member != null && plan.getMember().getMemberId().equals(member.getMemberId()); + + List dayList = dayRepository.findAllByMemberAndPlanOrderByDateAsc(plan.getMember(), plan); + List imageUrls = planQueryService.getPlaceImageListOfPlan(plan); // 분리된 로직 사용 + + Map> groupedByDate = dayList.stream() + .collect(Collectors.groupingBy(Day::getDate)); + + List dailyLists = new ArrayList<>(); + LocalDate startDate = plan.getStartDate(); + LocalDate endDate = plan.getEndDate(); + + for (LocalDate date = startDate; !date.isAfter(endDate); date = date.plusDays(1)) { + List days = groupedByDate.get(date); + List dailyPlaceInfoList = new ArrayList<>(); + if (days != null) { + for (Day day : days) { + List disabilityCategoryList = + disabilityPlaceCategoryRepository.findDisabilityCategoryIds(day.getPlace().getId()); + dailyPlaceInfoList.add(DailyPlaceInfo.of(day.getPlace(), disabilityCategoryList)); + } + } + dailyLists.add(DailyList.of(date, dailyPlaceInfoList)); + } + + dailyLists.sort(Comparator.comparing(DailyList::getDate)); + String remainDate = DateUtil.getRemainDateString(startDate, endDate); + + return PlanDetailRes.of(imageUrls, dailyLists, isWriter, plan, remainDate); + } +} + diff --git a/src/main/java/Journey/Together/domain/plan/service/query/PlanQueryService.java b/src/main/java/Journey/Together/domain/plan/service/query/PlanQueryService.java new file mode 100644 index 0000000..e032c35 --- /dev/null +++ b/src/main/java/Journey/Together/domain/plan/service/query/PlanQueryService.java @@ -0,0 +1,111 @@ +package Journey.Together.domain.plan.service.query; + +import Journey.Together.domain.member.entity.Member; +import Journey.Together.domain.plan.dto.*; +import Journey.Together.domain.plan.entity.Day; +import Journey.Together.domain.plan.entity.Plan; +import Journey.Together.domain.plan.repository.DayRepository; +import Journey.Together.domain.plan.repository.PlanRepository; +import Journey.Together.domain.plan.repository.PlanReviewRepository; +import Journey.Together.domain.plan.util.DateUtil; +import Journey.Together.global.util.S3UrlUtil; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDate; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class PlanQueryService { + private final DayRepository dayRepository; + private final PlanRepository planRepository; + private final PlanReviewRepository planReviewRepository; + private final S3UrlUtil s3UrlUtil; + + public String getFirstPlaceImageOfPlan(Plan plan) { + List dayList = dayRepository.findByPlanOrderByCreatedAtDesc(plan); + if (!dayList.isEmpty()) { + String placeImageUrl = dayList.get(0).getPlace().getFirstImg(); + return placeImageUrl.isEmpty() ? null : placeImageUrl; + } + return null; + } + + public List getPlaceImageListOfPlan(Plan plan) { + List dayList = dayRepository.findByPlanOrderByCreatedAtDesc(plan); + List list = new ArrayList<>(); + if (!dayList.isEmpty()) { + dayList.forEach(day -> { + list.add(day.getPlace().getFirstImg()); + }); + return list; + } + return null; + } + + @Transactional(readOnly = true) + public List findMyPlans(Member member) { + List plans = planRepository.findAllByMemberAndDeletedAtIsNull(member); + + List top3 = plans.stream() + .sorted(Comparator.comparingLong(plan -> + Math.abs(ChronoUnit.DAYS.between(LocalDate.now(), plan.getStartDate())))) + .limit(3) + .toList(); + + return top3.stream() + .map(plan -> { + String image = getFirstPlaceImageOfPlan(plan); + String remainDate = DateUtil.getRemainDateString(plan.getStartDate(), plan.getEndDate()); + Boolean hasReview = null; + + if (LocalDate.now().isAfter(plan.getEndDate())) { + hasReview = planReviewRepository.existsAllByPlanAndDeletedAtIsNull(plan); + } + + return MyPlanRes.of(plan, image, remainDate, hasReview); + }) + .collect(Collectors.toList()); + } + + @Transactional + public PlanPageRes findIsCompelete(Member member, Pageable page, Boolean compelete) { + Pageable pageable = PageRequest.of(page.getPageNumber(), page.getPageSize(), Sort.by("createdAt").descending()); + Page planPage; + List planResList; + if (compelete) { + planPage = planRepository.findAllByMemberAndEndDateBeforeAndDeletedAtIsNull(member, LocalDate.now(), pageable); + planResList = planPage.getContent().stream() + .map(plan -> PlanRes.of(plan, getFirstPlaceImageOfPlan(plan), null, planReviewRepository.existsAllByPlanAndReportFilter(plan))) + .collect(Collectors.toList()); + } else { + planPage = planRepository.findAllByMemberAndEndDateGreaterThanEqualAndDeletedAtIsNull(member, LocalDate.now(), pageable); + planResList = planPage.getContent().stream() + .map(plan -> PlanRes.of(plan, getFirstPlaceImageOfPlan(plan), DateUtil.getRemainDateString(plan.getStartDate(), plan.getEndDate()), null)) + .collect(Collectors.toList()); + } + + return PlanPageRes.of(planResList, planPage.getNumber(), planPage.getSize(), planPage.getTotalPages(), planPage.isLast()); + } + + @Transactional + public OpenPlanPageRes findOpenPlans(Pageable page) { + Pageable pageable = PageRequest.of(page.getPageNumber(), page.getPageSize(), Sort.by("createdAt").descending()); + Page planPage = planRepository.findOpenPlans(LocalDate.now(), pageable); + List openPlanResList = planPage.getContent().stream() + .map(plan -> OpenPlanRes.of(plan, s3UrlUtil.generateProfileUrl(plan.getMember().getProfileUuid()), getFirstPlaceImageOfPlan(plan))) + .collect(Collectors.toList()); + return OpenPlanPageRes.of(openPlanResList, planPage.getNumber(), planPage.getSize(), planPage.getTotalPages(), planPage.isLast()); + } + +} diff --git a/src/main/java/Journey/Together/domain/plan/service/query/PlanReviewQueryService.java b/src/main/java/Journey/Together/domain/plan/service/query/PlanReviewQueryService.java new file mode 100644 index 0000000..297a094 --- /dev/null +++ b/src/main/java/Journey/Together/domain/plan/service/query/PlanReviewQueryService.java @@ -0,0 +1,51 @@ +package Journey.Together.domain.plan.service.query; + +import Journey.Together.domain.member.entity.Member; +import Journey.Together.domain.plan.dto.PlanReviewRes; +import Journey.Together.domain.plan.entity.Plan; +import Journey.Together.domain.plan.entity.PlanReview; +import Journey.Together.domain.plan.repository.PlanRepository; +import Journey.Together.domain.plan.repository.PlanReviewRepository; +import Journey.Together.domain.plan.service.PlanReviewImageService; +import Journey.Together.domain.plan.service.validator.PlanValidator; +import Journey.Together.global.util.S3UrlUtil; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class PlanReviewQueryService { + + private final PlanRepository planRepository; + private final PlanReviewRepository planReviewRepository; + private final PlanValidator planValidator; + private final PlanReviewImageService planReviewImageService; + private final S3UrlUtil s3UrlUtil; + + public PlanReviewRes getReview(Member member, long planId) { + Plan plan = planRepository.findPlanByPlanIdAndDeletedAtIsNull(planId); + planValidator.validateExists(plan); + + PlanReview planReview = planReviewRepository.findPlanReview(plan); + boolean isWriter = (member != null) && plan.getMember().getMemberId().equals(member.getMemberId()); + + String profileUrl = s3UrlUtil.generateProfileUrl(plan.getMember().getProfileUuid()); + + if (planReview == null) { + return PlanReviewRes.of(null, null, null, isWriter, false, null, profileUrl, null); + } + + List imageList = planReviewImageService.getImageUrls(planReview); + if (planReview.getReport() == null) { + return PlanReviewRes.of(planReview.getPlanReviewId(), planReview.getContent(), planReview.getGrade(), + isWriter, true, imageList, profileUrl, null); + } + + return PlanReviewRes.of(planReview.getPlanReviewId(), planReview.getContent(), planReview.getGrade(), + isWriter, true, imageList, profileUrl, planReview.getReport()); + } +} + + diff --git a/src/main/java/Journey/Together/domain/plan/service/validator/PlanReviewValidator.java b/src/main/java/Journey/Together/domain/plan/service/validator/PlanReviewValidator.java new file mode 100644 index 0000000..cc2d436 --- /dev/null +++ b/src/main/java/Journey/Together/domain/plan/service/validator/PlanReviewValidator.java @@ -0,0 +1,27 @@ +package Journey.Together.domain.plan.service.validator; + +import Journey.Together.domain.member.entity.Member; +import Journey.Together.domain.plan.entity.Plan; +import Journey.Together.domain.plan.entity.PlanReview; +import Journey.Together.domain.plan.repository.PlanReviewRepository; +import Journey.Together.global.exception.ApplicationException; +import Journey.Together.global.exception.ErrorCode; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class PlanReviewValidator { + private final PlanReviewRepository planReviewRepository; + public void validateExists(Plan plan) { + if (planReviewRepository.existsAllByPlanAndDeletedAtIsNull(plan)) { + throw new ApplicationException(ErrorCode.ALREADY_EXIST_EXCEPTION); + } + } + + public void validateWriter(Member member, PlanReview planReview) { + if (!planReview.getPlan().getMember().getMemberId().equals(member.getMemberId())) { + throw new ApplicationException(ErrorCode.UNAUTHORIZED_EXCEPTION); + } + } +} diff --git a/src/main/java/Journey/Together/domain/plan/service/validator/PlanValidator.java b/src/main/java/Journey/Together/domain/plan/service/validator/PlanValidator.java new file mode 100644 index 0000000..034a953 --- /dev/null +++ b/src/main/java/Journey/Together/domain/plan/service/validator/PlanValidator.java @@ -0,0 +1,22 @@ +package Journey.Together.domain.plan.service.validator; + +import Journey.Together.domain.member.entity.Member; +import Journey.Together.domain.plan.entity.Plan; +import Journey.Together.global.exception.ApplicationException; +import Journey.Together.global.exception.ErrorCode; +import org.springframework.stereotype.Component; + +@Component +public class PlanValidator { + public void validateExists(Plan plan) { + if (plan == null) { + throw new ApplicationException(ErrorCode.NOT_FOUND_EXCEPTION); + } + } + + public void validateWriter(Member requester, Plan plan) { + if (!requester.getMemberId().equals(plan.getMember().getMemberId())) { + throw new ApplicationException(ErrorCode.UNAUTHORIZED_EXCEPTION); + } + } +} diff --git a/src/main/java/Journey/Together/domain/plan/util/DateUtil.java b/src/main/java/Journey/Together/domain/plan/util/DateUtil.java new file mode 100644 index 0000000..4821522 --- /dev/null +++ b/src/main/java/Journey/Together/domain/plan/util/DateUtil.java @@ -0,0 +1,21 @@ +package Journey.Together.domain.plan.util; + +import java.time.LocalDate; +import java.time.Period; + +public class DateUtil { + private DateUtil() {} // 인스턴스 생성 방지 + + public static String getRemainDateString(LocalDate startDate, LocalDate endDate) { + LocalDate today = LocalDate.now(); + + if ((today.isEqual(startDate) || today.isAfter(startDate)) + && (today.isEqual(endDate) || today.isBefore(endDate))) { + return "D-DAY"; + } else if (today.isBefore(startDate)) { + Period period = Period.between(today, startDate); + return "D-" + period.getDays(); + } + return null; + } +} diff --git a/src/main/java/Journey/Together/global/util/S3UrlUtil.java b/src/main/java/Journey/Together/global/util/S3UrlUtil.java new file mode 100644 index 0000000..4de171d --- /dev/null +++ b/src/main/java/Journey/Together/global/util/S3UrlUtil.java @@ -0,0 +1,15 @@ +package Journey.Together.global.util; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class S3UrlUtil { + + private final S3Client s3Client; + + public String generateProfileUrl(String profileUuid) { + return s3Client.baseUrl() + profileUuid + "/profile_" + profileUuid; + } +} \ No newline at end of file