Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
75f8073
#132 refactor: plan 생성 분리
sycuuui Jul 27, 2025
e4345f4
#132 refactor: day 생성 분리
sycuuui Jul 27, 2025
17bea40
#132 refactor: savePlacesByDay메소드, PlanPlaceService 클래스에 구현하여 책임 분리
sycuuui Jul 27, 2025
f9de27b
#132 refactor: 회원 존재 검증 memberValidator 클래스에 분리
sycuuui Jul 27, 2025
aa30e9f
#132 PlanModifier 클래스 생성하여 plan수정 작업 로직 분리
sycuuui Jul 27, 2025
6c4afe1
#132 PlanQueryService생성하여 plan의 첫 장소 이미지 가져오는 로직 분리
sycuuui Jul 27, 2025
91c9568
#132 PlanDeleter생성하여 plan의 day,planreview,plan 삭제 로직 분리
sycuuui Jul 27, 2025
32ce48a
#132 PlanDetailQueryService생성하여 plan의 day와 모든 day의 정보 가져오는 로직 분리
sycuuui Jul 27, 2025
f1adac9
#132 DateUtil생성하여 남은 기간 문자열 생성 로직 분리
sycuuui Jul 27, 2025
f8c9094
#132 일정 공개 여부 업데이트 메소드에서 작성자 검증 로직과 공개 여부 변환 로직 분리
sycuuui Jul 27, 2025
1e3a143
#132 PlanReviewVlidator,PlanReviewFactory 생성하여 planReview 검증 및 생성 로직 분리
sycuuui Jul 27, 2025
45e2439
#132 PlanReviewImageService 생성하여 planReview이미지들 저장 로직 분리
sycuuui Jul 27, 2025
ff02102
#132 PlanReview 가져오는 메소드 분리하여 PlanService에서 제거
sycuuui Jul 27, 2025
02670ad
#132 PlanReviewImageFactory 생성하여 리뷰이미지 생성 분리
sycuuui Jul 27, 2025
c9dcdc0
#132 PlanReview update 메소드 책임 분리 및 planService에서 제거
sycuuui Jul 27, 2025
65b8eee
#132 PlanReview 생성 메소드 PlanReviewService로 이동
sycuuui Jul 27, 2025
520d657
#132 PlanReview 삭제 메소드 책임 분리 및 PlanService에서 제거
sycuuui Jul 27, 2025
3fc12fa
#132 내 일정 가장 최신 3개 가져오는 메소드 책임 분리 및 PlanService에서 제거
sycuuui Jul 27, 2025
29cf605
PlaceQueryService 생성하여 장소 검색 메소드 이동 및 PlanService에서 제거
sycuuui Jul 27, 2025
2865f2d
#132 완료 여부에 따른 내 일정 가져오기 메소드 이동 및 PlanService에서 제거
sycuuui Jul 27, 2025
e0aa15c
#132 공개 일정 가져오기 메소드 이동 및 PlanService에서 제거
sycuuui Jul 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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));
}
}
Original file line number Diff line number Diff line change
@@ -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<Place> placePage = placeRepository.findAllByNameContainsOrderByCreatedAtDesc(word, pageable);
List<PlaceInfo> placeInfoList = placePage.getContent().stream()
.map(PlaceInfo::of)
.collect(Collectors.toList());

return PlaceInfoPageRes.of(
placeInfoList,
placePage.getNumber(),
placePage.getSize(),
placePage.getTotalPages(),
placePage.isLast(),
placePage.getTotalElements()
);
}
}

Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -51,40 +60,40 @@ public ApiResponse updatePlanIsPublic(@AuthenticationPrincipal PrincipalDetails

@GetMapping("/search")
public ApiResponse<PlaceInfoPageRes> 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<MultipartFile> 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<PlanReviewRes> 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<MultipartFile> 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<PlanReviewRes> 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<OpenPlanPageRes> 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}")
Expand All @@ -99,16 +108,16 @@ public ApiResponse<PlanDetailRes> findPalnDetailInfo(@PathVariable("plan_id")Lon

@GetMapping("/my")
public ApiResponse<List<MyPlanRes>> 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<PlanPageRes> 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<PlanPageRes> 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));
}

}
Original file line number Diff line number Diff line change
@@ -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<DailyPlace> 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);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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<MultipartFile> 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<String> getImageUrls(PlanReview planReview) {
return planReviewImageRepository.findAllByPlanReviewAndDeletedAtIsNull(planReview)
.stream()
.map(PlanReviewImage::getImageUrl)
.collect(Collectors.toList());
}

public void deleteImages(List<String> deleteImgUrls) {
for (String url : deleteImgUrls) {
planReviewImageRepository.deletePlanReviewImageByImageUrl(url);
s3Client.delete(StringUtils.substringAfter(url, "com/"));
}
}

public void deleteAllImages(PlanReview planReview) {
List<PlanReviewImage> images = planReviewImageRepository.findAllByPlanReviewAndDeletedAtIsNull(planReview);

for (PlanReviewImage image : images) {
String filename = image.getImageUrl().replace(s3Client.baseUrl(), "");
s3Client.delete(filename); // S3에서 이미지 삭제
planReviewImageRepository.deletePlanReviewImageByPlanReviewImageId(image.getPlanReviewImageId());
}
}
}
Original file line number Diff line number Diff line change
@@ -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<MultipartFile> 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<MultipartFile> 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());
}
}
Loading