diff --git a/src/main/java/UMC/career_mate/domain/member/service/MemberService.java b/src/main/java/UMC/career_mate/domain/member/service/MemberService.java index 2583c74..3d63b97 100644 --- a/src/main/java/UMC/career_mate/domain/member/service/MemberService.java +++ b/src/main/java/UMC/career_mate/domain/member/service/MemberService.java @@ -16,6 +16,8 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.List; + @Service @Transactional(readOnly = true) @RequiredArgsConstructor @@ -30,13 +32,11 @@ public Member makeProfile(CreateProfileDTO request,Member member) { Job job = jobService.findJobById(request.job()); Member profileMember = findMemberByMemberId(member.getId()); - //회원 프로필 작성 + //회원 프로필 작성하기 profileMember.createProfile(request, job); profileMember.completeProfile(); - //비어있는 플래너 생성 - CreatePlannerDTO createPlannerDTO = CreatePlannerDTO.builder().build(); - plannerService.savePlanner(profileMember,createPlannerDTO); + plannerService.savePlanner(profileMember); return profileMember; } diff --git a/src/main/java/UMC/career_mate/domain/planner/Planner.java b/src/main/java/UMC/career_mate/domain/planner/Planner.java index 037b531..605e401 100644 --- a/src/main/java/UMC/career_mate/domain/planner/Planner.java +++ b/src/main/java/UMC/career_mate/domain/planner/Planner.java @@ -50,7 +50,7 @@ public class Planner extends BaseEntity { @Column(name = "other_plans") private String otherPlans; - @OneToOne + @ManyToOne @JoinColumn(name = "member_id") private Member member; diff --git a/src/main/java/UMC/career_mate/domain/planner/controller/PlannerController.java b/src/main/java/UMC/career_mate/domain/planner/controller/PlannerController.java index e7fcbee..86821c5 100644 --- a/src/main/java/UMC/career_mate/domain/planner/controller/PlannerController.java +++ b/src/main/java/UMC/career_mate/domain/planner/controller/PlannerController.java @@ -1,8 +1,8 @@ package UMC.career_mate.domain.planner.controller; import UMC.career_mate.domain.member.Member; -import UMC.career_mate.domain.planner.dto.request.CreatePlannerDTO; -import UMC.career_mate.domain.planner.dto.response.PlannerResponseDTO; +import UMC.career_mate.domain.planner.dto.request.CreatePlannerListDTO; +import UMC.career_mate.domain.planner.dto.response.PlannerListResponseDTO; import UMC.career_mate.domain.planner.service.PlannerCommandService; import UMC.career_mate.domain.planner.service.PlannerQueryService; import UMC.career_mate.global.annotation.LoginMember; @@ -19,35 +19,6 @@ public class PlannerController { private final PlannerCommandService plannerCommandService; private final PlannerQueryService plannerQueryService; - @Deprecated - @PostMapping - @Operation( - summary = "[관리자용 API] 플래너 생성", - description = """ - [관리자용 API입니다] - 새로운 플래너를 생성합니다. - 값이 비어있어도 상관 없습니다. - ### Example JSON: - ```json - { - "activityName": "React.js로 커리어 메이트 00파트 개발", - "startTime": "2025-12-23T09:00:00", - "endTime": "2025-12-23T18:00:00", - "specifics": "React.js를 활용한 프로젝트 5개 기능 구현", - "measurable": "개인 프로젝트를 GitHub에 배포 + 최소 5개 기능 구현", - "achievable": "하루에 최소 3시간 이상 투자하기", - "relevant": "프로젝트 경험을 통한 기술 및 협업 역량 향상 기대", - "timeBound": "2025년 2월 10일 - 12/23~1/10: 00부분까지 완료, 1/12~1/26: API 연결", - "otherPlans": "매주 토요일 강남역 대면 미팅 참석" - } - ``` - """) - - public ApiResponse savePlanner(@LoginMember Member member, @RequestBody CreatePlannerDTO createPlannerDTO) { - plannerCommandService.savePlanner(member, createPlannerDTO); - return ApiResponse.onSuccess("플래너 추가 완료"); - } - @PatchMapping @Operation( summary = "플래너 수정", @@ -57,22 +28,35 @@ public ApiResponse savePlanner(@LoginMember Member member, @RequestBody 값이 비어있어도 상관 없습니다. ### Example JSON: ```json - { - "activityName": "React.js로 커리어 메이트 00파트 개발", - "startTime": "2025-12-23T09:00:00", - "endTime": "2025-12-23T18:00:00", - "specifics": "React.js를 활용한 프로젝트 5개 기능 구현", - "measurable": "개인 프로젝트를 GitHub에 배포 + 최소 5개 기능 구현", - "achievable": "하루에 최소 3시간 이상 투자하기", - "relevant": "프로젝트 경험을 통한 기술 및 협업 역량 향상 기대", - "timeBound": "2025년 2월 10일 - 12/23~1/10: 00부분까지 완료, 1/12~1/26: API 연결", - "otherPlans": "매주 토요일 강남역 대면 미팅 참석" - } + planners : [ + { + "activityName": "React.js로 커리어 메이트 00파트 개발", + "startTime": "2025-12-23T09:00:00", + "endTime": "2025-12-23T18:00:00", + "specifics": "React.js를 활용한 프로젝트 5개 기능 구현", + "measurable": "개인 프로젝트를 GitHub에 배포 + 최소 5개 기능 구현", + "achievable": "하루에 최소 3시간 이상 투자하기", + "relevant": "프로젝트 경험을 통한 기술 및 협업 역량 향상 기대", + "timeBound": "2025년 2월 10일 - 12/23~1/10: 00부분까지 완료, 1/12~1/26: API 연결", + "otherPlans": "매주 토요일 강남역 대면 미팅 참석" + }, + { + "activityName": "React.js로 커리어 메이트 00파트 개발", + "startTime": "2025-12-23T09:00:00", + "endTime": "2025-12-23T18:00:00", + "specifics": "React.js를 활용한 프로젝트 5개 기능 구현", + "measurable": "개인 프로젝트를 GitHub에 배포 + 최소 5개 기능 구현", + "achievable": "하루에 최소 3시간 이상 투자하기", + "relevant": "프로젝트 경험을 통한 기술 및 협업 역량 향상 기대", + "timeBound": "2025년 2월 10일 - 12/23~1/10: 00부분까지 완료, 1/12~1/26: API 연결", + "otherPlans": "매주 토요일 강남역 대면 미팅 참석" + } + ] ``` """) - public ApiResponse editPlanner(@LoginMember Member member, @RequestBody CreatePlannerDTO createPlannerDTO) { - plannerCommandService.editPlanner(member, createPlannerDTO); + public ApiResponse editPlanner(@LoginMember Member member, @RequestBody CreatePlannerListDTO createPlannerListDTO) { + plannerCommandService.editPlanner(member, createPlannerListDTO); return ApiResponse.onSuccess("플래너 수정 완료"); } @@ -95,20 +79,31 @@ public ApiResponse deletePlanner(@LoginMember Member member){ 없는 값은 null을 리턴합니다. ### Example JSON: ```json - { - "activityName": "React.js로 커리어 메이트 00파트 개발", - "startTime": "2025-12-23T09:00:00", - "endTime": "2025-12-23T18:00:00", - "specifics": "React.js를 활용한 프로젝트 5개 기능 구현", - "measurable": "개인 프로젝트를 GitHub에 배포 + 최소 5개 기능 구현", - "achievable": "하루에 최소 3시간 이상 투자하기", - "relevant": "프로젝트 경험을 통한 기술 및 협업 역량 향상 기대", - "timeBound": "2025년 2월 10일 - 12/23~1/10: 00부분까지 완료, 1/12~1/26: API 연결", - "otherPlans": "매주 토요일 강남역 대면 미팅 참석" - } + planners : [ + { + "activityName": "React.js로 커리어 메이트 00파트 개발", + "startTime": "2025-12-23T09:00:00", + "endTime": "2025-12-23T18:00:00", + "specifics": "React.js를 활용한 프로젝트 5개 기능 구현", + "measurable": "개인 프로젝트를 GitHub에 배포 + 최소 5개 기능 구현", + "achievable": "하루에 최소 3시간 이상 투자하기", + "relevant": "프로젝트 경험을 통한 기술 및 협업 역량 향상 기대", + "timeBound": "2025년 2월 10일 - 12/23~1/10: 00부분까지 완료, 1/12~1/26: API 연결", + "otherPlans": "매주 토요일 강남역 대면 미팅 참석" + },{ + "activityName": "React.js로 커리어 메이트 00파트 개발", + "startTime": "2025-12-23T09:00:00", + "endTime": "2025-12-23T18:00:00", + "specifics": "React.js를 활용한 프로젝트 5개 기능 구현", + "measurable": "개인 프로젝트를 GitHub에 배포 + 최소 5개 기능 구현", + "achievable": "하루에 최소 3시간 이상 투자하기", + "relevant": "프로젝트 경험을 통한 기술 및 협업 역량 향상 기대", + "timeBound": "2025년 2월 10일 - 12/23~1/10: 00부분까지 완료, 1/12~1/26: API 연결", + "otherPlans": "매주 토요일 강남역 대면 미팅 참석" + }] ``` """) - public ApiResponse getPlanner(@LoginMember Member member){ + public ApiResponse getPlanner(@LoginMember Member member){ return ApiResponse.onSuccess(plannerQueryService.getPlannerByMember(member)); } diff --git a/src/main/java/UMC/career_mate/domain/planner/converter/PlannerConverter.java b/src/main/java/UMC/career_mate/domain/planner/converter/PlannerConverter.java index 4dae263..aa758ba 100644 --- a/src/main/java/UMC/career_mate/domain/planner/converter/PlannerConverter.java +++ b/src/main/java/UMC/career_mate/domain/planner/converter/PlannerConverter.java @@ -3,8 +3,13 @@ import UMC.career_mate.domain.member.Member; import UMC.career_mate.domain.planner.Planner; import UMC.career_mate.domain.planner.dto.request.CreatePlannerDTO; +import UMC.career_mate.domain.planner.dto.response.PlannerListResponseDTO; import UMC.career_mate.domain.planner.dto.response.PlannerResponseDTO; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + public class PlannerConverter { public static Planner toPlanner(Member member, CreatePlannerDTO createPlannerDTO){ @@ -23,6 +28,10 @@ public static Planner toPlanner(Member member, CreatePlannerDTO createPlannerDTO .build(); } + public static List toPlannerList(Member member, List createPlannerDTOList){ + return createPlannerDTOList.stream().map(createPlannerDTO -> PlannerConverter.toPlanner(member, createPlannerDTO)).toList(); + } + public static PlannerResponseDTO toPlannerResponseDTO(Planner planner){ return PlannerResponseDTO.builder() .activityName(planner.getActivityName()) @@ -36,4 +45,14 @@ public static PlannerResponseDTO toPlannerResponseDTO(Planner planner){ .otherPlans(planner.getOtherPlans()) .build(); } + + public static PlannerListResponseDTO toPlannerListResponseDTO(List plannerList){ + List plannerResponseDTOList = plannerList.stream() + .map(PlannerConverter::toPlannerResponseDTO) + .toList(); + return PlannerListResponseDTO.builder() + .planners(plannerResponseDTOList) + .build(); + } + } diff --git a/src/main/java/UMC/career_mate/domain/planner/dto/request/CreatePlannerListDTO.java b/src/main/java/UMC/career_mate/domain/planner/dto/request/CreatePlannerListDTO.java new file mode 100644 index 0000000..d08956e --- /dev/null +++ b/src/main/java/UMC/career_mate/domain/planner/dto/request/CreatePlannerListDTO.java @@ -0,0 +1,24 @@ +package UMC.career_mate.domain.planner.dto.request; + +import UMC.career_mate.global.response.exception.GeneralException; +import UMC.career_mate.global.response.exception.code.CommonErrorCode; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import lombok.Builder; + +import java.util.List; + +@Builder +public record CreatePlannerListDTO( + + @NotNull(message = "플래너 목록은 필수입니다.") + @Valid + List planners +) { + public CreatePlannerListDTO { + + if (planners == null || planners.size() != 2) { + throw new GeneralException(CommonErrorCode.INVALID_PLANNER_COUNT); + } + } +} \ No newline at end of file diff --git a/src/main/java/UMC/career_mate/domain/planner/dto/response/PlannerListResponseDTO.java b/src/main/java/UMC/career_mate/domain/planner/dto/response/PlannerListResponseDTO.java new file mode 100644 index 0000000..9a3a2f0 --- /dev/null +++ b/src/main/java/UMC/career_mate/domain/planner/dto/response/PlannerListResponseDTO.java @@ -0,0 +1,12 @@ +package UMC.career_mate.domain.planner.dto.response; + +import UMC.career_mate.domain.planner.Planner; +import lombok.Builder; + +import java.util.List; + +@Builder +public record PlannerListResponseDTO( + List planners +) { +} diff --git a/src/main/java/UMC/career_mate/domain/planner/repository/PlannerRepository.java b/src/main/java/UMC/career_mate/domain/planner/repository/PlannerRepository.java index ccbe231..b8b56be 100644 --- a/src/main/java/UMC/career_mate/domain/planner/repository/PlannerRepository.java +++ b/src/main/java/UMC/career_mate/domain/planner/repository/PlannerRepository.java @@ -4,10 +4,12 @@ import UMC.career_mate.domain.planner.Planner; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; import java.util.Optional; public interface PlannerRepository extends JpaRepository { Boolean existsByMember(Member member); Optional findPlannerByMember(Member member); + List findByMember(Member member); } diff --git a/src/main/java/UMC/career_mate/domain/planner/service/PlannerCommandService.java b/src/main/java/UMC/career_mate/domain/planner/service/PlannerCommandService.java index b56673b..9093111 100644 --- a/src/main/java/UMC/career_mate/domain/planner/service/PlannerCommandService.java +++ b/src/main/java/UMC/career_mate/domain/planner/service/PlannerCommandService.java @@ -4,6 +4,7 @@ import UMC.career_mate.domain.planner.Planner; import UMC.career_mate.domain.planner.converter.PlannerConverter; import UMC.career_mate.domain.planner.dto.request.CreatePlannerDTO; +import UMC.career_mate.domain.planner.dto.request.CreatePlannerListDTO; import UMC.career_mate.domain.planner.repository.PlannerRepository; import UMC.career_mate.global.response.exception.GeneralException; import UMC.career_mate.global.response.exception.code.CommonErrorCode; @@ -12,6 +13,9 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.Iterator; +import java.util.List; + @Slf4j @Service @RequiredArgsConstructor @@ -20,25 +24,35 @@ public class PlannerCommandService { private final PlannerRepository plannerRepository; - public void savePlanner(Member member, CreatePlannerDTO createPlannerDTO){ + public void savePlanner(Member member){ + if(plannerRepository.existsByMember(member)){ throw new GeneralException(CommonErrorCode.PLANNER_EXISTS); } - Planner planner = PlannerConverter.toPlanner(member, createPlannerDTO); - plannerRepository.save(planner); + + List createPlannerDTOList = List.of( + CreatePlannerDTO.builder().build(), + CreatePlannerDTO.builder().build() + ); + + plannerRepository.saveAll(PlannerConverter.toPlannerList(member, createPlannerDTOList)); } - public void editPlanner(Member member, CreatePlannerDTO createPlannerDTO){ - Planner planner = plannerRepository.findPlannerByMember(member).orElseThrow( - ()->new GeneralException(CommonErrorCode.PLANNER_NOT_EXISTS)); + public void editPlanner(Member member, CreatePlannerListDTO createPlannerListDTO){ + + List planners = plannerRepository.findByMember(member); + List plannerDTOs = createPlannerListDTO.planners(); + + Iterator dtoIterator = plannerDTOs.iterator(); + planners.forEach(planner -> planner.update(dtoIterator.next())); - planner.update(createPlannerDTO); } public void deletePlanner(Member member){ - Planner planner = plannerRepository.findPlannerByMember(member).orElseThrow( - ()->new GeneralException(CommonErrorCode.PLANNER_NOT_EXISTS)); - plannerRepository.delete(planner); + + List planners = plannerRepository.findByMember(member); + plannerRepository.deleteAll(planners); + } } diff --git a/src/main/java/UMC/career_mate/domain/planner/service/PlannerQueryService.java b/src/main/java/UMC/career_mate/domain/planner/service/PlannerQueryService.java index 892c24f..b2709c9 100644 --- a/src/main/java/UMC/career_mate/domain/planner/service/PlannerQueryService.java +++ b/src/main/java/UMC/career_mate/domain/planner/service/PlannerQueryService.java @@ -3,6 +3,7 @@ import UMC.career_mate.domain.member.Member; import UMC.career_mate.domain.planner.Planner; import UMC.career_mate.domain.planner.converter.PlannerConverter; +import UMC.career_mate.domain.planner.dto.response.PlannerListResponseDTO; import UMC.career_mate.domain.planner.dto.response.PlannerResponseDTO; import UMC.career_mate.domain.planner.repository.PlannerRepository; import UMC.career_mate.global.response.exception.GeneralException; @@ -12,6 +13,8 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.List; + @Slf4j @Service @RequiredArgsConstructor @@ -20,11 +23,8 @@ public class PlannerQueryService { private final PlannerRepository plannerRepository; - public PlannerResponseDTO getPlannerByMember(Member member) { - Planner planner = plannerRepository.findPlannerByMember(member).orElseThrow( - ()-> new GeneralException(CommonErrorCode.PLANNER_NOT_EXISTS) - ); - - return PlannerConverter.toPlannerResponseDTO(planner); + public PlannerListResponseDTO getPlannerByMember(Member member) { + List planners = plannerRepository.findByMember(member); + return PlannerConverter.toPlannerListResponseDTO(planners); } } diff --git a/src/main/java/UMC/career_mate/global/response/exception/code/CommonErrorCode.java b/src/main/java/UMC/career_mate/global/response/exception/code/CommonErrorCode.java index 96d643e..0301d0e 100644 --- a/src/main/java/UMC/career_mate/global/response/exception/code/CommonErrorCode.java +++ b/src/main/java/UMC/career_mate/global/response/exception/code/CommonErrorCode.java @@ -22,6 +22,7 @@ public enum CommonErrorCode implements ErrorCode { //Planner 도메인 PLANNER_NOT_EXISTS(400, "EPL001","유저의 플래너가 존재하지 않습니다. 먼저 POST로 생성해주세요."), PLANNER_EXISTS(400, "EPL002","유저의 플래너가 존재합니다. 프로필 작성 완료시 플래너가 자동 생성되기 때문에,PATCH API를 사용해주시면 되겠습니다"), + INVALID_PLANNER_COUNT(400, "EPL003", "플래너는 2개씩 전송해야 합니다."), // Recruit 도메인 NOT_FOUND_RECRUIT(400, "ERE000", "해당 채용 공고를 찾을 수 없습니다."),