From c53561024c82e8117ca9bf3de44599cd03cedf05 Mon Sep 17 00:00:00 2001 From: dlwjddus1112 Date: Wed, 15 Apr 2026 15:57:28 +0900 Subject: [PATCH 01/19] =?UTF-8?q?[Feat]=20#238=20=EB=A1=9C=EB=93=9C?= =?UTF-8?q?=EB=A7=B5=20=EC=97=94=ED=8B=B0=ED=8B=B0=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kareer/domain/roadmap/entity/Roadmap.java | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/main/java/org/sopt/kareer/domain/roadmap/entity/Roadmap.java diff --git a/src/main/java/org/sopt/kareer/domain/roadmap/entity/Roadmap.java b/src/main/java/org/sopt/kareer/domain/roadmap/entity/Roadmap.java new file mode 100644 index 0000000..e211a09 --- /dev/null +++ b/src/main/java/org/sopt/kareer/domain/roadmap/entity/Roadmap.java @@ -0,0 +1,44 @@ +package org.sopt.kareer.domain.roadmap.entity; + +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.sopt.kareer.domain.member.entity.Member; +import org.sopt.kareer.domain.roadmap.entity.enums.RoadmapActiveStatus; +import org.sopt.kareer.global.entity.BaseEntity; + +@Entity +@Table(name = "roadmaps") +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Roadmap extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "roadmap_id") + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id", nullable = false) + private Member member; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private RoadmapActiveStatus status; + + public static Roadmap create(Member member) { + return Roadmap.builder() + .member(member) + .status(RoadmapActiveStatus.ACTIVE) + .build(); + } + + public void deactivate() { + this.status = RoadmapActiveStatus.INACTIVE; + } +} From a4b266ebb30dcedebc287f8dc97af2136e8e82b4 Mon Sep 17 00:00:00 2001 From: dlwjddus1112 Date: Wed, 15 Apr 2026 16:00:02 +0900 Subject: [PATCH 02/19] =?UTF-8?q?[Refactor]=20#238=20Phase=EA=B0=80=20memb?= =?UTF-8?q?er=EA=B0=80=20=EC=95=84=EB=8B=8C=20roadmap=EA=B3=BC=20=EC=97=B0?= =?UTF-8?q?=EA=B4=80=EA=B4=80=EA=B3=84=EB=A5=BC=20=EA=B0=96=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sopt/kareer/domain/roadmap/entity/Phase.java | 13 ++++++------- .../roadmap/entity/enums/RoadmapActiveStatus.java | 5 +++++ .../domain/roadmap/repository/PhaseRepository.java | 4 ++-- .../repository/PhaseRepositoryCustomImpl.java | 4 +++- .../repository/PhaseTranslationRepository.java | 2 +- .../kareer/domain/roadmap/service/PhaseService.java | 4 ++-- .../roadmap/service/RoadMapPersistService.java | 6 +++++- 7 files changed, 24 insertions(+), 14 deletions(-) create mode 100644 src/main/java/org/sopt/kareer/domain/roadmap/entity/enums/RoadmapActiveStatus.java diff --git a/src/main/java/org/sopt/kareer/domain/roadmap/entity/Phase.java b/src/main/java/org/sopt/kareer/domain/roadmap/entity/Phase.java index a6e7bdb..ef0a539 100644 --- a/src/main/java/org/sopt/kareer/domain/roadmap/entity/Phase.java +++ b/src/main/java/org/sopt/kareer/domain/roadmap/entity/Phase.java @@ -7,7 +7,6 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import org.sopt.kareer.domain.member.entity.Member; import org.sopt.kareer.domain.roadmap.entity.enums.PhaseStatus; import org.sopt.kareer.global.entity.BaseEntity; @@ -25,8 +24,8 @@ public class Phase extends BaseEntity { private Long id; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "member_id", nullable = false) - private Member member; + @JoinColumn(name = "roadmap_id", nullable = false) + private Roadmap roadmap; @Column(nullable = false) private int sequence; @@ -48,8 +47,8 @@ public class Phase extends BaseEntity { private LocalDate endDate; @Builder - private Phase(Member member, int sequence, String goal, String description, PhaseStatus status, LocalDate startDate, LocalDate endDate) { - this.member = member; + private Phase(Roadmap roadmap, int sequence, String goal, String description, PhaseStatus status, LocalDate startDate, LocalDate endDate) { + this.roadmap = roadmap; this.sequence = sequence; this.goal = goal; this.description = description; @@ -58,9 +57,9 @@ private Phase(Member member, int sequence, String goal, String description, Phas this.endDate = endDate; } - public static Phase create(Member member, int sequence, String goal, String description, PhaseStatus status, LocalDate startDate, LocalDate endDate) { + public static Phase create(Roadmap roadmap, int sequence, String goal, String description, PhaseStatus status, LocalDate startDate, LocalDate endDate) { return Phase.builder() - .member(member) + .roadmap(roadmap) .sequence(sequence) .goal(goal) .description(description) diff --git a/src/main/java/org/sopt/kareer/domain/roadmap/entity/enums/RoadmapActiveStatus.java b/src/main/java/org/sopt/kareer/domain/roadmap/entity/enums/RoadmapActiveStatus.java new file mode 100644 index 0000000..4c8293f --- /dev/null +++ b/src/main/java/org/sopt/kareer/domain/roadmap/entity/enums/RoadmapActiveStatus.java @@ -0,0 +1,5 @@ +package org.sopt.kareer.domain.roadmap.entity.enums; + +public enum RoadmapActiveStatus { + ACTIVE, INACTIVE +} diff --git a/src/main/java/org/sopt/kareer/domain/roadmap/repository/PhaseRepository.java b/src/main/java/org/sopt/kareer/domain/roadmap/repository/PhaseRepository.java index cf4e32d..a2ff301 100644 --- a/src/main/java/org/sopt/kareer/domain/roadmap/repository/PhaseRepository.java +++ b/src/main/java/org/sopt/kareer/domain/roadmap/repository/PhaseRepository.java @@ -5,7 +5,7 @@ public interface PhaseRepository extends JpaRepository, PhaseRepositoryCustom { - boolean existsByIdAndMember_Id(Long phaseId, Long memberId); + boolean existsByIdAndRoadmap_Member_Id(Long phaseId, Long memberId); - void deleteAllByMember_Id(Long memberId); + void deleteAllByRoadmap_Member_Id(Long memberId); } diff --git a/src/main/java/org/sopt/kareer/domain/roadmap/repository/PhaseRepositoryCustomImpl.java b/src/main/java/org/sopt/kareer/domain/roadmap/repository/PhaseRepositoryCustomImpl.java index bd249a0..b965038 100644 --- a/src/main/java/org/sopt/kareer/domain/roadmap/repository/PhaseRepositoryCustomImpl.java +++ b/src/main/java/org/sopt/kareer/domain/roadmap/repository/PhaseRepositoryCustomImpl.java @@ -18,6 +18,7 @@ import org.sopt.kareer.domain.roadmap.entity.QPhase; import org.sopt.kareer.domain.roadmap.entity.QPhaseAction; import org.sopt.kareer.domain.roadmap.entity.enums.PhaseActionType; +import org.sopt.kareer.domain.roadmap.entity.enums.RoadmapActiveStatus; import org.springframework.stereotype.Repository; @Repository @@ -51,7 +52,8 @@ public List findPhases(Long memberId) { phase.endDate )) .from(phase) - .where(phase.member.id.eq(memberId)) + .where(phase.roadmap.member.id.eq(memberId) + .and(phase.roadmap.status.eq(RoadmapActiveStatus.ACTIVE))) .orderBy(phase.sequence.asc()) .fetch(); } diff --git a/src/main/java/org/sopt/kareer/domain/roadmap/repository/PhaseTranslationRepository.java b/src/main/java/org/sopt/kareer/domain/roadmap/repository/PhaseTranslationRepository.java index ac2b64e..e1bea62 100644 --- a/src/main/java/org/sopt/kareer/domain/roadmap/repository/PhaseTranslationRepository.java +++ b/src/main/java/org/sopt/kareer/domain/roadmap/repository/PhaseTranslationRepository.java @@ -11,5 +11,5 @@ public interface PhaseTranslationRepository extends JpaRepository phaseIds, String language); - void deleteAllByPhase_Member_Id(Long memberId); + void deleteAllByPhase_Roadmap_Member_Id(Long memberId); } diff --git a/src/main/java/org/sopt/kareer/domain/roadmap/service/PhaseService.java b/src/main/java/org/sopt/kareer/domain/roadmap/service/PhaseService.java index 433bf63..8d5566b 100644 --- a/src/main/java/org/sopt/kareer/domain/roadmap/service/PhaseService.java +++ b/src/main/java/org/sopt/kareer/domain/roadmap/service/PhaseService.java @@ -60,7 +60,7 @@ public PhaseListResponse getPhases(Long memberId) { } public RoadmapPhaseDetailResponse getRoadmapPhaseDetail(Long memberId, Long phaseId) { - if (!phaseRepository.existsByIdAndMember_Id(phaseId, memberId)) { + if (!phaseRepository.existsByIdAndRoadmap_Member_Id(phaseId, memberId)) { throw new RoadMapException(RoadmapErrorCode.PHASE_NOT_FOUND); } Map> raw = @@ -117,7 +117,7 @@ private RoadmapPhaseDetailResponse.ActionGroupResponse wrap( } public HomePhaseDetailResponse getHomePhaseDetail(Long memberId, Long phaseId) { - if (!phaseRepository.existsByIdAndMember_Id(phaseId, memberId)) { + if (!phaseRepository.existsByIdAndRoadmap_Member_Id(phaseId, memberId)) { throw new RoadMapException(RoadmapErrorCode.PHASE_NOT_FOUND); } diff --git a/src/main/java/org/sopt/kareer/domain/roadmap/service/RoadMapPersistService.java b/src/main/java/org/sopt/kareer/domain/roadmap/service/RoadMapPersistService.java index 83045ca..d8af58f 100644 --- a/src/main/java/org/sopt/kareer/domain/roadmap/service/RoadMapPersistService.java +++ b/src/main/java/org/sopt/kareer/domain/roadmap/service/RoadMapPersistService.java @@ -5,6 +5,7 @@ import org.sopt.kareer.domain.roadmap.dto.response.RoadmapResponse; import org.sopt.kareer.domain.roadmap.dto.translation.RoadmapTranslationTarget; import org.sopt.kareer.domain.roadmap.entity.*; +import org.sopt.kareer.domain.roadmap.repository.RoadmapRepository; import org.sopt.kareer.domain.roadmap.entity.enums.ActionItemType; import org.sopt.kareer.domain.roadmap.entity.enums.PhaseActionType; import org.sopt.kareer.domain.roadmap.entity.enums.PhaseStatus; @@ -31,14 +32,17 @@ public class RoadMapPersistService { private final PhaseActionGuidelineRepository phaseActionGuidelineRepository; private final PhaseActionMistakeRepository phaseActionMistakeRepository; private final ActionItemRepository actionItemRepository; + private final RoadmapRepository roadmapRepository; @Transactional public RoadmapTranslationTarget saveRoadMap(Member member, RoadmapResponse response) { + Roadmap roadmap = roadmapRepository.save(Roadmap.create(member)); + List phaseTargets = new ArrayList<>(); for (RoadmapResponse.PhasePlan phasePlan : Optional.ofNullable(response.phases()).orElse(Collections.emptyList())) { Phase savedPhase = phaseRepository.save(Phase.create( - member, + roadmap, phasePlan.sequence(), phasePlan.goal(), phasePlan.description(), From 21cda674b31d8e4f74bbc6194c3833723a40204c Mon Sep 17 00:00:00 2001 From: dlwjddus1112 Date: Wed, 15 Apr 2026 16:00:37 +0900 Subject: [PATCH 03/19] =?UTF-8?q?[Refactor]=20#238=20actionItem=EC=9D=B4?= =?UTF-8?q?=20roadmap=EA=B3=BC=20=EC=97=B0=EA=B4=80=EA=B4=80=EA=B3=84?= =?UTF-8?q?=EB=A5=BC=20=EA=B0=96=EB=8F=84=EB=A1=9D=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/ActionItemRepository.java | 15 +++++++++++++++ .../PhaseActionGuidelineRepository.java | 2 +- ...aseActionGuidelineTranslationRepository.java | 2 +- .../PhaseActionMistakeRepository.java | 2 +- ...PhaseActionMistakeTranslationRepository.java | 2 +- .../repository/PhaseActionRepository.java | 5 +++-- .../PhaseActionTranslationRepository.java | 2 +- .../roadmap/repository/RoadmapRepository.java | 17 +++++++++++++++++ 8 files changed, 40 insertions(+), 7 deletions(-) create mode 100644 src/main/java/org/sopt/kareer/domain/roadmap/repository/RoadmapRepository.java diff --git a/src/main/java/org/sopt/kareer/domain/roadmap/repository/ActionItemRepository.java b/src/main/java/org/sopt/kareer/domain/roadmap/repository/ActionItemRepository.java index fb2447d..b2e4687 100644 --- a/src/main/java/org/sopt/kareer/domain/roadmap/repository/ActionItemRepository.java +++ b/src/main/java/org/sopt/kareer/domain/roadmap/repository/ActionItemRepository.java @@ -3,6 +3,8 @@ import org.sopt.kareer.domain.roadmap.entity.ActionItem; import org.sopt.kareer.domain.roadmap.entity.enums.ActionItemStatus; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import java.util.List; @@ -21,4 +23,17 @@ public interface ActionItemRepository extends JpaRepository { Optional findByIdAndMemberId(@Param("actionItemId") Long actionItemId, @Param("memberId") Long memberId); void deleteAllByMemberId(Long memberId); + + @Modifying + @Query(""" + UPDATE ActionItem ai + SET ai.status = 'INACTIVE' + WHERE ai.phaseAction.id IN ( + SELECT pa.id FROM PhaseAction pa + WHERE pa.phase.id IN ( + SELECT p.id FROM Phase p WHERE p.roadmap.id = :roadmapId + ) + ) + """) + void deactivateAllByRoadmapId(@Param("roadmapId") Long roadmapId); } diff --git a/src/main/java/org/sopt/kareer/domain/roadmap/repository/PhaseActionGuidelineRepository.java b/src/main/java/org/sopt/kareer/domain/roadmap/repository/PhaseActionGuidelineRepository.java index 2ec5338..cc69c6d 100644 --- a/src/main/java/org/sopt/kareer/domain/roadmap/repository/PhaseActionGuidelineRepository.java +++ b/src/main/java/org/sopt/kareer/domain/roadmap/repository/PhaseActionGuidelineRepository.java @@ -17,5 +17,5 @@ public interface PhaseActionGuidelineRepository extends JpaRepository findContentByPhaseActionId(@Param("phaseActionId") Long phaseActionId); - void deleteAllByPhaseAction_Phase_Member_Id(Long memberId); + void deleteAllByPhaseAction_Phase_Roadmap_Member_Id(Long memberId); } diff --git a/src/main/java/org/sopt/kareer/domain/roadmap/repository/PhaseActionGuidelineTranslationRepository.java b/src/main/java/org/sopt/kareer/domain/roadmap/repository/PhaseActionGuidelineTranslationRepository.java index 1cecab3..2a578d5 100644 --- a/src/main/java/org/sopt/kareer/domain/roadmap/repository/PhaseActionGuidelineTranslationRepository.java +++ b/src/main/java/org/sopt/kareer/domain/roadmap/repository/PhaseActionGuidelineTranslationRepository.java @@ -23,6 +23,6 @@ List findContentByPhaseActionIdAndLanguage(@Param("phaseActionId") Long void deleteAllByGuideline_PhaseAction_IdInAndLanguage(List phaseActionIds, String language); - void deleteAllByGuideline_PhaseAction_Phase_Member_Id(Long memberId); + void deleteAllByGuideline_PhaseAction_Phase_Roadmap_Member_Id(Long memberId); } diff --git a/src/main/java/org/sopt/kareer/domain/roadmap/repository/PhaseActionMistakeRepository.java b/src/main/java/org/sopt/kareer/domain/roadmap/repository/PhaseActionMistakeRepository.java index 9c12ae1..340acc7 100644 --- a/src/main/java/org/sopt/kareer/domain/roadmap/repository/PhaseActionMistakeRepository.java +++ b/src/main/java/org/sopt/kareer/domain/roadmap/repository/PhaseActionMistakeRepository.java @@ -17,5 +17,5 @@ public interface PhaseActionMistakeRepository extends JpaRepository findContentByPhaseActionId(@Param("phaseActionId") Long phaseActionId); - void deleteAllByPhaseAction_Phase_Member_Id(Long memberId); + void deleteAllByPhaseAction_Phase_Roadmap_Member_Id(Long memberId); } diff --git a/src/main/java/org/sopt/kareer/domain/roadmap/repository/PhaseActionMistakeTranslationRepository.java b/src/main/java/org/sopt/kareer/domain/roadmap/repository/PhaseActionMistakeTranslationRepository.java index d6361f0..0290dbc 100644 --- a/src/main/java/org/sopt/kareer/domain/roadmap/repository/PhaseActionMistakeTranslationRepository.java +++ b/src/main/java/org/sopt/kareer/domain/roadmap/repository/PhaseActionMistakeTranslationRepository.java @@ -23,6 +23,6 @@ List findContentByPhaseActionIdAndLanguage(@Param("phaseActionId") Long void deleteAllByMistake_PhaseAction_IdInAndLanguage(List phaseActionIds, String language); - void deleteAllByMistake_PhaseAction_Phase_Member_Id(Long memberId); + void deleteAllByMistake_PhaseAction_Phase_Roadmap_Member_Id(Long memberId); } diff --git a/src/main/java/org/sopt/kareer/domain/roadmap/repository/PhaseActionRepository.java b/src/main/java/org/sopt/kareer/domain/roadmap/repository/PhaseActionRepository.java index b42063e..5b75955 100644 --- a/src/main/java/org/sopt/kareer/domain/roadmap/repository/PhaseActionRepository.java +++ b/src/main/java/org/sopt/kareer/domain/roadmap/repository/PhaseActionRepository.java @@ -16,11 +16,12 @@ public interface PhaseActionRepository extends JpaRepository SELECT pa FROM PhaseAction pa JOIN FETCH pa.phase p - JOIN FETCH p.member m + JOIN FETCH p.roadmap r + JOIN FETCH r.member m WHERE pa.id = :phaseActionId AND m.id = :memberId """) Optional findByIdAndMemberId(@Param("phaseActionId") Long phaseActionId, @Param("memberId") Long memberId); - void deleteAllByPhase_Member_Id(Long memberId); + void deleteAllByPhase_Roadmap_Member_Id(Long memberId); } diff --git a/src/main/java/org/sopt/kareer/domain/roadmap/repository/PhaseActionTranslationRepository.java b/src/main/java/org/sopt/kareer/domain/roadmap/repository/PhaseActionTranslationRepository.java index 6f8861a..1361c0d 100644 --- a/src/main/java/org/sopt/kareer/domain/roadmap/repository/PhaseActionTranslationRepository.java +++ b/src/main/java/org/sopt/kareer/domain/roadmap/repository/PhaseActionTranslationRepository.java @@ -14,5 +14,5 @@ public interface PhaseActionTranslationRepository extends JpaRepository phaseActionIds, String language); - void deleteAllByPhaseAction_Phase_Member_Id(Long memberId); + void deleteAllByPhaseAction_Phase_Roadmap_Member_Id(Long memberId); } diff --git a/src/main/java/org/sopt/kareer/domain/roadmap/repository/RoadmapRepository.java b/src/main/java/org/sopt/kareer/domain/roadmap/repository/RoadmapRepository.java new file mode 100644 index 0000000..ba3ccc2 --- /dev/null +++ b/src/main/java/org/sopt/kareer/domain/roadmap/repository/RoadmapRepository.java @@ -0,0 +1,17 @@ +package org.sopt.kareer.domain.roadmap.repository; + +import org.sopt.kareer.domain.roadmap.entity.Roadmap; +import org.sopt.kareer.domain.roadmap.entity.enums.RoadmapActiveStatus; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.Optional; + +public interface RoadmapRepository extends JpaRepository { + + Optional findByMember_IdAndStatus(Long memberId, RoadmapActiveStatus status); + + List findAllByMember_Id(Long memberId); + + void deleteAllByMember_Id(Long memberId); +} From 5122b9576b12feded650ea81204ebe0b1ee80a9a Mon Sep 17 00:00:00 2001 From: dlwjddus1112 Date: Wed, 15 Apr 2026 16:01:40 +0900 Subject: [PATCH 04/19] =?UTF-8?q?[Refactor]=20#238=20=EB=A1=9C=EB=93=9C?= =?UTF-8?q?=EB=A7=B5=20=EC=A1=B0=ED=9A=8C=20=EC=8B=9C=20ACTIVE=ED=95=9C=20?= =?UTF-8?q?=EB=A1=9C=EB=93=9C=EB=A7=B5=EB=A7=8C=20=EC=A1=B0=ED=9A=8C?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 추후에 로드맵 재생성 기능 추가 시를 위해 소프트 딜리트 형태를 미리 구현 --- .../member/service/MemberDeletionService.java | 19 +++++++++++-------- .../roadmap/service/RoadMapService.java | 11 +++++++++++ 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/sopt/kareer/domain/member/service/MemberDeletionService.java b/src/main/java/org/sopt/kareer/domain/member/service/MemberDeletionService.java index c7321d3..5a579df 100644 --- a/src/main/java/org/sopt/kareer/domain/member/service/MemberDeletionService.java +++ b/src/main/java/org/sopt/kareer/domain/member/service/MemberDeletionService.java @@ -16,6 +16,7 @@ import org.sopt.kareer.domain.roadmap.repository.PhaseActionTranslationRepository; import org.sopt.kareer.domain.roadmap.repository.PhaseRepository; import org.sopt.kareer.domain.roadmap.repository.PhaseTranslationRepository; +import org.sopt.kareer.domain.roadmap.repository.RoadmapRepository; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -37,22 +38,24 @@ public class MemberDeletionService { private final PhaseActionMistakeTranslationRepository phaseActionMistakeTranslationRepository; private final ActionItemRepository actionItemRepository; private final ActionItemTranslationRepository actionItemTranslationRepository; + private final RoadmapRepository roadmapRepository; @Transactional public void deleteMember(Member member) { Long memberId = member.getId(); - phaseActionGuidelineTranslationRepository.deleteAllByGuideline_PhaseAction_Phase_Member_Id(memberId); - phaseActionMistakeTranslationRepository.deleteAllByMistake_PhaseAction_Phase_Member_Id(memberId); + phaseActionGuidelineTranslationRepository.deleteAllByGuideline_PhaseAction_Phase_Roadmap_Member_Id(memberId); + phaseActionMistakeTranslationRepository.deleteAllByMistake_PhaseAction_Phase_Roadmap_Member_Id(memberId); actionItemTranslationRepository.deleteAllByActionItem_Member_Id(memberId); - phaseActionTranslationRepository.deleteAllByPhaseAction_Phase_Member_Id(memberId); - phaseTranslationRepository.deleteAllByPhase_Member_Id(memberId); + phaseActionTranslationRepository.deleteAllByPhaseAction_Phase_Roadmap_Member_Id(memberId); + phaseTranslationRepository.deleteAllByPhase_Roadmap_Member_Id(memberId); - phaseActionGuidelineRepository.deleteAllByPhaseAction_Phase_Member_Id(memberId); - phaseActionMistakeRepository.deleteAllByPhaseAction_Phase_Member_Id(memberId); + phaseActionGuidelineRepository.deleteAllByPhaseAction_Phase_Roadmap_Member_Id(memberId); + phaseActionMistakeRepository.deleteAllByPhaseAction_Phase_Roadmap_Member_Id(memberId); actionItemRepository.deleteAllByMemberId(memberId); - phaseActionRepository.deleteAllByPhase_Member_Id(memberId); - phaseRepository.deleteAllByMember_Id(memberId); + phaseActionRepository.deleteAllByPhase_Roadmap_Member_Id(memberId); + phaseRepository.deleteAllByRoadmap_Member_Id(memberId); + roadmapRepository.deleteAllByMember_Id(memberId); jobPostingBookmarkRepository.deleteAllByMemberId(memberId); memberTermRepository.deleteAllByMemberId(memberId); memberVisaRepository.deleteAllByMemberId(memberId); diff --git a/src/main/java/org/sopt/kareer/domain/roadmap/service/RoadMapService.java b/src/main/java/org/sopt/kareer/domain/roadmap/service/RoadMapService.java index f25fa41..4e559d6 100644 --- a/src/main/java/org/sopt/kareer/domain/roadmap/service/RoadMapService.java +++ b/src/main/java/org/sopt/kareer/domain/roadmap/service/RoadMapService.java @@ -11,6 +11,9 @@ import org.sopt.kareer.domain.roadmap.dto.response.RoadmapResponse; import org.sopt.kareer.domain.roadmap.dto.response.RoadmapTestResponse; import org.sopt.kareer.domain.roadmap.dto.translation.RoadmapTranslationTarget; +import org.sopt.kareer.domain.roadmap.entity.enums.RoadmapActiveStatus; +import org.sopt.kareer.domain.roadmap.repository.ActionItemRepository; +import org.sopt.kareer.domain.roadmap.repository.RoadmapRepository; import org.sopt.kareer.global.external.ai.builder.context.MemberContextBuilder; import org.sopt.kareer.global.external.ai.service.OpenAiService; import org.sopt.kareer.global.external.ai.service.PolicyDocumentRetriever; @@ -34,12 +37,20 @@ public class RoadMapService { private final RequiredDocumentRetriever requiredRetriever; private final PolicyDocumentRetriever policyDocumentRetriever; private final MemberVisaRepository memberVisaRepository; + private final RoadmapRepository roadmapRepository; + private final ActionItemRepository actionItemRepository; @Transactional public RoadmapTranslationTarget createRoadmap(Long memberId){ Member member = memberService.getById(memberId); + roadmapRepository.findByMember_IdAndStatus(memberId, RoadmapActiveStatus.ACTIVE) + .ifPresent(existing -> { + actionItemRepository.deactivateAllByRoadmapId(existing.getId()); + existing.deactivate(); + }); + var memberContext = memberContextBuilder.load(memberId); MemberVisa visa = memberVisaRepository.findActiveByMemberId(memberId).orElseThrow(() -> new MemberException(MemberErrorCode.VISA_NOT_FOUND)); From c99b907de5b4c36c274ecec4913f59006527041b Mon Sep 17 00:00:00 2001 From: dlwjddus1112 Date: Wed, 15 Apr 2026 16:50:25 +0900 Subject: [PATCH 05/19] =?UTF-8?q?[Refactor]=20#238=20=EB=A1=9C=EB=93=9C?= =?UTF-8?q?=EB=A7=B5=20=EA=B4=80=EB=A0=A8=20API=20=EC=97=94=EB=93=9C?= =?UTF-8?q?=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit REST 설계 원칙에 따라 Roadmap 하위 도메인 관련 API들은 /roadmap 하위 경로로 위치하도록 변경 --- .../member/controller/MemberController.java | 29 ----------- .../controller/ActionItemController.java | 2 +- .../controller/PhaseActionController.java | 2 +- .../roadmap/controller/PhaseController.java | 4 +- .../roadmap/controller/RoadmapController.java | 51 +++++++++++++++++++ 5 files changed, 55 insertions(+), 33 deletions(-) create mode 100644 src/main/java/org/sopt/kareer/domain/roadmap/controller/RoadmapController.java diff --git a/src/main/java/org/sopt/kareer/domain/member/controller/MemberController.java b/src/main/java/org/sopt/kareer/domain/member/controller/MemberController.java index 3345fb3..9efa90f 100644 --- a/src/main/java/org/sopt/kareer/domain/member/controller/MemberController.java +++ b/src/main/java/org/sopt/kareer/domain/member/controller/MemberController.java @@ -13,9 +13,6 @@ import org.sopt.kareer.domain.member.dto.response.*; import org.sopt.kareer.domain.member.service.LocalizedOnboardQueryService; import org.sopt.kareer.domain.member.service.MemberService; -import org.sopt.kareer.domain.roadmap.dto.response.RoadmapTestResponse; -import org.sopt.kareer.domain.roadmap.service.RoadMapService; -import org.sopt.kareer.domain.roadmap.service.RoadmapTranslationService; import org.sopt.kareer.global.annotation.CustomExceptionDescription; import org.sopt.kareer.global.auth.service.AuthService; import org.sopt.kareer.global.config.swagger.SwaggerResponseDescription; @@ -36,8 +33,6 @@ public class MemberController { private final MemberService memberService; - private final RoadMapService roadMapService; - private final RoadmapTranslationService roadmapTranslationService; private final AuthService authService; private final LocalizedOnboardQueryService localizedOnboardQueryService; @@ -96,30 +91,6 @@ public ResponseEntity> getOnboardFields() { .body(BaseResponse.ok(localizedOnboardQueryService.getFields(), "온보딩 관심 분야 목록 조회에 성공하였습니다.")); } - @Operation(summary = "AI 로드맵 생성 API", description = "사용자가 온보딩에 입력한 정보를 통해 로드맵을 생성합니다.") - @CustomExceptionDescription(CREATE_ROADMAP) - @PostMapping("roadmap") - public ResponseEntity> generateRoadmap( - @AuthenticationPrincipal Long memberId) { - - var target = roadMapService.createRoadmap(memberId); - - roadmapTranslationService.translateAllLanguages(target); - - return ResponseEntity.status(HttpStatus.OK) - .body(BaseResponse.ok("AI 로드맵 생성에 성공하였습니다.")); - } - - @Operation(summary = "AI 로드맵 생성 테스트용 API (Server Only)", description = "사용자가 온보딩에 입력한 정보를 통해 로드맵을 생성합니다.") - @CustomExceptionDescription(CREATE_ROADMAP) - @PostMapping("roadmap/test") - public ResponseEntity> generateRoadmapForTest( - @AuthenticationPrincipal Long memberId) { - - return ResponseEntity.status(HttpStatus.OK) - .body(BaseResponse.ok(roadMapService.createRoadmapTest(memberId), "AI 로드맵 생성에 성공하였습니다.")); - } - @Operation(summary = "유저 상태 조회", description = "사용자의 비자 정보, 졸업 정보를 조회합니다.") @CustomExceptionDescription(USER_STATUS) @GetMapping("me/status") diff --git a/src/main/java/org/sopt/kareer/domain/roadmap/controller/ActionItemController.java b/src/main/java/org/sopt/kareer/domain/roadmap/controller/ActionItemController.java index 5232496..b26de7d 100644 --- a/src/main/java/org/sopt/kareer/domain/roadmap/controller/ActionItemController.java +++ b/src/main/java/org/sopt/kareer/domain/roadmap/controller/ActionItemController.java @@ -18,7 +18,7 @@ @Tag(name = "Action Item API", description = "Action Item 관련 API") @RestController -@RequestMapping("/api/v1/action-items") +@RequestMapping("/api/v1/roadmap/action-items") @RequiredArgsConstructor public class ActionItemController { diff --git a/src/main/java/org/sopt/kareer/domain/roadmap/controller/PhaseActionController.java b/src/main/java/org/sopt/kareer/domain/roadmap/controller/PhaseActionController.java index af216e7..902349e 100644 --- a/src/main/java/org/sopt/kareer/domain/roadmap/controller/PhaseActionController.java +++ b/src/main/java/org/sopt/kareer/domain/roadmap/controller/PhaseActionController.java @@ -19,7 +19,7 @@ @Tag(name = "Phase Action API", description = "Phase Action 관련 API") @RestController @RequiredArgsConstructor -@RequestMapping("/api/v1/phase-actions") +@RequestMapping("/api/v1/roadmap/phase-actions") public class PhaseActionController { private final PhaseActionService phaseActionService; diff --git a/src/main/java/org/sopt/kareer/domain/roadmap/controller/PhaseController.java b/src/main/java/org/sopt/kareer/domain/roadmap/controller/PhaseController.java index 8424a7e..6181ae5 100644 --- a/src/main/java/org/sopt/kareer/domain/roadmap/controller/PhaseController.java +++ b/src/main/java/org/sopt/kareer/domain/roadmap/controller/PhaseController.java @@ -21,7 +21,7 @@ @Tag(name="Phase API", description = "Phase 관련 API") @RestController @RequiredArgsConstructor -@RequestMapping("/api/v1/phases") +@RequestMapping("/api/v1/roadmap/phases") public class PhaseController { private final PhaseService phaseService; @@ -38,7 +38,7 @@ public ResponseEntity> getPhaseList( ); } - @GetMapping("/{phaseId}/roadmap") + @GetMapping("/{phaseId}") @Operation(summary = "로드맵 Phase 상세정보 조회", description = "로드맵 Phase 상세조회를 조회합니다.") @CustomExceptionDescription(SwaggerResponseDescription.ROADMAP_PHASE_LIST_DETAIL) public ResponseEntity> getRoadmapPhaseDetail( diff --git a/src/main/java/org/sopt/kareer/domain/roadmap/controller/RoadmapController.java b/src/main/java/org/sopt/kareer/domain/roadmap/controller/RoadmapController.java new file mode 100644 index 0000000..d7c039e --- /dev/null +++ b/src/main/java/org/sopt/kareer/domain/roadmap/controller/RoadmapController.java @@ -0,0 +1,51 @@ +package org.sopt.kareer.domain.roadmap.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.sopt.kareer.domain.roadmap.dto.response.RoadmapTestResponse; +import org.sopt.kareer.domain.roadmap.service.RoadMapService; +import org.sopt.kareer.domain.roadmap.service.RoadmapTranslationService; +import org.sopt.kareer.global.annotation.CustomExceptionDescription; +import org.sopt.kareer.global.response.BaseResponse; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import static org.sopt.kareer.global.config.swagger.SwaggerResponseDescription.CREATE_ROADMAP; + +@Tag(name = "Roadmap API", description = "Roadmap 관련 API") +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/v1/roadmap") +public class RoadmapController { + + private final RoadMapService roadMapService; + private final RoadmapTranslationService roadmapTranslationService; + + @Operation(summary = "AI 로드맵 생성", description = "사용자가 온보딩에 입력한 정보를 통해 로드맵을 생성합니다.") + @CustomExceptionDescription(CREATE_ROADMAP) + @PostMapping + public ResponseEntity> generateRoadmap( + @AuthenticationPrincipal Long memberId) { + + var target = roadMapService.createRoadmap(memberId); + roadmapTranslationService.translateAllLanguages(target); + + return ResponseEntity.status(HttpStatus.OK) + .body(BaseResponse.ok("AI 로드맵 생성에 성공하였습니다.")); + } + + @Operation(summary = "AI 로드맵 생성 테스트용 (Server Only)", description = "사용자가 온보딩에 입력한 정보를 통해 로드맵을 생성합니다.") + @CustomExceptionDescription(CREATE_ROADMAP) + @PostMapping("/test") + public ResponseEntity> generateRoadmapForTest( + @AuthenticationPrincipal Long memberId) { + + return ResponseEntity.status(HttpStatus.OK) + .body(BaseResponse.ok(roadMapService.createRoadmapTest(memberId), "AI 로드맵 생성에 성공하였습니다.")); + } +} From 0a7f81761d93f23e19ff2eb1f92c142493499111 Mon Sep 17 00:00:00 2001 From: dlwjddus1112 Date: Wed, 15 Apr 2026 16:57:40 +0900 Subject: [PATCH 06/19] =?UTF-8?q?[Chore]=20#238=20=EB=A1=9C=EB=93=9C?= =?UTF-8?q?=EB=A7=B5=20=EB=B9=84=EB=8F=99=EA=B8=B0=20=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=ED=95=84=EB=93=9C=20=EB=B0=8F=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kareer/domain/member/entity/Member.java | 18 ------------------ .../member/entity/enums/RoadmapStatus.java | 5 ----- .../member/exception/MemberErrorCode.java | 4 +--- .../member/repository/MemberRepository.java | 12 ------------ .../domain/roadmap/service/RoadMapService.java | 11 +---------- 5 files changed, 2 insertions(+), 48 deletions(-) delete mode 100644 src/main/java/org/sopt/kareer/domain/member/entity/enums/RoadmapStatus.java diff --git a/src/main/java/org/sopt/kareer/domain/member/entity/Member.java b/src/main/java/org/sopt/kareer/domain/member/entity/Member.java index dcfae8b..2a21ea0 100644 --- a/src/main/java/org/sopt/kareer/domain/member/entity/Member.java +++ b/src/main/java/org/sopt/kareer/domain/member/entity/Member.java @@ -75,10 +75,6 @@ public class Member extends BaseEntity { private String fieldsOfInterest; - @Enumerated(EnumType.STRING) - @Column(nullable = false) - private RoadmapStatus roadmapStatus; - // 프론트 온보딩 구현 완료 후 삭제 예정 public void updateInfo(String name, LocalDate birthDate, @@ -160,7 +156,6 @@ public static Member createOAuthMember(String name, .provider(provider) .providerId(providerId) .profileImageUrl(profileImageUrl) - .roadmapStatus(RoadmapStatus.NOT_STARTED) .build(); } @@ -176,19 +171,6 @@ private void assertPendingStatus() { } - public void assertCanStartRoadmap() { - if (roadmapStatus == RoadmapStatus.IN_PROGRESS) { - throw new MemberException(MemberErrorCode.ROADMAP_IN_PROGRESS); - } - if (roadmapStatus == RoadmapStatus.DONE) { - throw new MemberException(MemberErrorCode.ROADMAP_ALREADY_GENERATED); - } - } - - public void markRoadmapInProgress() { this.roadmapStatus = RoadmapStatus.IN_PROGRESS; } - public void markRoadmapDone() { this.roadmapStatus = RoadmapStatus.DONE; } - public void markRoadmapFailed() { this.roadmapStatus = RoadmapStatus.FAILED; } - public void updateProfile( String targetJob, LocalDate birthDate, diff --git a/src/main/java/org/sopt/kareer/domain/member/entity/enums/RoadmapStatus.java b/src/main/java/org/sopt/kareer/domain/member/entity/enums/RoadmapStatus.java deleted file mode 100644 index 06bb405..0000000 --- a/src/main/java/org/sopt/kareer/domain/member/entity/enums/RoadmapStatus.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.sopt.kareer.domain.member.entity.enums; - -public enum RoadmapStatus { - NOT_STARTED, IN_PROGRESS, DONE, FAILED -} diff --git a/src/main/java/org/sopt/kareer/domain/member/exception/MemberErrorCode.java b/src/main/java/org/sopt/kareer/domain/member/exception/MemberErrorCode.java index cf0921b..308a48c 100644 --- a/src/main/java/org/sopt/kareer/domain/member/exception/MemberErrorCode.java +++ b/src/main/java/org/sopt/kareer/domain/member/exception/MemberErrorCode.java @@ -12,9 +12,7 @@ public enum MemberErrorCode implements ErrorCode { ONBOARDING_ALREADY_COMPLETED(HttpStatus.BAD_REQUEST.value(), "이미 온보딩이 완료된 회원입니다."), INVALID_VISA_POINT(HttpStatus.BAD_REQUEST.value(), "visaPoint는 D10 비자인 경우에만 입력할 수 있습니다."), INVALID_COUNTRY(HttpStatus.BAD_REQUEST.value(), "지원하지 않는 국가입니다."), - VISA_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "비자 정보가 존재하지 않습니다."), - ROADMAP_IN_PROGRESS(HttpStatus.BAD_REQUEST.value(), "로드맵이 생성 중입니다."), - ROADMAP_ALREADY_GENERATED(HttpStatus.CONFLICT.value(), "이미 로드맵을 생성하였습니다."); + VISA_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "비자 정보가 존재하지 않습니다."); private final int httpStatus; private final String message; diff --git a/src/main/java/org/sopt/kareer/domain/member/repository/MemberRepository.java b/src/main/java/org/sopt/kareer/domain/member/repository/MemberRepository.java index 50df62f..741cc27 100644 --- a/src/main/java/org/sopt/kareer/domain/member/repository/MemberRepository.java +++ b/src/main/java/org/sopt/kareer/domain/member/repository/MemberRepository.java @@ -3,22 +3,10 @@ import org.sopt.kareer.domain.member.entity.Member; import org.sopt.kareer.domain.member.entity.enums.OAuthProvider; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Modifying; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; import java.util.Optional; public interface MemberRepository extends JpaRepository { Optional findByProviderAndProviderId(OAuthProvider provider, String providerId); - - @Modifying(clearAutomatically = true, flushAutomatically = true) - @Query(""" - update Member m - set m.roadmapStatus = 'IN_PROGRESS' - where m.id = :memberId - and m.roadmapStatus in ('NOT_STARTED','FAILED') - """) - int tryMarkRoadmapInProgress(@Param("memberId") Long memberId); } diff --git a/src/main/java/org/sopt/kareer/domain/roadmap/service/RoadMapService.java b/src/main/java/org/sopt/kareer/domain/roadmap/service/RoadMapService.java index 4e559d6..e1bf556 100644 --- a/src/main/java/org/sopt/kareer/domain/roadmap/service/RoadMapService.java +++ b/src/main/java/org/sopt/kareer/domain/roadmap/service/RoadMapService.java @@ -76,11 +76,7 @@ public RoadmapTranslationTarget createRoadmap(Long memberId){ policyDocs ); - RoadmapTranslationTarget target = roadMapPersistService.saveRoadMap(member, response); - - member.markRoadmapDone(); - - return target; + return roadMapPersistService.saveRoadMap(member, response); } @Transactional @@ -120,9 +116,4 @@ public RoadmapTestResponse createRoadmapTest(Long memberId) { return RoadmapTestResponse.of(roadmap, retrieved); } - @Transactional - public void markFailed(Long memberId) { - Member member = memberService.getById(memberId); - member.markRoadmapFailed(); - } } From f0c8c99674c16b48669fd54bf2ebdb26098437f4 Mon Sep 17 00:00:00 2001 From: dlwjddus1112 Date: Wed, 15 Apr 2026 18:19:46 +0900 Subject: [PATCH 07/19] =?UTF-8?q?[Refactor]=20#238=20TermController=20?= =?UTF-8?q?=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20=EC=9D=B8?= =?UTF-8?q?=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=EB=A1=9C=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kareer/domain/term/controller/TermApi.java | 16 ++++++++++++++++ .../domain/term/controller/TermController.java | 12 +++--------- 2 files changed, 19 insertions(+), 9 deletions(-) create mode 100644 src/main/java/org/sopt/kareer/domain/term/controller/TermApi.java diff --git a/src/main/java/org/sopt/kareer/domain/term/controller/TermApi.java b/src/main/java/org/sopt/kareer/domain/term/controller/TermApi.java new file mode 100644 index 0000000..60fdebd --- /dev/null +++ b/src/main/java/org/sopt/kareer/domain/term/controller/TermApi.java @@ -0,0 +1,16 @@ +package org.sopt.kareer.domain.term.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.sopt.kareer.domain.term.dto.response.TermsResponse; +import org.sopt.kareer.global.response.BaseResponse; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; + +@Tag(name = "Term API") +public interface TermApi { + + @GetMapping + @Operation(summary = "약관 조회", description = "약관 내용을 조회합니다.") + ResponseEntity> getTerms(); +} diff --git a/src/main/java/org/sopt/kareer/domain/term/controller/TermController.java b/src/main/java/org/sopt/kareer/domain/term/controller/TermController.java index 91d534f..c6d45f6 100644 --- a/src/main/java/org/sopt/kareer/domain/term/controller/TermController.java +++ b/src/main/java/org/sopt/kareer/domain/term/controller/TermController.java @@ -1,30 +1,24 @@ package org.sopt.kareer.domain.term.controller; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.sopt.kareer.domain.term.dto.response.TermsResponse; import org.sopt.kareer.domain.term.service.TermService; import org.sopt.kareer.global.response.BaseResponse; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequiredArgsConstructor @RequestMapping("/api/v1/terms") -@Tag(name = "Term API") -public class TermController { +public class TermController implements TermApi { private final TermService termService; - @GetMapping - @Operation(summary = "약관 조회", description = "약관 내용을 조회합니다.") + @Override public ResponseEntity> getTerms() { - return ResponseEntity - .status(HttpStatus.OK) + return ResponseEntity.status(HttpStatus.OK) .body(BaseResponse.ok(termService.getTerms(), "약관 조회에 성공했습니다.")); } } From faee36904732e87d7006116ff6cee1e4aa32c50f Mon Sep 17 00:00:00 2001 From: dlwjddus1112 Date: Wed, 15 Apr 2026 18:20:16 +0900 Subject: [PATCH 08/19] =?UTF-8?q?[Refactor]=20#238=20=EB=A1=9C=EB=93=9C?= =?UTF-8?q?=EB=A7=B5=20=EA=B4=80=EB=A0=A8=20=EC=BB=A8=ED=8A=B8=EB=A1=A4?= =?UTF-8?q?=EB=9F=AC=20=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=EB=A1=9C=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../roadmap/controller/ActionItemApi.java | 29 ++++++++++++++ .../controller/ActionItemController.java | 29 +++++--------- .../roadmap/controller/PhaseActionApi.java | 33 +++++++++++++++ .../controller/PhaseActionController.java | 34 +++++----------- .../domain/roadmap/controller/PhaseApi.java | 36 +++++++++++++++++ .../roadmap/controller/PhaseController.java | 40 +++++-------------- .../domain/roadmap/controller/RoadmapApi.java | 26 ++++++++++++ .../roadmap/controller/RoadmapController.java | 17 ++------ 8 files changed, 157 insertions(+), 87 deletions(-) create mode 100644 src/main/java/org/sopt/kareer/domain/roadmap/controller/ActionItemApi.java create mode 100644 src/main/java/org/sopt/kareer/domain/roadmap/controller/PhaseActionApi.java create mode 100644 src/main/java/org/sopt/kareer/domain/roadmap/controller/PhaseApi.java create mode 100644 src/main/java/org/sopt/kareer/domain/roadmap/controller/RoadmapApi.java diff --git a/src/main/java/org/sopt/kareer/domain/roadmap/controller/ActionItemApi.java b/src/main/java/org/sopt/kareer/domain/roadmap/controller/ActionItemApi.java new file mode 100644 index 0000000..de10a0c --- /dev/null +++ b/src/main/java/org/sopt/kareer/domain/roadmap/controller/ActionItemApi.java @@ -0,0 +1,29 @@ +package org.sopt.kareer.domain.roadmap.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.sopt.kareer.domain.roadmap.dto.response.ActionItemListResponse; +import org.sopt.kareer.global.annotation.CustomExceptionDescription; +import org.sopt.kareer.global.config.swagger.SwaggerResponseDescription; +import org.sopt.kareer.global.response.BaseResponse; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; + +@Tag(name = "Action Item API", description = "Action Item 관련 API") +public interface ActionItemApi { + + @Operation(summary = "액션 아이템 완료 상태 토글", description = "특정 액션 아이템의 완료 상태를 토글합니다.") + @CustomExceptionDescription(SwaggerResponseDescription.TOGGLE_ACTION_ITEM_COMPLETION) + @PatchMapping("/{actionItemId}/completed") + ResponseEntity> toggleActionItemCompletion( + @AuthenticationPrincipal Long memberId, + @PathVariable Long actionItemId); + + @Operation(summary = "액션 아이템 전체 조회", description = "로그인한 회원의 활성화된 모든 액션 아이템을 조회합니다.") + @CustomExceptionDescription(SwaggerResponseDescription.GET_ALL_ACTION_ITEMS) + @GetMapping + ResponseEntity> getAllActionItems(@AuthenticationPrincipal Long memberId); +} diff --git a/src/main/java/org/sopt/kareer/domain/roadmap/controller/ActionItemController.java b/src/main/java/org/sopt/kareer/domain/roadmap/controller/ActionItemController.java index b26de7d..6b75655 100644 --- a/src/main/java/org/sopt/kareer/domain/roadmap/controller/ActionItemController.java +++ b/src/main/java/org/sopt/kareer/domain/roadmap/controller/ActionItemController.java @@ -1,45 +1,36 @@ package org.sopt.kareer.domain.roadmap.controller; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.sopt.kareer.domain.roadmap.dto.response.ActionItemListResponse; import org.sopt.kareer.domain.roadmap.service.ActionItemService; -import org.sopt.kareer.global.annotation.CustomExceptionDescription; -import org.sopt.kareer.global.config.swagger.SwaggerResponseDescription; import org.sopt.kareer.global.response.BaseResponse; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -@Tag(name = "Action Item API", description = "Action Item 관련 API") @RestController -@RequestMapping("/api/v1/roadmap/action-items") @RequiredArgsConstructor -public class ActionItemController { +@RequestMapping("/api/v1/roadmap/action-items") +public class ActionItemController implements ActionItemApi { private final ActionItemService actionItemService; - @PatchMapping("/{actionItemId}/completed") - @Operation(summary = "액션 아이템 완료 상태 토글", description = "특정 액션 아이템의 완료 상태를 토글합니다.") - @CustomExceptionDescription(SwaggerResponseDescription.TOGGLE_ACTION_ITEM_COMPLETION) - public ResponseEntity> toggleActionItemCompletion(@AuthenticationPrincipal Long memberId, - @PathVariable Long actionItemId) { + @Override + public ResponseEntity> toggleActionItemCompletion( + @AuthenticationPrincipal Long memberId, + @PathVariable Long actionItemId) { + actionItemService.toggleCompletion(memberId, actionItemId); return ResponseEntity.ok(BaseResponse.ok("액션 아이템 완료 상태가 토글되었습니다.")); } - @GetMapping("") - @Operation(summary = "액션 아이템 전체 조회", description = "로그인한 회원의 활성화된 모든 액션 아이템을 조회합니다.") - @CustomExceptionDescription(SwaggerResponseDescription.GET_ALL_ACTION_ITEMS) + @Override public ResponseEntity> getAllActionItems( @AuthenticationPrincipal Long memberId) { + return ResponseEntity.ok( - BaseResponse.ok(actionItemService.getAllActionItems(memberId), "모든 액션 아이템이 조회되었습니다.") - ); + BaseResponse.ok(actionItemService.getAllActionItems(memberId), "모든 액션 아이템이 조회되었습니다.")); } } diff --git a/src/main/java/org/sopt/kareer/domain/roadmap/controller/PhaseActionApi.java b/src/main/java/org/sopt/kareer/domain/roadmap/controller/PhaseActionApi.java new file mode 100644 index 0000000..c6773c8 --- /dev/null +++ b/src/main/java/org/sopt/kareer/domain/roadmap/controller/PhaseActionApi.java @@ -0,0 +1,33 @@ +package org.sopt.kareer.domain.roadmap.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.sopt.kareer.domain.roadmap.dto.response.AiGuideResponse; +import org.sopt.kareer.global.annotation.CustomExceptionDescription; +import org.sopt.kareer.global.config.swagger.SwaggerResponseDescription; +import org.sopt.kareer.global.response.BaseResponse; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; + +import static org.sopt.kareer.global.config.swagger.SwaggerResponseDescription.CREATE_TODO; + +@Tag(name = "Phase Action API", description = "Phase Action 관련 API") +public interface PhaseActionApi { + + @Operation(summary = "Phase Action 기반 Todo 생성", description = "특정 Phase Action을 기반으로 Todo를 생성합니다.") + @CustomExceptionDescription(CREATE_TODO) + @PostMapping("/{phaseActionId}/todo") + ResponseEntity> createPhaseActionTodo( + @AuthenticationPrincipal Long memberId, + @PathVariable Long phaseActionId); + + @Operation(summary = "AI 가이드 조회", description = "AI 가이드를 조회합니다.") + @CustomExceptionDescription(SwaggerResponseDescription.AI_GUIDE) + @GetMapping("/{phaseActionId}/guide") + ResponseEntity> getAiGuide( + @AuthenticationPrincipal Long memberId, + @PathVariable Long phaseActionId); +} diff --git a/src/main/java/org/sopt/kareer/domain/roadmap/controller/PhaseActionController.java b/src/main/java/org/sopt/kareer/domain/roadmap/controller/PhaseActionController.java index 902349e..4fc864a 100644 --- a/src/main/java/org/sopt/kareer/domain/roadmap/controller/PhaseActionController.java +++ b/src/main/java/org/sopt/kareer/domain/roadmap/controller/PhaseActionController.java @@ -1,52 +1,38 @@ package org.sopt.kareer.domain.roadmap.controller; -import static org.sopt.kareer.global.config.swagger.SwaggerResponseDescription.*; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.sopt.kareer.domain.roadmap.dto.response.AiGuideResponse; import org.sopt.kareer.domain.roadmap.service.PhaseActionService; -import org.sopt.kareer.domain.roadmap.service.PhaseService; -import org.sopt.kareer.global.annotation.CustomExceptionDescription; -import org.sopt.kareer.global.config.swagger.SwaggerResponseDescription; import org.sopt.kareer.global.response.BaseResponse; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; -@Tag(name = "Phase Action API", description = "Phase Action 관련 API") @RestController @RequiredArgsConstructor @RequestMapping("/api/v1/roadmap/phase-actions") -public class PhaseActionController { +public class PhaseActionController implements PhaseActionApi { private final PhaseActionService phaseActionService; - private final PhaseService phaseService; - @PostMapping("/{phaseActionId}/todo") - @Operation(summary = "Phase Action 기반 Todo 생성", description = "특정 Phase Action을 기반으로 Todo를 생성합니다.") - @CustomExceptionDescription(CREATE_TODO) + @Override public ResponseEntity> createPhaseActionTodo( @AuthenticationPrincipal Long memberId, - @PathVariable Long phaseActionId - ) { + @PathVariable Long phaseActionId) { + phaseActionService.createPhaseActionTodo(memberId, phaseActionId); return ResponseEntity.ok(BaseResponse.ok("Phase Action 기반 Todo가 생성되었습니다.")); } - @GetMapping("/{phaseActionId}/guide") - @Operation(summary = "AI 가이드 조회", description = "AI 가이드를 조회합니다.") - @CustomExceptionDescription(SwaggerResponseDescription.AI_GUIDE) + @Override public ResponseEntity> getAiGuide( @AuthenticationPrincipal Long memberId, - @PathVariable Long phaseActionId - ) { - AiGuideResponse response = phaseActionService.getAiGuide(memberId, phaseActionId); + @PathVariable Long phaseActionId) { return ResponseEntity.status(HttpStatus.OK) - .body(BaseResponse.ok(response, "AI 가이드가 조회되었습니다.") - ); + .body(BaseResponse.ok(phaseActionService.getAiGuide(memberId, phaseActionId), "AI 가이드가 조회되었습니다.")); } } diff --git a/src/main/java/org/sopt/kareer/domain/roadmap/controller/PhaseApi.java b/src/main/java/org/sopt/kareer/domain/roadmap/controller/PhaseApi.java new file mode 100644 index 0000000..3a42114 --- /dev/null +++ b/src/main/java/org/sopt/kareer/domain/roadmap/controller/PhaseApi.java @@ -0,0 +1,36 @@ +package org.sopt.kareer.domain.roadmap.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.sopt.kareer.domain.roadmap.dto.response.HomePhaseDetailResponse; +import org.sopt.kareer.domain.roadmap.dto.response.PhaseListResponse; +import org.sopt.kareer.domain.roadmap.dto.response.RoadmapPhaseDetailResponse; +import org.sopt.kareer.global.annotation.CustomExceptionDescription; +import org.sopt.kareer.global.config.swagger.SwaggerResponseDescription; +import org.sopt.kareer.global.response.BaseResponse; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; + +@Tag(name = "Phase API", description = "Phase 관련 API") +public interface PhaseApi { + + @Operation(summary = "Phase 리스트 조회", description = "Phase 리스트를 조회합니다.") + @GetMapping + ResponseEntity> getPhaseList(@AuthenticationPrincipal Long memberId); + + @Operation(summary = "로드맵 Phase 상세정보 조회", description = "로드맵 Phase 상세조회를 조회합니다.") + @CustomExceptionDescription(SwaggerResponseDescription.ROADMAP_PHASE_LIST_DETAIL) + @GetMapping("/{phaseId}") + ResponseEntity> getRoadmapPhaseDetail( + @AuthenticationPrincipal Long memberId, + @PathVariable Long phaseId); + + @Operation(summary = "홈 Phase 상세정보 조회", description = "홈 Phase 상세조회를 조회합니다.") + @CustomExceptionDescription(SwaggerResponseDescription.HOME_PHASE_LIST_DETAIL) + @GetMapping("/{phaseId}/home") + ResponseEntity> getHomePhaseDetail( + @AuthenticationPrincipal Long memberId, + @PathVariable Long phaseId); +} diff --git a/src/main/java/org/sopt/kareer/domain/roadmap/controller/PhaseController.java b/src/main/java/org/sopt/kareer/domain/roadmap/controller/PhaseController.java index 6181ae5..ca28ef3 100644 --- a/src/main/java/org/sopt/kareer/domain/roadmap/controller/PhaseController.java +++ b/src/main/java/org/sopt/kareer/domain/roadmap/controller/PhaseController.java @@ -1,68 +1,48 @@ package org.sopt.kareer.domain.roadmap.controller; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.sopt.kareer.domain.roadmap.dto.response.HomePhaseDetailResponse; import org.sopt.kareer.domain.roadmap.dto.response.PhaseListResponse; import org.sopt.kareer.domain.roadmap.dto.response.RoadmapPhaseDetailResponse; import org.sopt.kareer.domain.roadmap.service.PhaseService; -import org.sopt.kareer.global.annotation.CustomExceptionDescription; -import org.sopt.kareer.global.config.swagger.SwaggerResponseDescription; import org.sopt.kareer.global.response.BaseResponse; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -@Tag(name="Phase API", description = "Phase 관련 API") @RestController @RequiredArgsConstructor @RequestMapping("/api/v1/roadmap/phases") -public class PhaseController { +public class PhaseController implements PhaseApi { private final PhaseService phaseService; - @GetMapping - @Operation(summary = "Phase 리스트 조회", description = "Phase 리스트를 조회합니다.") + @Override public ResponseEntity> getPhaseList( - @AuthenticationPrincipal Long memberId - ) { - PhaseListResponse response = phaseService.getPhases(memberId); + @AuthenticationPrincipal Long memberId) { return ResponseEntity.status(HttpStatus.OK) - .body(BaseResponse.ok(response, "Phase 리스트가 조회되었습니다.") - ); + .body(BaseResponse.ok(phaseService.getPhases(memberId), "Phase 리스트가 조회되었습니다.")); } - @GetMapping("/{phaseId}") - @Operation(summary = "로드맵 Phase 상세정보 조회", description = "로드맵 Phase 상세조회를 조회합니다.") - @CustomExceptionDescription(SwaggerResponseDescription.ROADMAP_PHASE_LIST_DETAIL) + @Override public ResponseEntity> getRoadmapPhaseDetail( @AuthenticationPrincipal Long memberId, - @PathVariable Long phaseId - ) { - RoadmapPhaseDetailResponse response = phaseService.getRoadmapPhaseDetail(memberId, phaseId); + @PathVariable Long phaseId) { return ResponseEntity.status(HttpStatus.OK) - .body(BaseResponse.ok(response, "로드맵 Phase 상세정보가 조회되었습니다.") - ); + .body(BaseResponse.ok(phaseService.getRoadmapPhaseDetail(memberId, phaseId), "로드맵 Phase 상세정보가 조회되었습니다.")); } - @GetMapping("/{phaseId}/home") - @Operation(summary = "홈 Phase 상세정보 조회", description = "홈 Phase 상세조회를 조회합니다.") - @CustomExceptionDescription(SwaggerResponseDescription.HOME_PHASE_LIST_DETAIL) + @Override public ResponseEntity> getHomePhaseDetail( @AuthenticationPrincipal Long memberId, - @PathVariable Long phaseId - ) { - HomePhaseDetailResponse response = phaseService.getHomePhaseDetail(memberId, phaseId); + @PathVariable Long phaseId) { return ResponseEntity.status(HttpStatus.OK) - .body(BaseResponse.ok(response, "홈 Phase 상세정보가 조회되었습니다.") - ); + .body(BaseResponse.ok(phaseService.getHomePhaseDetail(memberId, phaseId), "홈 Phase 상세정보가 조회되었습니다.")); } } diff --git a/src/main/java/org/sopt/kareer/domain/roadmap/controller/RoadmapApi.java b/src/main/java/org/sopt/kareer/domain/roadmap/controller/RoadmapApi.java new file mode 100644 index 0000000..57e6155 --- /dev/null +++ b/src/main/java/org/sopt/kareer/domain/roadmap/controller/RoadmapApi.java @@ -0,0 +1,26 @@ +package org.sopt.kareer.domain.roadmap.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.sopt.kareer.domain.roadmap.dto.response.RoadmapTestResponse; +import org.sopt.kareer.global.annotation.CustomExceptionDescription; +import org.sopt.kareer.global.response.BaseResponse; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.PostMapping; + +import static org.sopt.kareer.global.config.swagger.SwaggerResponseDescription.CREATE_ROADMAP; + +@Tag(name = "Roadmap API", description = "Roadmap 관련 API") +public interface RoadmapApi { + + @Operation(summary = "AI 로드맵 생성", description = "사용자가 온보딩에 입력한 정보를 통해 로드맵을 생성합니다.") + @CustomExceptionDescription(CREATE_ROADMAP) + @PostMapping + ResponseEntity> generateRoadmap(@AuthenticationPrincipal Long memberId); + + @Operation(summary = "AI 로드맵 생성 테스트용 (Server Only)", description = "사용자가 온보딩에 입력한 정보를 통해 로드맵을 생성합니다.") + @CustomExceptionDescription(CREATE_ROADMAP) + @PostMapping("/test") + ResponseEntity> generateRoadmapForTest(@AuthenticationPrincipal Long memberId); +} diff --git a/src/main/java/org/sopt/kareer/domain/roadmap/controller/RoadmapController.java b/src/main/java/org/sopt/kareer/domain/roadmap/controller/RoadmapController.java index d7c039e..9a5893e 100644 --- a/src/main/java/org/sopt/kareer/domain/roadmap/controller/RoadmapController.java +++ b/src/main/java/org/sopt/kareer/domain/roadmap/controller/RoadmapController.java @@ -1,34 +1,25 @@ package org.sopt.kareer.domain.roadmap.controller; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.sopt.kareer.domain.roadmap.dto.response.RoadmapTestResponse; import org.sopt.kareer.domain.roadmap.service.RoadMapService; import org.sopt.kareer.domain.roadmap.service.RoadmapTranslationService; -import org.sopt.kareer.global.annotation.CustomExceptionDescription; import org.sopt.kareer.global.response.BaseResponse; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import static org.sopt.kareer.global.config.swagger.SwaggerResponseDescription.CREATE_ROADMAP; - -@Tag(name = "Roadmap API", description = "Roadmap 관련 API") @RestController @RequiredArgsConstructor @RequestMapping("/api/v1/roadmap") -public class RoadmapController { +public class RoadmapController implements RoadmapApi { private final RoadMapService roadMapService; private final RoadmapTranslationService roadmapTranslationService; - @Operation(summary = "AI 로드맵 생성", description = "사용자가 온보딩에 입력한 정보를 통해 로드맵을 생성합니다.") - @CustomExceptionDescription(CREATE_ROADMAP) - @PostMapping + @Override public ResponseEntity> generateRoadmap( @AuthenticationPrincipal Long memberId) { @@ -39,9 +30,7 @@ public ResponseEntity> generateRoadmap( .body(BaseResponse.ok("AI 로드맵 생성에 성공하였습니다.")); } - @Operation(summary = "AI 로드맵 생성 테스트용 (Server Only)", description = "사용자가 온보딩에 입력한 정보를 통해 로드맵을 생성합니다.") - @CustomExceptionDescription(CREATE_ROADMAP) - @PostMapping("/test") + @Override public ResponseEntity> generateRoadmapForTest( @AuthenticationPrincipal Long memberId) { From fa544db7aa7cfff18109c64b4a6923c9fb916116 Mon Sep 17 00:00:00 2001 From: dlwjddus1112 Date: Wed, 15 Apr 2026 18:20:40 +0900 Subject: [PATCH 09/19] =?UTF-8?q?[Refactor]=20#238=20MemberController=20?= =?UTF-8?q?=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20=EC=9D=B8?= =?UTF-8?q?=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=EB=A1=9C=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/member/controller/MemberApi.java | 94 ++++++++++++++++ .../domain/member/controller/MemberApiV2.java | 23 ++++ .../member/controller/MemberController.java | 104 +++++------------- .../member/controller/MemberControllerV2.java | 19 +--- 4 files changed, 151 insertions(+), 89 deletions(-) create mode 100644 src/main/java/org/sopt/kareer/domain/member/controller/MemberApi.java create mode 100644 src/main/java/org/sopt/kareer/domain/member/controller/MemberApiV2.java diff --git a/src/main/java/org/sopt/kareer/domain/member/controller/MemberApi.java b/src/main/java/org/sopt/kareer/domain/member/controller/MemberApi.java new file mode 100644 index 0000000..b564923 --- /dev/null +++ b/src/main/java/org/sopt/kareer/domain/member/controller/MemberApi.java @@ -0,0 +1,94 @@ +package org.sopt.kareer.domain.member.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import org.sopt.kareer.domain.member.dto.request.MemberOnboardRequest; +import org.sopt.kareer.domain.member.dto.request.MemberTermsRequest; +import org.sopt.kareer.domain.member.dto.request.MypageRequest; +import org.sopt.kareer.domain.member.dto.response.*; +import org.sopt.kareer.global.annotation.CustomExceptionDescription; +import org.sopt.kareer.global.config.swagger.SwaggerResponseDescription; +import org.sopt.kareer.global.response.BaseResponse; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import static org.sopt.kareer.global.config.swagger.SwaggerResponseDescription.*; + +@Tag(name = "Member API") +public interface MemberApi { + + @GetMapping("/me") + @Operation(summary = "회원 정보 조회", description = "로그인한 회원의 정보를 조회합니다.") + @CustomExceptionDescription(SwaggerResponseDescription.MEMBER_INFO) + ResponseEntity> getMemberInfo(@AuthenticationPrincipal Long memberId); + + @PostMapping("/onboard") + @Operation(summary = "회원 온보딩", description = "PENDING 상태의 회원의 온보딩 결과를 저장합니다.") + @CustomExceptionDescription(SwaggerResponseDescription.MEMBER_ONBOARD) + ResponseEntity> onboardMember(@AuthenticationPrincipal Long memberId, + @Valid @RequestBody MemberOnboardRequest request); + + @GetMapping("/onboard/universities") + @Operation(summary = "온보딩 대학교 목록 조회", description = "회원 온보딩 시 선택할 수 있는 대학교 목록을 조회합니다.") + ResponseEntity> getOnboardUniversities(); + + @GetMapping("/onboard/countries") + @Operation(summary = "온보딩 국가 목록 조회", description = "회원 온보딩 시 선택할 수 있는 국가 목록을 조회합니다.") + ResponseEntity> getOnboardCountries(); + + @GetMapping("/onboard/majors") + @Operation(summary = "온보딩 전공 목록 조회", description = "회원 온보딩 시 선택할 수 있는 전공 목록을 조회합니다.") + ResponseEntity> getOnboardMajors(); + + @GetMapping("/onboard/fields") + @Operation(summary = "온보딩 관심 분야 목록 조회", description = "회원 온보딩 시 선택할 수 있는 관심 분야 목록을 조회합니다.") + ResponseEntity> getOnboardFields(); + + @GetMapping("me/status") + @Operation(summary = "유저 상태 조회", description = "사용자의 비자 정보, 졸업 정보를 조회합니다.") + @CustomExceptionDescription(USER_STATUS) + ResponseEntity> getMemberStatus(@AuthenticationPrincipal Long memberId); + + @GetMapping("mypage") + @Operation(summary = "마이페이지 조회", description = "마이페이지에서 유저 정보를 조회합니다.") + @CustomExceptionDescription(GET_MYPAGE) + ResponseEntity> getMypage(@AuthenticationPrincipal Long memberId); + + @PutMapping("mypage") + @Operation(summary = "마이페이지 수정", description = "마이페이지에서 유저 프로필을 수정합니다.") + @CustomExceptionDescription(UPDATE_MYPAGE) + ResponseEntity> updateMypage(@AuthenticationPrincipal Long memberId, + @Valid @RequestBody MypageRequest request); + + @DeleteMapping("/me") + @Operation(summary = "회원 탈퇴", description = "회원 탈퇴를 진행합니다.") + @CustomExceptionDescription(MEMBER_DELETE) + ResponseEntity> deleteMember(@AuthenticationPrincipal Long memberId, + HttpServletRequest request, + HttpServletResponse response); + + @PostMapping(value = "/onboard/ocr/visa", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + @Operation(summary = "온보딩 비자 OCR API", description = "온보딩 과정에서 유저의 비자 문서를 분석하여 정보를 추출합니다.") + ResponseEntity> getVisaInfo(@RequestPart("file") MultipartFile file); + + @PostMapping(value = "/onboard/ocr/passport", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + @Operation(summary = "온보딩 여권 OCR API", description = "온보딩 과정에서 유저의 여권을 분석하여 정보를 추출합니다.") + ResponseEntity> getPassportInfo(@RequestPart("file") MultipartFile file); + + @PostMapping("/term-agreements") + @Operation(summary = "약관 동의", description = "약관에 동의합니다.") + @CustomExceptionDescription(TERM_AGREE) + ResponseEntity> agreeTerms(@AuthenticationPrincipal Long memberId, + @RequestBody @Valid MemberTermsRequest request); + + @GetMapping("completion") + @Operation(summary = "온보딩, 약관동의 여부 조회") + @CustomExceptionDescription(GET_COMPLETION) + ResponseEntity> getMemberCompletionStatus(@AuthenticationPrincipal Long memberId); +} diff --git a/src/main/java/org/sopt/kareer/domain/member/controller/MemberApiV2.java b/src/main/java/org/sopt/kareer/domain/member/controller/MemberApiV2.java new file mode 100644 index 0000000..91c5914 --- /dev/null +++ b/src/main/java/org/sopt/kareer/domain/member/controller/MemberApiV2.java @@ -0,0 +1,23 @@ +package org.sopt.kareer.domain.member.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import org.sopt.kareer.domain.member.dto.request.MemberOnboardV2Request; +import org.sopt.kareer.global.annotation.CustomExceptionDescription; +import org.sopt.kareer.global.config.swagger.SwaggerResponseDescription; +import org.sopt.kareer.global.response.BaseResponse; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; + +@Tag(name = "Member API V2", description = "회원 API 버전 2") +public interface MemberApiV2 { + + @PostMapping("/onboard") + @Operation(summary = "회원 온보딩 V2", description = "PENDING 상태의 회원의 온보딩 결과를 저장합니다.") + @CustomExceptionDescription(SwaggerResponseDescription.MEMBER_ONBOARD) + ResponseEntity> onboardMember(@AuthenticationPrincipal Long memberId, + @Valid @RequestBody MemberOnboardV2Request request); +} diff --git a/src/main/java/org/sopt/kareer/domain/member/controller/MemberController.java b/src/main/java/org/sopt/kareer/domain/member/controller/MemberController.java index 9efa90f..af3cc43 100644 --- a/src/main/java/org/sopt/kareer/domain/member/controller/MemberController.java +++ b/src/main/java/org/sopt/kareer/domain/member/controller/MemberController.java @@ -1,8 +1,5 @@ package org.sopt.kareer.domain.member.controller; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; @@ -13,103 +10,74 @@ import org.sopt.kareer.domain.member.dto.response.*; import org.sopt.kareer.domain.member.service.LocalizedOnboardQueryService; import org.sopt.kareer.domain.member.service.MemberService; -import org.sopt.kareer.global.annotation.CustomExceptionDescription; import org.sopt.kareer.global.auth.service.AuthService; -import org.sopt.kareer.global.config.swagger.SwaggerResponseDescription; import org.sopt.kareer.global.response.BaseResponse; import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; -import static org.sopt.kareer.global.config.swagger.SwaggerResponseDescription.*; - @RestController @RequiredArgsConstructor @RequestMapping("/api/v1/members") -@Tag(name = "Member API") -public class MemberController { +public class MemberController implements MemberApi { private final MemberService memberService; private final AuthService authService; private final LocalizedOnboardQueryService localizedOnboardQueryService; - @GetMapping("/me") - @Operation(summary = "회원 정보 조회", description = "로그인한 회원의 정보를 조회합니다.") - @CustomExceptionDescription(SwaggerResponseDescription.MEMBER_INFO) + @Override public ResponseEntity> getMemberInfo(@AuthenticationPrincipal Long memberId) { - return ResponseEntity - .status(HttpStatus.OK) + return ResponseEntity.status(HttpStatus.OK) .body(BaseResponse.ok(memberService.getMemberInfo(memberId), "회원 정보 조회에 성공하였습니다.")); } - @PostMapping("/onboard") - @Operation(summary = "회원 온보딩", description = "PENDING 상태의 회원의 온보딩 결과를 저장합니다.") - @CustomExceptionDescription(SwaggerResponseDescription.MEMBER_ONBOARD) + @Override public ResponseEntity> onboardMember(@AuthenticationPrincipal Long memberId, @Valid @RequestBody MemberOnboardRequest request) { memberService.onboardMember(request, memberId); - return ResponseEntity - .status(HttpStatus.OK) + return ResponseEntity.status(HttpStatus.OK) .body(BaseResponse.ok("회원 온보딩이 완료되었습니다.")); } - @GetMapping("/onboard/universities") - @Operation(summary = "온보딩 대학교 목록 조회", description = "회원 온보딩 시 선택할 수 있는 대학교 목록을 조회합니다.") + @Override public ResponseEntity> getOnboardUniversities() { - return ResponseEntity - .status(HttpStatus.OK) - .body(BaseResponse.ok(localizedOnboardQueryService.getUniversities(), - "온보딩 대학교 목록 조회에 성공하였습니다.")); + return ResponseEntity.status(HttpStatus.OK) + .body(BaseResponse.ok(localizedOnboardQueryService.getUniversities(), "온보딩 대학교 목록 조회에 성공하였습니다.")); } - @GetMapping("/onboard/countries") - @Operation(summary = "온보딩 국가 목록 조회", description = "회원 온보딩 시 선택할 수 있는 국가 목록을 조회합니다.") + @Override public ResponseEntity> getOnboardCountries() { - return ResponseEntity - .status(HttpStatus.OK) - .body(BaseResponse.ok( - localizedOnboardQueryService.getCountries(), - "온보딩 국가 목록 조회에 성공하였습니다.")); + return ResponseEntity.status(HttpStatus.OK) + .body(BaseResponse.ok(localizedOnboardQueryService.getCountries(), "온보딩 국가 목록 조회에 성공하였습니다.")); } - @GetMapping("/onboard/majors") - @Operation(summary = "온보딩 전공 목록 조회", description = "회원 온보딩 시 선택할 수 있는 전공 목록을 조회합니다.") + @Override public ResponseEntity> getOnboardMajors() { - return ResponseEntity - .status(HttpStatus.OK) + return ResponseEntity.status(HttpStatus.OK) .body(BaseResponse.ok(localizedOnboardQueryService.getMajors(), "온보딩 전공 목록 조회에 성공하였습니다.")); } - @GetMapping("/onboard/fields") - @Operation(summary = "온보딩 관심 분야 목록 조회", description = "회원 온보딩 시 선택할 수 있는 관심 분야 목록을 조회합니다.") + @Override public ResponseEntity> getOnboardFields() { - return ResponseEntity - .status(HttpStatus.OK) + return ResponseEntity.status(HttpStatus.OK) .body(BaseResponse.ok(localizedOnboardQueryService.getFields(), "온보딩 관심 분야 목록 조회에 성공하였습니다.")); } - @Operation(summary = "유저 상태 조회", description = "사용자의 비자 정보, 졸업 정보를 조회합니다.") - @CustomExceptionDescription(USER_STATUS) - @GetMapping("me/status") + @Override public ResponseEntity> getMemberStatus(@AuthenticationPrincipal Long memberId) { return ResponseEntity.status(HttpStatus.OK) .body(BaseResponse.ok(memberService.getMemberStatus(memberId), "My status 조회에 성공하였습니다.")); } - @Operation(summary = "마이페이지 조회", description = "마이페이지에서 유저 정보를 조회합니다.") - @CustomExceptionDescription(GET_MYPAGE) - @GetMapping("mypage") + @Override public ResponseEntity> getMypage(@AuthenticationPrincipal Long memberId) { return ResponseEntity.status(HttpStatus.OK) .body(BaseResponse.ok(memberService.getMypage(memberId), "마이페이지 조회에 성공하였습니다.")); } - @Operation(summary = "마이페이지 수정", description = "마이페이지에서 유저 프로필을 수정합니다.") - @CustomExceptionDescription(UPDATE_MYPAGE) - @PutMapping("mypage") + @Override public ResponseEntity> updateMypage(@AuthenticationPrincipal Long memberId, @Valid @RequestBody MypageRequest request) { memberService.updateMypage(memberId, request.toCommand()); @@ -117,9 +85,7 @@ public ResponseEntity> updateMypage(@AuthenticationPrincipal .body(BaseResponse.ok("마이페이지 수정에 성공하였습니다.")); } - @Operation(summary = "회원 탈퇴", description = "회원 탈퇴를 진행합니다.") - @CustomExceptionDescription(MEMBER_DELETE) - @DeleteMapping("/me") + @Override public ResponseEntity> deleteMember(@AuthenticationPrincipal Long memberId, HttpServletRequest request, HttpServletResponse response) { @@ -129,43 +95,29 @@ public ResponseEntity> deleteMember(@AuthenticationPrincipal .body(BaseResponse.ok("회원 탈퇴에 성공하였습니다.")); } - @Operation(summary = "온보딩 비자 OCR API", description = "온보딩 과정에서 유저의 비자 문서를 분석하여 정보를 추출합니다.") - @PostMapping(value = "/onboard/ocr/visa", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public ResponseEntity> getVisaInfo( - @RequestPart("file") MultipartFile file){ + @Override + public ResponseEntity> getVisaInfo(@RequestPart("file") MultipartFile file) { return ResponseEntity.status(HttpStatus.OK) .body(BaseResponse.ok(memberService.getVisaOcr(file), "사용자 비자 정보 추출에 성공했습니다.")); } - @Operation(summary = "온보딩 여권 OCR API", description = "온보딩 과정에서 유저의 여권을 분석하여 정보를 추출합니다.") - @PostMapping(value = "/onboard/ocr/passport", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public ResponseEntity> getPassportInfo( - @RequestPart("file") MultipartFile file){ + @Override + public ResponseEntity> getPassportInfo(@RequestPart("file") MultipartFile file) { return ResponseEntity.status(HttpStatus.OK) .body(BaseResponse.ok(memberService.getPassportOcr(file), "사용자 여권 정보 추출에 성공했습니다.")); } - - @Operation(summary = "약관 동의", description = "약관에 동의합니다.") - @CustomExceptionDescription(TERM_AGREE) - @PostMapping("/term-agreements") - public ResponseEntity> agreeTerms( - @AuthenticationPrincipal Long memberId, - @RequestBody @Valid MemberTermsRequest request - ) { + @Override + public ResponseEntity> agreeTerms(@AuthenticationPrincipal Long memberId, + @RequestBody @Valid MemberTermsRequest request) { memberService.agreeTerms(memberId, request); return ResponseEntity.status(HttpStatus.OK) .body(BaseResponse.ok("약관 동의 저장에 성공했습니다.")); } - @Operation(summary = "온보딩, 약관동의 여부 조회") - @CustomExceptionDescription(GET_COMPLETION) - @GetMapping("completion") - public ResponseEntity> getMemberCompletionStatus( - @AuthenticationPrincipal Long memberId - ){ + @Override + public ResponseEntity> getMemberCompletionStatus(@AuthenticationPrincipal Long memberId) { return ResponseEntity.status(HttpStatus.OK) .body(BaseResponse.ok(memberService.getCompletion(memberId), "온보딩/약관동의 여부 조회에 성공하였습니다,")); } - } diff --git a/src/main/java/org/sopt/kareer/domain/member/controller/MemberControllerV2.java b/src/main/java/org/sopt/kareer/domain/member/controller/MemberControllerV2.java index c4bb16c..26eed44 100644 --- a/src/main/java/org/sopt/kareer/domain/member/controller/MemberControllerV2.java +++ b/src/main/java/org/sopt/kareer/domain/member/controller/MemberControllerV2.java @@ -1,34 +1,27 @@ package org.sopt.kareer.domain.member.controller; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; -import org.sopt.kareer.domain.member.dto.request.*; +import org.sopt.kareer.domain.member.dto.request.MemberOnboardV2Request; import org.sopt.kareer.domain.member.service.MemberService; -import org.sopt.kareer.global.annotation.CustomExceptionDescription; -import org.sopt.kareer.global.config.swagger.SwaggerResponseDescription; import org.sopt.kareer.global.response.BaseResponse; -import org.springframework.http.*; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; @RestController @RequiredArgsConstructor @RequestMapping("/api/v2/members") -@Tag(name = "Member API V2", description = "회원 API 버전 2") -public class MemberControllerV2 { +public class MemberControllerV2 implements MemberApiV2 { private final MemberService memberService; - @PostMapping("/onboard") - @Operation(summary = "회원 온보딩 V2", description = "PENDING 상태의 회원의 온보딩 결과를 저장합니다.") - @CustomExceptionDescription(SwaggerResponseDescription.MEMBER_ONBOARD) + @Override public ResponseEntity> onboardMember(@AuthenticationPrincipal Long memberId, @Valid @RequestBody MemberOnboardV2Request request) { memberService.onboardMemberV2(request, memberId); - return ResponseEntity - .status(HttpStatus.OK) + return ResponseEntity.status(HttpStatus.OK) .body(BaseResponse.ok("회원 온보딩이 완료되었습니다.")); } } From 587ceb20803409e3dce96f9e7301bb346ee47947 Mon Sep 17 00:00:00 2001 From: dlwjddus1112 Date: Wed, 15 Apr 2026 18:21:04 +0900 Subject: [PATCH 10/19] =?UTF-8?q?[Refactor]=20#238=20JobPostingController?= =?UTF-8?q?=20=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20=EC=9D=B8?= =?UTF-8?q?=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=EB=A1=9C=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jobposting/controller/JobPostingApi.java | 45 +++++++++++++++++++ .../controller/JobPostingController.java | 35 ++++----------- 2 files changed, 53 insertions(+), 27 deletions(-) create mode 100644 src/main/java/org/sopt/kareer/domain/jobposting/controller/JobPostingApi.java diff --git a/src/main/java/org/sopt/kareer/domain/jobposting/controller/JobPostingApi.java b/src/main/java/org/sopt/kareer/domain/jobposting/controller/JobPostingApi.java new file mode 100644 index 0000000..a565cb4 --- /dev/null +++ b/src/main/java/org/sopt/kareer/domain/jobposting/controller/JobPostingApi.java @@ -0,0 +1,45 @@ +package org.sopt.kareer.domain.jobposting.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.sopt.kareer.domain.jobposting.dto.response.JobPostingCrawlListResponse; +import org.sopt.kareer.domain.jobposting.dto.response.JobPostingListResponse; +import org.sopt.kareer.global.annotation.CustomExceptionDescription; +import org.sopt.kareer.global.response.BaseResponse; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +import static org.sopt.kareer.global.config.swagger.SwaggerResponseDescription.*; + +@Tag(name = "채용 공고 관련 API") +public interface JobPostingApi { + + @GetMapping("crawl") + @Operation(summary = "채용 공고 크롤링 (Server Only)") + ResponseEntity> crawlJobPostings(@RequestParam(defaultValue = "5") int limit); + + @PostMapping(value = "recommend", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + @Operation(summary = "채용 공고 추천", description = "사용자가 업로드한 이력서/자소서, 사용자 정보 기반으로 채용 공고를 추천합니다.") + @CustomExceptionDescription(RECOMMEND_JOBPOSTING) + ResponseEntity> recommendJobPostings( + @AuthenticationPrincipal Long memberId, + @RequestPart(value = "files", required = false) List files, + @RequestParam(value = "includeCompletedTodo", defaultValue = "false") boolean includeCompletedTodos); + + @PostMapping("{jobPostingId}/bookmarks") + @Operation(summary = "채용 공고 북마크 추가/삭제", description = "사용자가 추천된 채용 공고를 추가하거나 삭제합니다.") + @CustomExceptionDescription(CREATE_BOOKMARK) + ResponseEntity> createJobPostingBookmark( + @AuthenticationPrincipal Long memberId, + @PathVariable Long jobPostingId); + + @GetMapping("bookmarks") + @Operation(summary = "채용 공고 북마크 조회", description = "사용자가 북마크한 채용 공고를 조회합니다.") + @CustomExceptionDescription(GET_BOOKMARK) + ResponseEntity> getJobPostingBookmarks(@AuthenticationPrincipal Long memberId); +} diff --git a/src/main/java/org/sopt/kareer/domain/jobposting/controller/JobPostingController.java b/src/main/java/org/sopt/kareer/domain/jobposting/controller/JobPostingController.java index f295f0d..379e7da 100644 --- a/src/main/java/org/sopt/kareer/domain/jobposting/controller/JobPostingController.java +++ b/src/main/java/org/sopt/kareer/domain/jobposting/controller/JobPostingController.java @@ -1,16 +1,12 @@ package org.sopt.kareer.domain.jobposting.controller; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.sopt.kareer.domain.jobposting.dto.response.JobPostingCrawlListResponse; import org.sopt.kareer.domain.jobposting.dto.response.JobPostingListResponse; import org.sopt.kareer.domain.jobposting.service.JobPostingCrawler; import org.sopt.kareer.domain.jobposting.service.JobPostingService; -import org.sopt.kareer.global.annotation.CustomExceptionDescription; import org.sopt.kareer.global.response.BaseResponse; import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; @@ -18,56 +14,41 @@ import java.util.List; -import static org.sopt.kareer.global.config.swagger.SwaggerResponseDescription.*; - @RestController @RequiredArgsConstructor @RequestMapping("/api/v1/job-postings") -@Tag(name = "채용 공고 관련 API") -public class JobPostingController { +public class JobPostingController implements JobPostingApi { private final JobPostingCrawler jobPostingCrawler; private final JobPostingService jobPostingService; - @Operation(summary = "채용 공고 크롤링 (Server Only)") - @GetMapping("crawl") + @Override public ResponseEntity> crawlJobPostings(@RequestParam(defaultValue = "5") int limit) { return ResponseEntity.status(HttpStatus.OK) .body(BaseResponse.ok(jobPostingCrawler.crawlJobPostingForTest(limit), "채용 공고 크롤링에 성공하였습니다.")); } - @Operation(summary = "채용 공고 추천", description = "사용자가 업로드한 이력서/자소서, 사용자 정보 기반으로 채용 공고를 추천합니다.") - @CustomExceptionDescription(RECOMMEND_JOBPOSTING) - @PostMapping(value = "recommend", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + @Override public ResponseEntity> recommendJobPostings( @AuthenticationPrincipal Long memberId, @RequestPart(value = "files", required = false) List files, - @RequestParam(value = "includeCompletedTodo", defaultValue = "false") boolean includeCompletedTodos - ){ + @RequestParam(value = "includeCompletedTodo", defaultValue = "false") boolean includeCompletedTodos) { return ResponseEntity.status(HttpStatus.OK) .body(BaseResponse.ok(jobPostingService.recommend(memberId, files, includeCompletedTodos), "채용 공고 추천에 성공하였습니다.")); } - @Operation(summary = "채용 공고 북마크 추가/삭제", description = "사용자가 추천된 채용 공고를 추가하거나 삭제합니다.") - @CustomExceptionDescription(CREATE_BOOKMARK) - @PostMapping("{jobPostingId}/bookmarks") + @Override public ResponseEntity> createJobPostingBookmark( @AuthenticationPrincipal Long memberId, - @PathVariable Long jobPostingId){ + @PathVariable Long jobPostingId) { jobPostingService.createOrDeleteBookmark(memberId, jobPostingId); - return ResponseEntity.status(HttpStatus.OK) .body(BaseResponse.ok("채용 공고 북마크 추가 / 삭제에 성공했습니다.")); } - @Operation(summary = "채용 공고 북마크 조회", description = "사용자가 북마크한 채용 공고를 조회합니다.") - @CustomExceptionDescription(GET_BOOKMARK) - @GetMapping("bookmarks") - public ResponseEntity> getJobPostingBookmarks( - @AuthenticationPrincipal Long memberId - ){ + @Override + public ResponseEntity> getJobPostingBookmarks(@AuthenticationPrincipal Long memberId) { return ResponseEntity.status(HttpStatus.OK) .body(BaseResponse.ok(jobPostingService.getJobPostingBookmarks(memberId), "북마크 채용 공고 조회에 성공하였습니다.")); } - } From e5418b2a8e99f82743316f79c837a638d2019d02 Mon Sep 17 00:00:00 2001 From: dlwjddus1112 Date: Wed, 15 Apr 2026 18:23:41 +0900 Subject: [PATCH 11/19] =?UTF-8?q?[Chore]=20#238=20=EC=9D=B4=EC=A0=84=20?= =?UTF-8?q?=EB=B2=84=EC=A0=84=EC=9D=98=20=EC=98=A8=EB=B3=B4=EB=94=A9=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/member/controller/MemberApi.java | 7 ---- .../member/controller/MemberController.java | 16 +++------- .../domain/member/service/MemberService.java | 32 ------------------- 3 files changed, 5 insertions(+), 50 deletions(-) diff --git a/src/main/java/org/sopt/kareer/domain/member/controller/MemberApi.java b/src/main/java/org/sopt/kareer/domain/member/controller/MemberApi.java index b564923..7df5e69 100644 --- a/src/main/java/org/sopt/kareer/domain/member/controller/MemberApi.java +++ b/src/main/java/org/sopt/kareer/domain/member/controller/MemberApi.java @@ -5,7 +5,6 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; -import org.sopt.kareer.domain.member.dto.request.MemberOnboardRequest; import org.sopt.kareer.domain.member.dto.request.MemberTermsRequest; import org.sopt.kareer.domain.member.dto.request.MypageRequest; import org.sopt.kareer.domain.member.dto.response.*; @@ -28,12 +27,6 @@ public interface MemberApi { @CustomExceptionDescription(SwaggerResponseDescription.MEMBER_INFO) ResponseEntity> getMemberInfo(@AuthenticationPrincipal Long memberId); - @PostMapping("/onboard") - @Operation(summary = "회원 온보딩", description = "PENDING 상태의 회원의 온보딩 결과를 저장합니다.") - @CustomExceptionDescription(SwaggerResponseDescription.MEMBER_ONBOARD) - ResponseEntity> onboardMember(@AuthenticationPrincipal Long memberId, - @Valid @RequestBody MemberOnboardRequest request); - @GetMapping("/onboard/universities") @Operation(summary = "온보딩 대학교 목록 조회", description = "회원 온보딩 시 선택할 수 있는 대학교 목록을 조회합니다.") ResponseEntity> getOnboardUniversities(); diff --git a/src/main/java/org/sopt/kareer/domain/member/controller/MemberController.java b/src/main/java/org/sopt/kareer/domain/member/controller/MemberController.java index af3cc43..44d0ea4 100644 --- a/src/main/java/org/sopt/kareer/domain/member/controller/MemberController.java +++ b/src/main/java/org/sopt/kareer/domain/member/controller/MemberController.java @@ -4,7 +4,6 @@ import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; -import org.sopt.kareer.domain.member.dto.request.MemberOnboardRequest; import org.sopt.kareer.domain.member.dto.request.MemberTermsRequest; import org.sopt.kareer.domain.member.dto.request.MypageRequest; import org.sopt.kareer.domain.member.dto.response.*; @@ -15,7 +14,10 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; @RestController @@ -32,15 +34,7 @@ public ResponseEntity> getMemberInfo(@Authentic return ResponseEntity.status(HttpStatus.OK) .body(BaseResponse.ok(memberService.getMemberInfo(memberId), "회원 정보 조회에 성공하였습니다.")); } - - @Override - public ResponseEntity> onboardMember(@AuthenticationPrincipal Long memberId, - @Valid @RequestBody MemberOnboardRequest request) { - memberService.onboardMember(request, memberId); - return ResponseEntity.status(HttpStatus.OK) - .body(BaseResponse.ok("회원 온보딩이 완료되었습니다.")); - } - + @Override public ResponseEntity> getOnboardUniversities() { return ResponseEntity.status(HttpStatus.OK) diff --git a/src/main/java/org/sopt/kareer/domain/member/service/MemberService.java b/src/main/java/org/sopt/kareer/domain/member/service/MemberService.java index e823599..4ce5602 100644 --- a/src/main/java/org/sopt/kareer/domain/member/service/MemberService.java +++ b/src/main/java/org/sopt/kareer/domain/member/service/MemberService.java @@ -1,7 +1,6 @@ package org.sopt.kareer.domain.member.service; import lombok.RequiredArgsConstructor; -import org.sopt.kareer.domain.member.dto.request.MemberOnboardRequest; import org.sopt.kareer.domain.member.dto.request.MemberOnboardV2Request; import org.sopt.kareer.domain.member.dto.request.MemberTermsRequest; import org.sopt.kareer.domain.member.dto.response.*; @@ -107,37 +106,6 @@ public MemberInfoResponse getMemberInfo(Long memberId) { ); } - // 프론트 온보딩 구현 완료 후 삭제 예정 - @Transactional - public void onboardMember(MemberOnboardRequest request, Long memberId) { - Member member = getById(memberId); - member.updateInfo( - request.name(), - request.birthDate(), - request.country(), - null, - null, - null, - null, - request.languageLevel(), - request.degree(), - request.expectedGraduationDate(), - request.primaryMajor(), - request.secondaryMajor(), - request.targetJob(), - request.targetJobSkill(), - request.personalBackground() - ); - - MemberVisa memberVisa = MemberVisa.createMemberVisa( - member, - request.visaType(), - request.visaExpiredAt(), - request.visaStartDate() - ); - memberVisaRepository.save(memberVisa); - } - @Transactional public void onboardMemberV2(MemberOnboardV2Request request, Long memberId) { Member member = getById(memberId); From 89ee7136387aad307967e2ae514efdeaed5af94a Mon Sep 17 00:00:00 2001 From: dlwjddus1112 Date: Wed, 15 Apr 2026 19:34:00 +0900 Subject: [PATCH 12/19] =?UTF-8?q?[Chore]=20#238=20test=EC=9A=A9=20applicat?= =?UTF-8?q?ion.yml=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/resources/application-test.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/test/resources/application-test.yml b/src/test/resources/application-test.yml index 12b1b2d..e923ab4 100644 --- a/src/test/resources/application-test.yml +++ b/src/test/resources/application-test.yml @@ -90,6 +90,8 @@ auth: name: test secure: false same-site: test + token-blacklist: + redis-prefix: test-blacklist swagger: auth: username: ddd @@ -97,3 +99,15 @@ swagger: discord: webhook: url: test-url + +cohere: + api-key: test-cohere-key + base-url: https://api.cohere.com + rerank: + model: rerank-multilingual-v3.0 + top-n: 10 + +google: + translate: + url: https://translation.googleapis.com + api-key: test-google-translate-key From 69a26344cf5a6ff3ac91b88671a44805f71d42f6 Mon Sep 17 00:00:00 2001 From: dlwjddus1112 Date: Wed, 15 Apr 2026 19:36:51 +0900 Subject: [PATCH 13/19] =?UTF-8?q?[Test]=20#238=20Member=20=ED=85=8C?= =?UTF-8?q?=EC=9D=B4=EB=B8=94=20=EC=BB=AC=EB=9F=BC=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=EC=97=90=20=EB=94=B0=EB=A5=B8=20Fixture=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/member/entity/MemberTest.java | 2 +- .../domain/member/fixture/MemberFixture.java | 31 +++++++++---------- .../fixture/MemberOnboardRequestFixture.java | 29 ++++++++++------- .../member/fixture/MemberVisaFixture.java | 3 -- 4 files changed, 32 insertions(+), 33 deletions(-) diff --git a/src/test/java/org/sopt/kareer/domain/member/entity/MemberTest.java b/src/test/java/org/sopt/kareer/domain/member/entity/MemberTest.java index f0b8b98..ba8f5fc 100644 --- a/src/test/java/org/sopt/kareer/domain/member/entity/MemberTest.java +++ b/src/test/java/org/sopt/kareer/domain/member/entity/MemberTest.java @@ -38,6 +38,6 @@ void updateProfile(){ assertThat(member.getPrimaryMajorCode()).isEqualTo("computer-science"); assertThat(member.getSecondaryMajor()).isEqualTo("Statistic"); assertThat(member.getLanguageLevel()).isEqualTo(LanguageLevel.LEVEL_3); - assertThat(member.getUniversityCode()).isEqualTo("beginner"); + assertThat(member.getEnglishLevelCode()).isEqualTo("beginner"); } } diff --git a/src/test/java/org/sopt/kareer/domain/member/fixture/MemberFixture.java b/src/test/java/org/sopt/kareer/domain/member/fixture/MemberFixture.java index f18ef4e..ad7b0e9 100644 --- a/src/test/java/org/sopt/kareer/domain/member/fixture/MemberFixture.java +++ b/src/test/java/org/sopt/kareer/domain/member/fixture/MemberFixture.java @@ -10,44 +10,41 @@ public class MemberFixture { public static final OAuthProvider MEMBER_OAUTH_PROVIDER = OAuthProvider.GOOGLE; public static final String MEMBER_PROVIDER_ID = "test-provider-id"; public static final String MEMBER_EMAIL = "member@example.com"; - public static final Country MEMBER_COUNTRY = Country.AFGHANISTAN; - public static final Degree MEMBER_DEGREE = Degree.DOMESTIC_ASSOCIATE; - public static final String MEMBER_UNIVERSITY = "University"; - public static final EnglishLevel MEMBER_ENGLISH_LEVEL = EnglishLevel.ADVANCED; - + public static final String MEMBER_COUNTRY_CODE = "afghanistan"; + public static final String MEMBER_DEGREE_CODE = "south-korea-associate"; + public static final String MEMBER_UNIVERSITY_CODE = "konkuk-university"; + public static final String MEMBER_ENGLISH_LEVEL_CODE = "advanced"; public static Member getMember() { return getMember(MEMBER_PROVIDER_ID); } - public static Member getMember(String providerId){ + public static Member getMember(String providerId) { return Member.builder() .name(MEMBER_NAME) .email(MEMBER_EMAIL) - .country(MEMBER_COUNTRY) + .countryCode(MEMBER_COUNTRY_CODE) .provider(MEMBER_OAUTH_PROVIDER) .providerId(providerId) .status(MemberStatus.ACTIVE) - .roadmapStatus(RoadmapStatus.NOT_STARTED) - .degree(MEMBER_DEGREE) - .englishLevel(MEMBER_ENGLISH_LEVEL) - .university(MEMBER_UNIVERSITY) + .degreeCode(MEMBER_DEGREE_CODE) + .englishLevelCode(MEMBER_ENGLISH_LEVEL_CODE) + .universityCode(MEMBER_UNIVERSITY_CODE) .build(); } - public static Member getMember(LocalDate birthDate){ + public static Member getMember(LocalDate birthDate) { return Member.builder() .name(MEMBER_NAME) .email(MEMBER_EMAIL) - .country(MEMBER_COUNTRY) + .countryCode(MEMBER_COUNTRY_CODE) .provider(MEMBER_OAUTH_PROVIDER) .providerId(MEMBER_PROVIDER_ID) .status(MemberStatus.ACTIVE) - .roadmapStatus(RoadmapStatus.NOT_STARTED) - .degree(MEMBER_DEGREE) - .englishLevel(MEMBER_ENGLISH_LEVEL) - .university(MEMBER_UNIVERSITY) + .degreeCode(MEMBER_DEGREE_CODE) + .englishLevelCode(MEMBER_ENGLISH_LEVEL_CODE) + .universityCode(MEMBER_UNIVERSITY_CODE) .birthDate(birthDate) .build(); } diff --git a/src/test/java/org/sopt/kareer/domain/member/fixture/MemberOnboardRequestFixture.java b/src/test/java/org/sopt/kareer/domain/member/fixture/MemberOnboardRequestFixture.java index cd276bb..dadd5c3 100644 --- a/src/test/java/org/sopt/kareer/domain/member/fixture/MemberOnboardRequestFixture.java +++ b/src/test/java/org/sopt/kareer/domain/member/fixture/MemberOnboardRequestFixture.java @@ -1,28 +1,33 @@ package org.sopt.kareer.domain.member.fixture; -import java.time.LocalDate; -import org.sopt.kareer.domain.member.dto.request.MemberOnboardRequest; -import org.sopt.kareer.domain.member.entity.enums.Country; +import org.sopt.kareer.domain.member.dto.request.MemberOnboardV2Request; import org.sopt.kareer.domain.member.entity.enums.Degree; +import org.sopt.kareer.domain.member.entity.enums.EnglishLevel; import org.sopt.kareer.domain.member.entity.enums.LanguageLevel; import org.sopt.kareer.domain.member.entity.enums.VisaType; +import java.time.LocalDate; +import java.util.List; + public class MemberOnboardRequestFixture { - public static MemberOnboardRequest create() { - return new MemberOnboardRequest( + public static MemberOnboardV2Request create() { + return new MemberOnboardV2Request( "test-user", LocalDate.of(2000, 4, 1), - Country.UNITED_STATES, + "konkuk-university", + "united-states", LanguageLevel.LEVEL_4, + EnglishLevel.ADVANCED, Degree.OVERSEAS_BACHELORS, - VisaType.D10, - null, - LocalDate.of(2024, 5, 1), + VisaType.D2, + LocalDate.of(2026, 2, 1), + LocalDate.of(2022, 3, 1), LocalDate.of(2026, 5, 1), - 70, - "Computer Science", - "Applied Mathematics", + "computer-science", + "applied-mathematics", + List.of("it-development", "data-science"), + List.of(), "Backend Engineer", "Java, Spring", "해외 경험을 쌓고 싶어요" diff --git a/src/test/java/org/sopt/kareer/domain/member/fixture/MemberVisaFixture.java b/src/test/java/org/sopt/kareer/domain/member/fixture/MemberVisaFixture.java index 027d7e6..c42b1a8 100644 --- a/src/test/java/org/sopt/kareer/domain/member/fixture/MemberVisaFixture.java +++ b/src/test/java/org/sopt/kareer/domain/member/fixture/MemberVisaFixture.java @@ -14,7 +14,6 @@ public static MemberVisa activeD2(Member member) { .visaType(VisaType.D2) .visaStatus(VisaStatus.ACTIVE) .visaExpiredAt(LocalDate.now().plusYears(1)) - .visaPoint(null) .visaStartDate(LocalDate.now().minusMonths(3)) .build(); } @@ -25,7 +24,6 @@ public static MemberVisa inactiveD2(Member member) { .visaType(VisaType.D2) .visaStatus(VisaStatus.INACTIVE) .visaExpiredAt(LocalDate.now().minusMonths(1)) - .visaPoint(null) .visaStartDate(LocalDate.now().minusYears(1)) .build(); } @@ -36,7 +34,6 @@ public static MemberVisa activeD10(Member member) { .visaType(VisaType.D10) .visaStatus(VisaStatus.ACTIVE) .visaExpiredAt(LocalDate.now().plusMonths(18)) - .visaPoint(70) .visaStartDate(LocalDate.now()) .build(); } From a9f6b436670bcbfca9fcf84bb20b0d1f6f81149c Mon Sep 17 00:00:00 2001 From: dlwjddus1112 Date: Wed, 15 Apr 2026 19:39:39 +0900 Subject: [PATCH 14/19] =?UTF-8?q?[Test]=20#238=20=EC=98=A8=EB=B3=B4?= =?UTF-8?q?=EB=94=A9=20=EB=A1=9C=EC=A7=81=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F?= =?UTF-8?q?=20=EB=B9=84=EB=8F=99=EA=B8=B0=20=EC=B2=98=EB=A6=AC=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=A0=9C=EA=B1=B0=EC=97=90=20=EB=94=B0=EB=A5=B8=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/MemberControllerTest.java | 51 ++++++---------- .../controller/MemberControllerV2Test.java | 39 ++++++++++++ .../repository/MemberRepositoryTest.java | 27 --------- .../member/service/MemberServiceTest.java | 59 +++++++++---------- 4 files changed, 84 insertions(+), 92 deletions(-) create mode 100644 src/test/java/org/sopt/kareer/domain/member/controller/MemberControllerV2Test.java diff --git a/src/test/java/org/sopt/kareer/domain/member/controller/MemberControllerTest.java b/src/test/java/org/sopt/kareer/domain/member/controller/MemberControllerTest.java index 60b0c80..652a96e 100644 --- a/src/test/java/org/sopt/kareer/domain/member/controller/MemberControllerTest.java +++ b/src/test/java/org/sopt/kareer/domain/member/controller/MemberControllerTest.java @@ -2,17 +2,14 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.sopt.kareer.domain.member.dto.request.MemberOnboardRequest; import org.sopt.kareer.domain.member.dto.response.MemberInfoResponse; import org.sopt.kareer.domain.member.dto.response.MemberStatusResponse; import org.sopt.kareer.domain.member.dto.response.MypageResponse; import org.sopt.kareer.domain.member.entity.Member; import org.sopt.kareer.domain.member.entity.MemberVisa; -import org.sopt.kareer.domain.member.entity.enums.Country; import org.sopt.kareer.domain.member.entity.enums.LanguageLevel; import org.sopt.kareer.domain.member.entity.enums.VisaType; import org.sopt.kareer.domain.member.fixture.MemberFixture; -import org.sopt.kareer.domain.member.fixture.MemberOnboardRequestFixture; import org.sopt.kareer.domain.member.fixture.MemberVisaFixture; import org.sopt.kareer.support.ControllerTestSupport; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; @@ -22,11 +19,8 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; -import static org.mockito.BDDMockito.willDoNothing; -import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -46,14 +40,16 @@ void getMemberInfo() throws Exception { "test-user@example.com", "profile-image-url", LocalDate.of(2000, 4, 1), - Country.UNITED_STATES, + "United States", "Computer Science", "Math", "Backend", + "Seoul National University", LocalDate.of(2023, 2, 1), LocalDate.of(2024, 2, 1), LanguageLevel.LEVEL_4, - "Bachelor", + "outside-korea-bachelor", + "advanced", "Java", VisaType.D2 ); @@ -67,28 +63,12 @@ void getMemberInfo() throws Exception { .andExpect(jsonPath("$.data.memberId").value(response.memberId())); } - @DisplayName("회원 온보딩 정보를 저장한다.") - @Test - void onboardMember() throws Exception { - MemberOnboardRequest request = MemberOnboardRequestFixture.create(); - willDoNothing().given(memberService).onboardMember(any(), any()); - - mockMvc.perform(post("/api/v1/members/onboard") - .with(authentication(authenticatedMember())) - .contentType(APPLICATION_JSON) - .content(objectMapper.writeValueAsString(request))) - .andDo(print()) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.message").value("회원 온보딩이 완료되었습니다.")); - } - @DisplayName("온보딩 국가 목록을 조회한다.") @Test void getOnboardCountries() throws Exception { mockMvc.perform(get("/api/v1/members/onboard/countries")) .andDo(print()) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.data.countries").isArray()); + .andExpect(status().isOk()); } @DisplayName("온보딩 전공 목록을 조회한다.") @@ -96,11 +76,9 @@ void getOnboardCountries() throws Exception { void getOnboardMajors() throws Exception { mockMvc.perform(get("/api/v1/members/onboard/majors")) .andDo(print()) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.data.majors").isArray()); + .andExpect(status().isOk()); } - @DisplayName("회원 상태 정보를 조회한다.") @Test void getMemberStatus() throws Exception { @@ -122,20 +100,25 @@ void getMemberStatus() throws Exception { @DisplayName("마이페이지를 조회한다.") @Test void getMyPage() throws Exception { - //given + //given Member member = MemberFixture.getMember(); MemberVisa memberVisa = MemberVisaFixture.activeD2(member); - MypageResponse response = MypageResponse.from(member, memberVisa); - + MypageResponse response = MypageResponse.of( + member, memberVisa, + member.getCountryCode(), + member.getPrimaryMajorCode(), + member.getUniversityCode(), + member.getDegreeCode(), + member.getEnglishLevelCode() + ); given(memberService.getMypage(any())).willReturn(response); - //when && then + //when && then mockMvc.perform(get("/api/v1/members/mypage") - .with(authentication(authenticatedMember()))) + .with(authentication(authenticatedMember()))) .andDo(print()) .andExpect(status().isOk()) .andExpect(jsonPath("$.data.name").value(member.getName())); - } } diff --git a/src/test/java/org/sopt/kareer/domain/member/controller/MemberControllerV2Test.java b/src/test/java/org/sopt/kareer/domain/member/controller/MemberControllerV2Test.java new file mode 100644 index 0000000..2a5f973 --- /dev/null +++ b/src/test/java/org/sopt/kareer/domain/member/controller/MemberControllerV2Test.java @@ -0,0 +1,39 @@ +package org.sopt.kareer.domain.member.controller; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.sopt.kareer.domain.member.fixture.MemberOnboardRequestFixture; +import org.sopt.kareer.support.ControllerTestSupport; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; + +import java.util.List; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.willDoNothing; +import static org.springframework.http.MediaType.APPLICATION_JSON; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +class MemberControllerV2Test extends ControllerTestSupport { + + private UsernamePasswordAuthenticationToken authenticatedMember() { + return new UsernamePasswordAuthenticationToken(1L, null, List.of()); + } + + @DisplayName("회원 온보딩 V2 정보를 저장한다.") + @Test + void onboardMember() throws Exception { + willDoNothing().given(memberService).onboardMemberV2(any(), any()); + + mockMvc.perform(post("/api/v2/members/onboard") + .with(authentication(authenticatedMember())) + .contentType(APPLICATION_JSON) + .content(objectMapper.writeValueAsString(MemberOnboardRequestFixture.create()))) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.message").value("회원 온보딩이 완료되었습니다.")); + } +} diff --git a/src/test/java/org/sopt/kareer/domain/member/repository/MemberRepositoryTest.java b/src/test/java/org/sopt/kareer/domain/member/repository/MemberRepositoryTest.java index 1fabed4..2f8491e 100644 --- a/src/test/java/org/sopt/kareer/domain/member/repository/MemberRepositoryTest.java +++ b/src/test/java/org/sopt/kareer/domain/member/repository/MemberRepositoryTest.java @@ -6,7 +6,6 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.sopt.kareer.domain.member.entity.Member; -import org.sopt.kareer.domain.member.entity.enums.RoadmapStatus; import org.sopt.kareer.domain.member.fixture.MemberFixture; import org.sopt.kareer.global.config.QuerydslConfig; import org.springframework.beans.factory.annotation.Autowired; @@ -35,30 +34,4 @@ void findByProviderAndProviderId() { assertThat(found).isPresent(); assertThat(found.get().getId()).isEqualTo(member.getId()); } - - @DisplayName("로드맵 상태가 NOT_STARTED면 IN_PROGRESS로 전환된다.") - @Test - void tryMarkRoadmapInProgress() { - Member member = memberRepository.save(MemberFixture.getMember("provider-2")); - - int updatedCount = memberRepository.tryMarkRoadmapInProgress(member.getId()); - - assertThat(updatedCount).isEqualTo(1); - Member updatedMember = memberRepository.findById(member.getId()).orElseThrow(); - assertThat(updatedMember.getRoadmapStatus()).isEqualTo(RoadmapStatus.IN_PROGRESS); - } - - @DisplayName("이미 로드맵이 완료된 회원은 상태가 변경되지 않는다.") - @Test - void tryMarkRoadmapInProgressWithDoneStatus() { - Member member = memberRepository.save(MemberFixture.getMember("provider-3")); - member.markRoadmapDone(); - memberRepository.save(member); - - int updatedCount = memberRepository.tryMarkRoadmapInProgress(member.getId()); - - assertThat(updatedCount).isZero(); - Member foundMember = memberRepository.findById(member.getId()).orElseThrow(); - assertThat(foundMember.getRoadmapStatus()).isEqualTo(RoadmapStatus.DONE); - } } diff --git a/src/test/java/org/sopt/kareer/domain/member/service/MemberServiceTest.java b/src/test/java/org/sopt/kareer/domain/member/service/MemberServiceTest.java index 8d7445d..7410072 100644 --- a/src/test/java/org/sopt/kareer/domain/member/service/MemberServiceTest.java +++ b/src/test/java/org/sopt/kareer/domain/member/service/MemberServiceTest.java @@ -3,13 +3,15 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.sopt.kareer.domain.member.dto.request.MemberOnboardRequest; +import org.sopt.kareer.domain.member.dto.request.MemberOnboardV2Request; import org.sopt.kareer.domain.member.dto.response.MemberInfoResponse; import org.sopt.kareer.domain.member.dto.response.MemberStatusResponse; import org.sopt.kareer.domain.member.dto.response.MypageResponse; import org.sopt.kareer.domain.member.entity.Member; import org.sopt.kareer.domain.member.entity.MemberVisa; -import org.sopt.kareer.domain.member.entity.enums.*; +import org.sopt.kareer.domain.member.entity.enums.LanguageLevel; +import org.sopt.kareer.domain.member.entity.enums.MemberStatus; +import org.sopt.kareer.domain.member.entity.enums.OAuthProvider; import org.sopt.kareer.domain.member.exception.MemberErrorCode; import org.sopt.kareer.domain.member.exception.MemberException; import org.sopt.kareer.domain.member.fixture.MemberFixture; @@ -74,7 +76,7 @@ void findOrCreateByOAuthReturnsExistingMember() { @DisplayName("온보딩 요청을 저장하면 회원 정보와 비자 정보가 갱신된다.") @Test - void onboardMember() { + void onboardMemberV2() { Member member = memberRepository.save(Member.createOAuthMember( "test-user", OAuthProvider.GOOGLE, @@ -82,15 +84,15 @@ void onboardMember() { "test_image_url", "test-user@example.com" )); - MemberOnboardRequest request = MemberOnboardRequestFixture.create(); + MemberOnboardV2Request request = MemberOnboardRequestFixture.create(); - memberService.onboardMember(request, member.getId()); + memberService.onboardMemberV2(request, member.getId()); Member updated = memberRepository.findById(member.getId()).orElseThrow(); assertThat(updated.getStatus()).isEqualTo(MemberStatus.ACTIVE); assertThat(updated.getName()).isEqualTo(request.name()); - assertThat(updated.getCountry()).isEqualTo(request.country()); - assertThat(updated.getPrimaryMajor()).isEqualTo(request.primaryMajor()); + assertThat(updated.getCountryCode()).isEqualTo(request.countryCode()); + assertThat(updated.getPrimaryMajorCode()).isEqualTo(request.primaryMajorCode()); assertThat(updated.getTargetJobSkill()).isEqualTo(request.targetJobSkill()); assertThat(updated.getExpectedGraduationDate()).isEqualTo(request.expectedGraduationDate()); @@ -98,7 +100,6 @@ void onboardMember() { assertThat(visas).hasSize(1); MemberVisa savedVisa = visas.getFirst(); assertThat(savedVisa.getVisaType()).isEqualTo(request.visaType()); - assertThat(savedVisa.getVisaPoint()).isEqualTo(request.visaPoint()); } @DisplayName("회원 정보 조회 시 멤버와 비자 정보가 함께 반환된다.") @@ -110,15 +111,16 @@ void getMemberInfo() { .status(MemberStatus.ACTIVE) .provider(OAuthProvider.GOOGLE) .providerId(UUID.randomUUID().toString()) - .country(Country.AUSTRALIA) - .primaryMajor("Computer Science") + .countryCode("australia") + .primaryMajorCode("computer-science") .secondaryMajor("Mathematics") .targetJob("Backend Engineer") .graduationDate(LocalDate.of(2023, 2, 1)) .languageLevel(LanguageLevel.LEVEL_3) - .degree(Degree.OVERSEAS_BACHELORS) + .degreeCode("outside-korea-bachelor") + .englishLevelCode("advanced") + .universityCode("some-university") .targetJobSkill("Java") - .roadmapStatus(RoadmapStatus.NOT_STARTED) .build()); MemberVisa activeVisa = memberVisaRepository.save(MemberVisaFixture.activeD10(member)); @@ -126,8 +128,8 @@ void getMemberInfo() { assertThat(response.memberId()).isEqualTo(member.getId()); assertThat(response.name()).isEqualTo(member.getName()); - assertThat(response.country()).isEqualTo(member.getCountry()); - assertThat(response.degree()).isEqualTo(member.getDegree().getDescription()); + assertThat(response.country()).isEqualTo(member.getCountryCode()); + assertThat(response.degree()).isEqualTo(member.getDegreeCode()); assertThat(response.visaType()).isEqualTo(activeVisa.getVisaType()); } @@ -150,9 +152,8 @@ void getMemberStatus() { .status(MemberStatus.ACTIVE) .provider(OAuthProvider.GOOGLE) .providerId(UUID.randomUUID().toString()) - .country(Country.AUSTRALIA) - .roadmapStatus(RoadmapStatus.NOT_STARTED) - .degree(Degree.OVERSEAS_BACHELORS) + .countryCode("australia") + .degreeCode("outside-korea-bachelor") .languageLevel(LanguageLevel.LEVEL_5) .build()); MemberVisa activeVisa = memberVisaRepository.save(MemberVisaFixture.activeD2(member)); @@ -175,41 +176,39 @@ void getMemberStatusWithoutVisa() { @DisplayName("마이페이지에서 유저 정보를 조회한다.") @Test - void getMyPage(){ - //given + void getMyPage() { + //given Member member = memberRepository.save(MemberFixture.getMember()); - MemberVisa memberVisa = memberVisaRepository.save(MemberVisaFixture.activeD2(member)); - //when + memberVisaRepository.save(MemberVisaFixture.activeD2(member)); + + //when MypageResponse response = memberService.getMypage(member.getId()); //then assertThat(response.name()).isEqualTo(member.getName()); - assertThat(response.country()).isEqualTo(member.getCountry().getCountryName()); + assertThat(response.country()).isEqualTo(member.getCountryCode()); } @DisplayName("존재하지 않는 회원의 마이페이지 조회 시 예외가 발생한다.") @Test - void getMyPageWithoutMember(){ - + void getMyPageWithoutMember() { assertThatThrownBy(() -> memberService.getMypage(1L)) .isInstanceOf(MemberException.class) .hasMessage(MemberErrorCode.MEMBER_NOT_FOUND.getMessage()); - } @DisplayName("비자정보가 존재하지 않는 경우 마이페이지 조회 시 예외가 발생한다.") @Test - void getMypageWithoutMemberVisa(){ - //given + void getMypageWithoutMemberVisa() { + //given Member member = memberRepository.save(MemberFixture.getMember()); - //when && then + //when && then assertThatThrownBy(() -> memberService.getMypage(member.getId())) .isInstanceOf(MemberException.class) .hasMessage(MemberErrorCode.VISA_NOT_FOUND.getMessage()); } - private OAuthAttributes createOAuthAttributes() { String providerId = UUID.randomUUID().toString(); return new OAuthAttributes( @@ -222,6 +221,4 @@ private OAuthAttributes createOAuthAttributes() { Map.of() ); } - - } From 98b6ad27db9fdc54edbe80a6549c5a9cbcdf87a8 Mon Sep 17 00:00:00 2001 From: dlwjddus1112 Date: Wed, 15 Apr 2026 19:40:03 +0900 Subject: [PATCH 15/19] =?UTF-8?q?[Test]=20#238=20Roadmap=20=EC=97=94?= =?UTF-8?q?=ED=8B=B0=ED=8B=B0=20=EC=83=9D=EC=84=B1=EC=97=90=20=EB=94=B0?= =?UTF-8?q?=EB=A5=B8=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/PhaseActionControllerTest.java | 4 +-- .../controller/PhaseControllerTest.java | 10 +++--- .../domain/roadmap/fixture/PhaseFixture.java | 6 ++-- .../PhaseActionGuidelineRepositoryTest.java | 7 ++++- .../PhaseActionMistakeRepositoryTest.java | 7 ++++- .../repository/PhaseActionRepositoryTest.java | 12 +++++-- .../repository/PhaseRepositoryTest.java | 25 +++++++++------ .../service/PhaseActionServiceTest.java | 10 +++++- .../roadmap/service/PhaseServiceTest.java | 31 ++++++++++++------- .../kareer/support/ControllerTestSupport.java | 7 ++++- 10 files changed, 82 insertions(+), 37 deletions(-) diff --git a/src/test/java/org/sopt/kareer/domain/roadmap/controller/PhaseActionControllerTest.java b/src/test/java/org/sopt/kareer/domain/roadmap/controller/PhaseActionControllerTest.java index 0bf29c9..36a2582 100644 --- a/src/test/java/org/sopt/kareer/domain/roadmap/controller/PhaseActionControllerTest.java +++ b/src/test/java/org/sopt/kareer/domain/roadmap/controller/PhaseActionControllerTest.java @@ -40,7 +40,7 @@ void getAiGuide_success() throws Exception { .willReturn(response); // when & then - mockMvc.perform(get("/api/v1/phase-actions/{phaseActionId}/guide", phaseActionId)) + mockMvc.perform(get("/api/v1/roadmap/phase-actions/{phaseActionId}/guide", phaseActionId)) .andDo(print()) .andExpect(status().isOk()) .andExpect(jsonPath("$.message").value("AI 가이드가 조회되었습니다.")) @@ -62,7 +62,7 @@ void getAiGuide_notFoundPhaseAction() throws Exception { .willThrow(new RoadMapException(RoadmapErrorCode.PHASE_ACTION_NOT_FOUND)); // when & then - mockMvc.perform(get("/api/v1/phase-actions/{phaseActionId}/guide", phaseActionId)) + mockMvc.perform(get("/api/v1/roadmap/phase-actions/{phaseActionId}/guide", phaseActionId)) .andDo(print()) .andExpect(status().isNotFound()) .andExpect(jsonPath("$.message").value(RoadmapErrorCode.PHASE_ACTION_NOT_FOUND.getMessage())); diff --git a/src/test/java/org/sopt/kareer/domain/roadmap/controller/PhaseControllerTest.java b/src/test/java/org/sopt/kareer/domain/roadmap/controller/PhaseControllerTest.java index 1f78d2e..555010d 100644 --- a/src/test/java/org/sopt/kareer/domain/roadmap/controller/PhaseControllerTest.java +++ b/src/test/java/org/sopt/kareer/domain/roadmap/controller/PhaseControllerTest.java @@ -45,7 +45,7 @@ void getPhaseList_success() throws Exception { ); // when & then - mockMvc.perform(get("/api/v1/phases")) + mockMvc.perform(get("/api/v1/roadmap/phases")) .andDo(print()) .andExpect(status().isOk()) .andExpect(jsonPath("$.message").value("Phase 리스트가 조회되었습니다.")) @@ -89,7 +89,7 @@ void getRoadmapPhaseDetail_success() throws Exception { .willReturn(response); // when & then - mockMvc.perform(get("/api/v1/phases/{phaseId}/roadmap", phaseId)) + mockMvc.perform(get("/api/v1/roadmap/phases/{phaseId}", phaseId)) .andDo(print()) .andExpect(status().isOk()) .andExpect(jsonPath("$.message").value("로드맵 Phase 상세정보가 조회되었습니다.")) @@ -108,7 +108,7 @@ void getRoadmapPhaseDetail_notFoundPhase() throws Exception { .willThrow(new RoadMapException(RoadmapErrorCode.PHASE_NOT_FOUND)); // when & then - mockMvc.perform(get("/api/v1/phases/{phaseId}/roadmap", phaseId)) + mockMvc.perform(get("/api/v1/roadmap/phases/{phaseId}", phaseId)) .andDo(print()) .andExpect(status().isNotFound()) .andExpect(jsonPath("$.message").value(RoadmapErrorCode.PHASE_NOT_FOUND.getMessage())); @@ -138,7 +138,7 @@ void getHomePhaseDetail_success() throws Exception { .willReturn(response); // when & then - mockMvc.perform(get("/api/v1/phases/{phaseId}/home", phaseId)) + mockMvc.perform(get("/api/v1/roadmap/phases/{phaseId}/home", phaseId)) .andDo(print()) .andExpect(status().isOk()) .andExpect(jsonPath("$.message").value("홈 Phase 상세정보가 조회되었습니다.")) @@ -158,7 +158,7 @@ void getHomePhaseDetail_notFoundPhase() throws Exception { .willThrow(new RoadMapException(RoadmapErrorCode.PHASE_NOT_FOUND)); // when & then - mockMvc.perform(get("/api/v1/phases/{phaseId}/home", phaseId)) + mockMvc.perform(get("/api/v1/roadmap/phases/{phaseId}/home", phaseId)) .andDo(print()) .andExpect(status().isNotFound()) .andExpect(jsonPath("$.message").value(RoadmapErrorCode.PHASE_NOT_FOUND.getMessage())); diff --git a/src/test/java/org/sopt/kareer/domain/roadmap/fixture/PhaseFixture.java b/src/test/java/org/sopt/kareer/domain/roadmap/fixture/PhaseFixture.java index b9fc4a1..24eb415 100644 --- a/src/test/java/org/sopt/kareer/domain/roadmap/fixture/PhaseFixture.java +++ b/src/test/java/org/sopt/kareer/domain/roadmap/fixture/PhaseFixture.java @@ -1,7 +1,7 @@ package org.sopt.kareer.domain.roadmap.fixture; -import org.sopt.kareer.domain.member.entity.Member; import org.sopt.kareer.domain.roadmap.entity.Phase; +import org.sopt.kareer.domain.roadmap.entity.Roadmap; import org.sopt.kareer.domain.roadmap.entity.enums.PhaseStatus; import java.time.LocalDate; @@ -13,7 +13,7 @@ public class PhaseFixture { public static final LocalDate START_DATE = LocalDate.of(2026, 6, 1); public static final LocalDate END_DATE = LocalDate.of(2026, 8, 31); - public static Phase getPhase(Member member, int sequence, PhaseStatus phaseStatus) { - return Phase.create(member, sequence, GOAL, DESCRIPTION, phaseStatus, START_DATE, END_DATE); + public static Phase getPhase(Roadmap roadmap, int sequence, PhaseStatus phaseStatus) { + return Phase.create(roadmap, sequence, GOAL, DESCRIPTION, phaseStatus, START_DATE, END_DATE); } } diff --git a/src/test/java/org/sopt/kareer/domain/roadmap/repository/PhaseActionGuidelineRepositoryTest.java b/src/test/java/org/sopt/kareer/domain/roadmap/repository/PhaseActionGuidelineRepositoryTest.java index efbb692..24760d6 100644 --- a/src/test/java/org/sopt/kareer/domain/roadmap/repository/PhaseActionGuidelineRepositoryTest.java +++ b/src/test/java/org/sopt/kareer/domain/roadmap/repository/PhaseActionGuidelineRepositoryTest.java @@ -8,6 +8,7 @@ import org.sopt.kareer.domain.roadmap.entity.Phase; import org.sopt.kareer.domain.roadmap.entity.PhaseAction; import org.sopt.kareer.domain.roadmap.entity.PhaseActionGuideline; +import org.sopt.kareer.domain.roadmap.entity.Roadmap; import org.sopt.kareer.domain.roadmap.entity.enums.PhaseActionType; import org.sopt.kareer.domain.roadmap.entity.enums.PhaseStatus; import org.sopt.kareer.domain.roadmap.fixture.PhaseActionFixture; @@ -30,6 +31,9 @@ public class PhaseActionGuidelineRepositoryTest { @Autowired private MemberRepository memberRepository; + @Autowired + private RoadmapRepository roadmapRepository; + @Autowired private PhaseRepository phaseRepository; @@ -44,7 +48,8 @@ public class PhaseActionGuidelineRepositoryTest { void findContentByPhaseActionId() { // given Member member1 = memberRepository.save(MemberFixture.getMember("test-provider-id-1")); - Phase phase1 = phaseRepository.save(PhaseFixture.getPhase(member1, 1, PhaseStatus.CURRENT)); + Roadmap roadmap1 = roadmapRepository.save(Roadmap.create(member1)); + Phase phase1 = phaseRepository.save(PhaseFixture.getPhase(roadmap1, 1, PhaseStatus.CURRENT)); PhaseAction action1 = PhaseActionFixture.getPhaseAction(phase1, PhaseActionType.VISA); PhaseAction action2 = PhaseActionFixture.getPhaseAction(phase1, PhaseActionType.VISA); diff --git a/src/test/java/org/sopt/kareer/domain/roadmap/repository/PhaseActionMistakeRepositoryTest.java b/src/test/java/org/sopt/kareer/domain/roadmap/repository/PhaseActionMistakeRepositoryTest.java index 05e4dee..803860a 100644 --- a/src/test/java/org/sopt/kareer/domain/roadmap/repository/PhaseActionMistakeRepositoryTest.java +++ b/src/test/java/org/sopt/kareer/domain/roadmap/repository/PhaseActionMistakeRepositoryTest.java @@ -8,6 +8,7 @@ import org.sopt.kareer.domain.roadmap.entity.Phase; import org.sopt.kareer.domain.roadmap.entity.PhaseAction; import org.sopt.kareer.domain.roadmap.entity.PhaseActionMistake; +import org.sopt.kareer.domain.roadmap.entity.Roadmap; import org.sopt.kareer.domain.roadmap.entity.enums.PhaseActionType; import org.sopt.kareer.domain.roadmap.entity.enums.PhaseStatus; import org.sopt.kareer.domain.roadmap.fixture.PhaseActionFixture; @@ -30,6 +31,9 @@ public class PhaseActionMistakeRepositoryTest { @Autowired private MemberRepository memberRepository; + @Autowired + private RoadmapRepository roadmapRepository; + @Autowired private PhaseRepository phaseRepository; @@ -44,7 +48,8 @@ public class PhaseActionMistakeRepositoryTest { void findContentByPhaseActionId() { // given Member member1 = memberRepository.save(MemberFixture.getMember("test-provider-id-1")); - Phase phase1 = phaseRepository.save(PhaseFixture.getPhase(member1, 1, PhaseStatus.CURRENT)); + Roadmap roadmap1 = roadmapRepository.save(Roadmap.create(member1)); + Phase phase1 = phaseRepository.save(PhaseFixture.getPhase(roadmap1, 1, PhaseStatus.CURRENT)); PhaseAction action1 = PhaseActionFixture.getPhaseAction(phase1, PhaseActionType.VISA); PhaseAction action2 = PhaseActionFixture.getPhaseAction(phase1, PhaseActionType.VISA); diff --git a/src/test/java/org/sopt/kareer/domain/roadmap/repository/PhaseActionRepositoryTest.java b/src/test/java/org/sopt/kareer/domain/roadmap/repository/PhaseActionRepositoryTest.java index 5f0a5e0..f931ed8 100644 --- a/src/test/java/org/sopt/kareer/domain/roadmap/repository/PhaseActionRepositoryTest.java +++ b/src/test/java/org/sopt/kareer/domain/roadmap/repository/PhaseActionRepositoryTest.java @@ -8,6 +8,7 @@ import org.sopt.kareer.domain.member.repository.MemberRepository; import org.sopt.kareer.domain.roadmap.entity.Phase; import org.sopt.kareer.domain.roadmap.entity.PhaseAction; +import org.sopt.kareer.domain.roadmap.entity.Roadmap; import org.sopt.kareer.domain.roadmap.entity.enums.PhaseActionType; import org.sopt.kareer.domain.roadmap.entity.enums.PhaseStatus; import org.sopt.kareer.domain.roadmap.fixture.PhaseActionFixture; @@ -34,23 +35,28 @@ public class PhaseActionRepositoryTest { @Autowired private MemberRepository memberRepository; + @Autowired + private RoadmapRepository roadmapRepository; + @Autowired private PhaseActionRepository phaseActionRepository; private Phase phase1; private Member member1; + private Roadmap roadmap1; @BeforeEach void setUp() { member1 = memberRepository.save(MemberFixture.getMember("test-provider-id-1")); - phase1 = phaseRepository.save(PhaseFixture.getPhase(member1, 1, PhaseStatus.CURRENT)); + roadmap1 = roadmapRepository.save(Roadmap.create(member1)); + phase1 = phaseRepository.save(PhaseFixture.getPhase(roadmap1, 1, PhaseStatus.CURRENT)); } @Test @DisplayName("phaseId가 일치하는 phaseAction들 중 아직 완료되지 않은 phaseAction들을 반환한다. ") void findByPhaseIdAndCompletedIsFalse() { // given - Phase phase2 = phaseRepository.save(PhaseFixture.getPhase(member1, 2, PhaseStatus.NEXT)); + Phase phase2 = phaseRepository.save(PhaseFixture.getPhase(roadmap1, 2, PhaseStatus.NEXT)); PhaseAction action1 = PhaseActionFixture.getPhaseAction(phase1, PhaseActionType.VISA); PhaseAction action2 = PhaseActionFixture.getPhaseAction(phase1, PhaseActionType.VISA); @@ -82,7 +88,7 @@ void findByIdAndMemberId() { // then assertThat(result).isPresent(); assertThat(result.get().getId()).isEqualTo(action1.getId()); - assertThat(result.get().getPhase().getMember().getId()) + assertThat(result.get().getPhase().getRoadmap().getMember().getId()) .isEqualTo(member1.getId()); } } diff --git a/src/test/java/org/sopt/kareer/domain/roadmap/repository/PhaseRepositoryTest.java b/src/test/java/org/sopt/kareer/domain/roadmap/repository/PhaseRepositoryTest.java index 58d1394..0951cf7 100644 --- a/src/test/java/org/sopt/kareer/domain/roadmap/repository/PhaseRepositoryTest.java +++ b/src/test/java/org/sopt/kareer/domain/roadmap/repository/PhaseRepositoryTest.java @@ -8,6 +8,7 @@ import org.sopt.kareer.domain.roadmap.dto.response.RoadmapPhaseDetailResponse; import org.sopt.kareer.domain.roadmap.entity.Phase; import org.sopt.kareer.domain.roadmap.entity.PhaseAction; +import org.sopt.kareer.domain.roadmap.entity.Roadmap; import org.sopt.kareer.domain.roadmap.entity.enums.PhaseActionType; import org.sopt.kareer.domain.roadmap.entity.enums.PhaseStatus; import org.sopt.kareer.domain.roadmap.fixture.PhaseActionFixture; @@ -34,16 +35,21 @@ public class PhaseRepositoryTest { @Autowired private MemberRepository memberRepository; + @Autowired + private RoadmapRepository roadmapRepository; + @Autowired private PhaseActionRepository phaseActionRepository; private Phase phase1; private Member member1; + private Roadmap roadmap1; @BeforeEach void setUp() { member1 = memberRepository.save(MemberFixture.getMember("test-provider-id-1")); - phase1 = phaseRepository.save(PhaseFixture.getPhase(member1, 1, PhaseStatus.CURRENT)); + roadmap1 = roadmapRepository.save(Roadmap.create(member1)); + phase1 = phaseRepository.save(PhaseFixture.getPhase(roadmap1, 1, PhaseStatus.CURRENT)); } @Nested @@ -54,8 +60,8 @@ class FindPhases { @DisplayName("phase 리스트가 sequence 오름차순 정렬되어 반환된다.") void findPhases_ordered() { // given - Phase phase3 = PhaseFixture.getPhase(member1, 3, PhaseStatus.FUTURE); - Phase phase2 = PhaseFixture.getPhase(member1, 2, PhaseStatus.NEXT); + Phase phase3 = PhaseFixture.getPhase(roadmap1, 3, PhaseStatus.FUTURE); + Phase phase2 = PhaseFixture.getPhase(roadmap1, 2, PhaseStatus.NEXT); phaseRepository.saveAll(List.of(phase2, phase3)); // when @@ -74,7 +80,8 @@ void findPhases_ordered() { void findPhases_onlyOwnData() { // given Member member2 = memberRepository.save(MemberFixture.getMember("test-provider-id-2")); - Phase phase2 = phaseRepository.save(PhaseFixture.getPhase(member2, 1, PhaseStatus.CURRENT)); + Roadmap roadmap2 = roadmapRepository.save(Roadmap.create(member2)); + Phase phase2 = phaseRepository.save(PhaseFixture.getPhase(roadmap2, 1, PhaseStatus.CURRENT)); // when List response = phaseRepository.findPhases(member1.getId()); @@ -124,7 +131,8 @@ void getRoadmapPhaseDetail_grouped() { void getRoadmapPhaseDetail_onlyOwnData() { // given Member member2 = memberRepository.save(MemberFixture.getMember("test-provider-id-2")); - Phase phase2 = phaseRepository.save(PhaseFixture.getPhase(member2, 1, PhaseStatus.CURRENT)); + Roadmap roadmap2 = roadmapRepository.save(Roadmap.create(member2)); + Phase phase2 = phaseRepository.save(PhaseFixture.getPhase(roadmap2, 1, PhaseStatus.CURRENT)); PhaseAction phaseActionVisa1 = PhaseActionFixture.getPhaseAction(phase1, PhaseActionType.VISA); PhaseAction phaseActionVisaOfOtherMember = PhaseActionFixture.getPhaseAction(phase2, PhaseActionType.CAREER); @@ -146,10 +154,8 @@ class ExistsByIdAndMemberId { @Test @DisplayName("phaseId와 memberId가 일치하는 Phase가 존재하면 true를 반환한다.") void existsByIdAndMemberId_true() { - // given - // when - boolean result = phaseRepository.existsByIdAndMember_Id(phase1.getId(), member1.getId()); + boolean result = phaseRepository.existsByIdAndRoadmap_Member_Id(phase1.getId(), member1.getId()); // then assertThat(result).isTrue(); @@ -160,8 +166,9 @@ void existsByIdAndMemberId_true() { void existsByIdAndMemberId_false() { // given Member member2 = memberRepository.save(MemberFixture.getMember("test-provider-id-2")); + // when - boolean result = phaseRepository.existsByIdAndMember_Id(phase1.getId(), member2.getId()); + boolean result = phaseRepository.existsByIdAndRoadmap_Member_Id(phase1.getId(), member2.getId()); // then assertThat(result).isFalse(); diff --git a/src/test/java/org/sopt/kareer/domain/roadmap/service/PhaseActionServiceTest.java b/src/test/java/org/sopt/kareer/domain/roadmap/service/PhaseActionServiceTest.java index bb1cb3e..8ed90b1 100644 --- a/src/test/java/org/sopt/kareer/domain/roadmap/service/PhaseActionServiceTest.java +++ b/src/test/java/org/sopt/kareer/domain/roadmap/service/PhaseActionServiceTest.java @@ -9,6 +9,7 @@ import org.sopt.kareer.domain.roadmap.entity.PhaseAction; import org.sopt.kareer.domain.roadmap.entity.PhaseActionGuideline; import org.sopt.kareer.domain.roadmap.entity.PhaseActionMistake; +import org.sopt.kareer.domain.roadmap.entity.Roadmap; import org.sopt.kareer.domain.roadmap.entity.enums.PhaseActionType; import org.sopt.kareer.domain.roadmap.entity.enums.PhaseStatus; import org.sopt.kareer.domain.roadmap.exception.RoadMapException; @@ -19,6 +20,7 @@ import org.sopt.kareer.domain.roadmap.repository.PhaseActionMistakeRepository; import org.sopt.kareer.domain.roadmap.repository.PhaseActionRepository; import org.sopt.kareer.domain.roadmap.repository.PhaseRepository; +import org.sopt.kareer.domain.roadmap.repository.RoadmapRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ActiveProfiles; @@ -35,6 +37,9 @@ public class PhaseActionServiceTest { @Autowired private MemberRepository memberRepository; + @Autowired + private RoadmapRepository roadmapRepository; + @Autowired private PhaseRepository phaseRepository; @@ -52,11 +57,13 @@ public class PhaseActionServiceTest { private Phase phase1; private Member member1; + private Roadmap roadmap1; @BeforeEach void setUp() { member1 = memberRepository.save(MemberFixture.getMember("test-provider-id-1")); - phase1 = phaseRepository.save(PhaseFixture.getPhase(member1, 1, PhaseStatus.CURRENT)); + roadmap1 = roadmapRepository.save(Roadmap.create(member1)); + phase1 = phaseRepository.save(PhaseFixture.getPhase(roadmap1, 1, PhaseStatus.CURRENT)); } @AfterEach @@ -65,6 +72,7 @@ void tearDown() { phaseActionMistakeRepository.deleteAll(); phaseActionRepository.deleteAllInBatch(); phaseRepository.deleteAllInBatch(); + roadmapRepository.deleteAllInBatch(); memberRepository.deleteAllInBatch(); } diff --git a/src/test/java/org/sopt/kareer/domain/roadmap/service/PhaseServiceTest.java b/src/test/java/org/sopt/kareer/domain/roadmap/service/PhaseServiceTest.java index 8bd46e2..0602b12 100644 --- a/src/test/java/org/sopt/kareer/domain/roadmap/service/PhaseServiceTest.java +++ b/src/test/java/org/sopt/kareer/domain/roadmap/service/PhaseServiceTest.java @@ -10,6 +10,7 @@ import org.sopt.kareer.domain.roadmap.dto.response.RoadmapPhaseDetailResponse; import org.sopt.kareer.domain.roadmap.entity.Phase; import org.sopt.kareer.domain.roadmap.entity.PhaseAction; +import org.sopt.kareer.domain.roadmap.entity.Roadmap; import org.sopt.kareer.domain.roadmap.entity.enums.PhaseActionType; import org.sopt.kareer.domain.roadmap.entity.enums.PhaseStatus; import org.sopt.kareer.domain.roadmap.exception.RoadMapException; @@ -18,6 +19,7 @@ import org.sopt.kareer.domain.roadmap.fixture.PhaseFixture; import org.sopt.kareer.domain.roadmap.repository.PhaseActionRepository; import org.sopt.kareer.domain.roadmap.repository.PhaseRepository; +import org.sopt.kareer.domain.roadmap.repository.RoadmapRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ActiveProfiles; @@ -35,6 +37,9 @@ public class PhaseServiceTest { @Autowired private MemberRepository memberRepository; + @Autowired + private RoadmapRepository roadmapRepository; + @Autowired private PhaseRepository phaseRepository; @@ -46,17 +51,20 @@ public class PhaseServiceTest { private Phase phase1; private Member member1; + private Roadmap roadmap1; @BeforeEach void setUp() { member1 = memberRepository.save(MemberFixture.getMember("test-provider-id-1")); - phase1 = phaseRepository.save(PhaseFixture.getPhase(member1, 1, PhaseStatus.CURRENT)); + roadmap1 = roadmapRepository.save(Roadmap.create(member1)); + phase1 = phaseRepository.save(PhaseFixture.getPhase(roadmap1, 1, PhaseStatus.CURRENT)); } @AfterEach void tearDown() { phaseActionRepository.deleteAllInBatch(); phaseRepository.deleteAllInBatch(); + roadmapRepository.deleteAllInBatch(); memberRepository.deleteAllInBatch(); } @@ -68,8 +76,8 @@ class GetPhases { @DisplayName("Phase 리스트를 조회한다.") void getPhases_success() { // given - Phase phase2 = PhaseFixture.getPhase(member1, 2, PhaseStatus.NEXT); - Phase phase3 = PhaseFixture.getPhase(member1, 3, PhaseStatus.FUTURE); + Phase phase2 = PhaseFixture.getPhase(roadmap1, 2, PhaseStatus.NEXT); + Phase phase3 = PhaseFixture.getPhase(roadmap1, 3, PhaseStatus.FUTURE); phaseRepository.saveAll(List.of(phase1, phase2, phase3)); // when @@ -91,9 +99,10 @@ void getPhases_success() { void getPhases_onlyOwnData() { // given Member member2 = memberRepository.save(MemberFixture.getMember("test-provider-id-2")); - Phase phase2 = PhaseFixture.getPhase(member1, 2, PhaseStatus.NEXT); - Phase phase3 = PhaseFixture.getPhase(member1, 3, PhaseStatus.FUTURE); - Phase phase4 = PhaseFixture.getPhase(member2, 2, PhaseStatus.CURRENT); + Roadmap roadmap2 = roadmapRepository.save(Roadmap.create(member2)); + Phase phase2 = PhaseFixture.getPhase(roadmap1, 2, PhaseStatus.NEXT); + Phase phase3 = PhaseFixture.getPhase(roadmap1, 3, PhaseStatus.FUTURE); + Phase phase4 = PhaseFixture.getPhase(roadmap2, 2, PhaseStatus.CURRENT); phaseRepository.saveAll(List.of(phase1, phase2, phase3, phase4)); // when @@ -163,9 +172,9 @@ void getRoadmapPhaseDetail_emptyGrouped() { @DisplayName("로드맵 Phase 상세정보를 조회시 그룹 내 아이템은 deadline 오름차순, 동일 시 title 오름차순으로 정렬된다.") void getRoadmapPhaseDetail_ordered() { // given - PhaseAction phaseActionVisaLateTitle1 = PhaseActionFixture.getPhaseAction(phase1, PhaseActionType.VISA, "test-title-1", LocalDate.of(2026, 3,2)); - PhaseAction phaseActionVisaLateTitle2 = PhaseActionFixture.getPhaseAction(phase1, PhaseActionType.VISA,"test-title-2", LocalDate.of(2026, 3,2)); - PhaseAction phaseActionVisaEarlyTitle1 = PhaseActionFixture.getPhaseAction(phase1, PhaseActionType.VISA, "test-title-1", LocalDate.of(2026, 3,1)); + PhaseAction phaseActionVisaLateTitle1 = PhaseActionFixture.getPhaseAction(phase1, PhaseActionType.VISA, "test-title-1", LocalDate.of(2026, 3, 2)); + PhaseAction phaseActionVisaLateTitle2 = PhaseActionFixture.getPhaseAction(phase1, PhaseActionType.VISA, "test-title-2", LocalDate.of(2026, 3, 2)); + PhaseAction phaseActionVisaEarlyTitle1 = PhaseActionFixture.getPhaseAction(phase1, PhaseActionType.VISA, "test-title-1", LocalDate.of(2026, 3, 1)); phaseActionRepository.saveAll(List.of(phaseActionVisaLateTitle1, phaseActionVisaLateTitle2, phaseActionVisaEarlyTitle1)); @@ -186,7 +195,7 @@ void getRoadmapPhaseDetail_ordered() { @DisplayName("특정 로드맵 Phase 상세정보 조회 시, 다른 Phase의 Action은 포함되지 않는다.") void getRoadmapPhaseDetail_onlyParticularPhaseData() { // given - Phase phase2 = phaseRepository.save(PhaseFixture.getPhase(member1, 1, PhaseStatus.CURRENT)); + Phase phase2 = phaseRepository.save(PhaseFixture.getPhase(roadmap1, 1, PhaseStatus.CURRENT)); PhaseAction phaseActionVisa1 = PhaseActionFixture.getPhaseAction(phase1, PhaseActionType.VISA); PhaseAction phaseActionVisa2 = PhaseActionFixture.getPhaseAction(phase2, PhaseActionType.VISA); phaseActionRepository.saveAll(List.of(phaseActionVisa1, phaseActionVisa2)); @@ -261,7 +270,7 @@ void getHomePhaseDetail_completed() { @DisplayName("특정 홈 Phase 상세정보 조회 시, 다른 Phase의 Action은 포함되지 않는다.") void getHomePhaseDetail_onlyParticularPhaseData() { // given - Phase phase2 = phaseRepository.save(PhaseFixture.getPhase(member1, 1, PhaseStatus.CURRENT)); + Phase phase2 = phaseRepository.save(PhaseFixture.getPhase(roadmap1, 1, PhaseStatus.CURRENT)); PhaseAction phaseActionVisa1 = PhaseActionFixture.getPhaseAction(phase1, PhaseActionType.VISA); PhaseAction phaseActionVisa2 = PhaseActionFixture.getPhaseAction(phase2, PhaseActionType.VISA); phaseActionRepository.saveAll(List.of(phaseActionVisa1, phaseActionVisa2)); diff --git a/src/test/java/org/sopt/kareer/support/ControllerTestSupport.java b/src/test/java/org/sopt/kareer/support/ControllerTestSupport.java index f3ab36f..35b91f4 100644 --- a/src/test/java/org/sopt/kareer/support/ControllerTestSupport.java +++ b/src/test/java/org/sopt/kareer/support/ControllerTestSupport.java @@ -5,6 +5,8 @@ import org.sopt.kareer.domain.jobposting.service.JobPostingCrawler; import org.sopt.kareer.domain.jobposting.service.JobPostingService; import org.sopt.kareer.domain.member.controller.MemberController; +import org.sopt.kareer.domain.member.controller.MemberControllerV2; +import org.sopt.kareer.domain.member.service.LocalizedOnboardQueryService; import org.sopt.kareer.domain.member.service.MemberService; import org.sopt.kareer.domain.roadmap.controller.PhaseActionController; import org.sopt.kareer.domain.roadmap.controller.PhaseController; @@ -31,7 +33,7 @@ import java.util.List; -@WebMvcTest(controllers = {JobPostingController.class, MemberController.class, PhaseController.class, PhaseActionController.class}, +@WebMvcTest(controllers = {JobPostingController.class, MemberController.class, MemberControllerV2.class, PhaseController.class, PhaseActionController.class}, excludeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE) }) @AutoConfigureMockMvc(addFilters = false) @@ -56,6 +58,9 @@ public abstract class ControllerTestSupport { @MockBean protected MemberService memberService; + @MockBean + protected LocalizedOnboardQueryService localizedOnboardQueryService; + @MockBean protected RoadMapService roadMapService; From 9080c72d663017874f3b0e4a2e8b9eb8dfd5c32b Mon Sep 17 00:00:00 2001 From: dlwjddus1112 Date: Wed, 15 Apr 2026 20:55:56 +0900 Subject: [PATCH 16/19] =?UTF-8?q?[Chore]=20#239=20=EC=BB=A8=ED=8A=B8?= =?UTF-8?q?=EB=A1=A4=EB=9F=AC=20=EB=B0=98=ED=99=98=20=EB=A9=94=EC=8B=9C?= =?UTF-8?q?=EC=A7=80=20=EC=98=A4=ED=83=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kareer/domain/member/controller/MemberController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/sopt/kareer/domain/member/controller/MemberController.java b/src/main/java/org/sopt/kareer/domain/member/controller/MemberController.java index 44d0ea4..67b0084 100644 --- a/src/main/java/org/sopt/kareer/domain/member/controller/MemberController.java +++ b/src/main/java/org/sopt/kareer/domain/member/controller/MemberController.java @@ -34,7 +34,7 @@ public ResponseEntity> getMemberInfo(@Authentic return ResponseEntity.status(HttpStatus.OK) .body(BaseResponse.ok(memberService.getMemberInfo(memberId), "회원 정보 조회에 성공하였습니다.")); } - + @Override public ResponseEntity> getOnboardUniversities() { return ResponseEntity.status(HttpStatus.OK) @@ -112,6 +112,6 @@ public ResponseEntity> agreeTerms(@AuthenticationPrincipal Lo @Override public ResponseEntity> getMemberCompletionStatus(@AuthenticationPrincipal Long memberId) { return ResponseEntity.status(HttpStatus.OK) - .body(BaseResponse.ok(memberService.getCompletion(memberId), "온보딩/약관동의 여부 조회에 성공하였습니다,")); + .body(BaseResponse.ok(memberService.getCompletion(memberId), "온보딩/약관동의 여부 조회에 성공하였습니다.")); } } From cf0793cea24537f964aef91f7f895702bc52dd92 Mon Sep 17 00:00:00 2001 From: dlwjddus1112 Date: Wed, 15 Apr 2026 21:02:06 +0900 Subject: [PATCH 17/19] =?UTF-8?q?[Refactor]=20#238=20Phase=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EC=8B=9C=20=EC=83=81=ED=83=9C=20=EA=B8=B0=EC=A4=80?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=A1=B0=ED=9A=8C=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sopt/kareer/domain/roadmap/repository/PhaseRepository.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/org/sopt/kareer/domain/roadmap/repository/PhaseRepository.java b/src/main/java/org/sopt/kareer/domain/roadmap/repository/PhaseRepository.java index a2ff301..8659477 100644 --- a/src/main/java/org/sopt/kareer/domain/roadmap/repository/PhaseRepository.java +++ b/src/main/java/org/sopt/kareer/domain/roadmap/repository/PhaseRepository.java @@ -1,11 +1,14 @@ package org.sopt.kareer.domain.roadmap.repository; import org.sopt.kareer.domain.roadmap.entity.Phase; +import org.sopt.kareer.domain.roadmap.entity.enums.RoadmapActiveStatus; import org.springframework.data.jpa.repository.JpaRepository; public interface PhaseRepository extends JpaRepository, PhaseRepositoryCustom { boolean existsByIdAndRoadmap_Member_Id(Long phaseId, Long memberId); + boolean existsByIdAndRoadmap_Member_IdAndRoadmap_Status(Long phaseId, Long memberId, RoadmapActiveStatus status); + void deleteAllByRoadmap_Member_Id(Long memberId); } From cf478b98446f3407a6a68923cb9b30df4d574e7d Mon Sep 17 00:00:00 2001 From: dlwjddus1112 Date: Wed, 15 Apr 2026 21:17:20 +0900 Subject: [PATCH 18/19] =?UTF-8?q?[Refactor]=20#238=20PhaseAction=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EC=8B=9C=EC=97=90=EB=8F=84=20Roadmap=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../roadmap/repository/PhaseActionRepository.java | 7 ++++++- .../domain/roadmap/repository/PhaseRepository.java | 2 -- .../domain/roadmap/service/PhaseActionService.java | 5 +++-- .../kareer/domain/roadmap/service/PhaseService.java | 11 ++++++----- .../roadmap/repository/PhaseActionRepositoryTest.java | 3 ++- .../roadmap/repository/PhaseRepositoryTest.java | 5 +++-- 6 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/sopt/kareer/domain/roadmap/repository/PhaseActionRepository.java b/src/main/java/org/sopt/kareer/domain/roadmap/repository/PhaseActionRepository.java index 5b75955..5af37a7 100644 --- a/src/main/java/org/sopt/kareer/domain/roadmap/repository/PhaseActionRepository.java +++ b/src/main/java/org/sopt/kareer/domain/roadmap/repository/PhaseActionRepository.java @@ -1,6 +1,7 @@ package org.sopt.kareer.domain.roadmap.repository; import org.sopt.kareer.domain.roadmap.entity.PhaseAction; +import org.sopt.kareer.domain.roadmap.entity.enums.RoadmapActiveStatus; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -20,8 +21,12 @@ public interface PhaseActionRepository extends JpaRepository JOIN FETCH r.member m WHERE pa.id = :phaseActionId AND m.id = :memberId + AND r.status = :status """) - Optional findByIdAndMemberId(@Param("phaseActionId") Long phaseActionId, @Param("memberId") Long memberId); + Optional findByIdAndMemberIdAndRoadmapStatus( + @Param("phaseActionId") Long phaseActionId, + @Param("memberId") Long memberId, + @Param("status") RoadmapActiveStatus status); void deleteAllByPhase_Roadmap_Member_Id(Long memberId); } diff --git a/src/main/java/org/sopt/kareer/domain/roadmap/repository/PhaseRepository.java b/src/main/java/org/sopt/kareer/domain/roadmap/repository/PhaseRepository.java index 8659477..9c28d4c 100644 --- a/src/main/java/org/sopt/kareer/domain/roadmap/repository/PhaseRepository.java +++ b/src/main/java/org/sopt/kareer/domain/roadmap/repository/PhaseRepository.java @@ -6,8 +6,6 @@ public interface PhaseRepository extends JpaRepository, PhaseRepositoryCustom { - boolean existsByIdAndRoadmap_Member_Id(Long phaseId, Long memberId); - boolean existsByIdAndRoadmap_Member_IdAndRoadmap_Status(Long phaseId, Long memberId, RoadmapActiveStatus status); void deleteAllByRoadmap_Member_Id(Long memberId); diff --git a/src/main/java/org/sopt/kareer/domain/roadmap/service/PhaseActionService.java b/src/main/java/org/sopt/kareer/domain/roadmap/service/PhaseActionService.java index 9199ea3..99f694e 100644 --- a/src/main/java/org/sopt/kareer/domain/roadmap/service/PhaseActionService.java +++ b/src/main/java/org/sopt/kareer/domain/roadmap/service/PhaseActionService.java @@ -6,6 +6,7 @@ import org.sopt.kareer.domain.roadmap.entity.ActionItem; import org.sopt.kareer.domain.roadmap.entity.PhaseAction; import org.sopt.kareer.domain.roadmap.entity.PhaseActionTranslation; +import org.sopt.kareer.domain.roadmap.entity.enums.RoadmapActiveStatus; import org.sopt.kareer.domain.roadmap.exception.RoadMapException; import org.sopt.kareer.domain.roadmap.exception.RoadmapErrorCode; import org.sopt.kareer.domain.roadmap.repository.ActionItemRepository; @@ -34,7 +35,7 @@ public class PhaseActionService { @Transactional public void createPhaseActionTodo(Long memberId, Long phaseActionId) { - PhaseAction phaseAction = phaseActionRepository.findByIdAndMemberId(phaseActionId, memberId) + PhaseAction phaseAction = phaseActionRepository.findByIdAndMemberIdAndRoadmapStatus(phaseActionId, memberId, RoadmapActiveStatus.ACTIVE) .orElseThrow(() -> new RoadMapException(RoadmapErrorCode.PHASE_ACTION_NOT_FOUND)); if (phaseAction.getAdded()) { @@ -51,7 +52,7 @@ public void createPhaseActionTodo(Long memberId, Long phaseActionId) { } public AiGuideResponse getAiGuide(Long memberId, Long phaseActionId) { - PhaseAction phaseAction = phaseActionRepository.findByIdAndMemberId(phaseActionId, memberId) + PhaseAction phaseAction = phaseActionRepository.findByIdAndMemberIdAndRoadmapStatus(phaseActionId, memberId, RoadmapActiveStatus.ACTIVE) .orElseThrow(() -> new RoadMapException(RoadmapErrorCode.PHASE_ACTION_NOT_FOUND)); String importance = phaseAction.getImportance(); diff --git a/src/main/java/org/sopt/kareer/domain/roadmap/service/PhaseService.java b/src/main/java/org/sopt/kareer/domain/roadmap/service/PhaseService.java index 8d5566b..22351f5 100644 --- a/src/main/java/org/sopt/kareer/domain/roadmap/service/PhaseService.java +++ b/src/main/java/org/sopt/kareer/domain/roadmap/service/PhaseService.java @@ -1,17 +1,18 @@ package org.sopt.kareer.domain.roadmap.service; import lombok.RequiredArgsConstructor; -import org.sopt.kareer.domain.roadmap.dto.response.*; -import org.sopt.kareer.domain.roadmap.dto.response.PhaseResponse; +import org.sopt.kareer.domain.roadmap.dto.response.HomePhaseDetailResponse; import org.sopt.kareer.domain.roadmap.dto.response.PhaseListResponse; +import org.sopt.kareer.domain.roadmap.dto.response.PhaseResponse; +import org.sopt.kareer.domain.roadmap.dto.response.RoadmapPhaseDetailResponse; import org.sopt.kareer.domain.roadmap.entity.PhaseAction; import org.sopt.kareer.domain.roadmap.entity.PhaseActionTranslation; import org.sopt.kareer.domain.roadmap.entity.PhaseTranslation; +import org.sopt.kareer.domain.roadmap.entity.enums.RoadmapActiveStatus; import org.sopt.kareer.domain.roadmap.exception.RoadMapException; import org.sopt.kareer.domain.roadmap.exception.RoadmapErrorCode; import org.sopt.kareer.domain.roadmap.repository.PhaseActionRepository; import org.sopt.kareer.domain.roadmap.repository.PhaseActionTranslationRepository; -import org.sopt.kareer.domain.roadmap.dto.response.RoadmapPhaseDetailResponse; import org.sopt.kareer.domain.roadmap.repository.PhaseRepository; import org.sopt.kareer.domain.roadmap.repository.PhaseTranslationRepository; import org.springframework.context.i18n.LocaleContextHolder; @@ -60,7 +61,7 @@ public PhaseListResponse getPhases(Long memberId) { } public RoadmapPhaseDetailResponse getRoadmapPhaseDetail(Long memberId, Long phaseId) { - if (!phaseRepository.existsByIdAndRoadmap_Member_Id(phaseId, memberId)) { + if (!phaseRepository.existsByIdAndRoadmap_Member_IdAndRoadmap_Status(phaseId, memberId, RoadmapActiveStatus.ACTIVE)) { throw new RoadMapException(RoadmapErrorCode.PHASE_NOT_FOUND); } Map> raw = @@ -117,7 +118,7 @@ private RoadmapPhaseDetailResponse.ActionGroupResponse wrap( } public HomePhaseDetailResponse getHomePhaseDetail(Long memberId, Long phaseId) { - if (!phaseRepository.existsByIdAndRoadmap_Member_Id(phaseId, memberId)) { + if (!phaseRepository.existsByIdAndRoadmap_Member_IdAndRoadmap_Status(phaseId, memberId, RoadmapActiveStatus.ACTIVE)) { throw new RoadMapException(RoadmapErrorCode.PHASE_NOT_FOUND); } diff --git a/src/test/java/org/sopt/kareer/domain/roadmap/repository/PhaseActionRepositoryTest.java b/src/test/java/org/sopt/kareer/domain/roadmap/repository/PhaseActionRepositoryTest.java index f931ed8..912e1cf 100644 --- a/src/test/java/org/sopt/kareer/domain/roadmap/repository/PhaseActionRepositoryTest.java +++ b/src/test/java/org/sopt/kareer/domain/roadmap/repository/PhaseActionRepositoryTest.java @@ -11,6 +11,7 @@ import org.sopt.kareer.domain.roadmap.entity.Roadmap; import org.sopt.kareer.domain.roadmap.entity.enums.PhaseActionType; import org.sopt.kareer.domain.roadmap.entity.enums.PhaseStatus; +import org.sopt.kareer.domain.roadmap.entity.enums.RoadmapActiveStatus; import org.sopt.kareer.domain.roadmap.fixture.PhaseActionFixture; import org.sopt.kareer.domain.roadmap.fixture.PhaseFixture; import org.sopt.kareer.global.config.QuerydslConfig; @@ -83,7 +84,7 @@ void findByIdAndMemberId() { PhaseAction action1 = phaseActionRepository.save(PhaseActionFixture.getPhaseAction(phase1, PhaseActionType.VISA)); // when - Optional result = phaseActionRepository.findByIdAndMemberId(action1.getId(), member1.getId()); + Optional result = phaseActionRepository.findByIdAndMemberIdAndRoadmapStatus(action1.getId(), member1.getId(), RoadmapActiveStatus.ACTIVE); // then assertThat(result).isPresent(); diff --git a/src/test/java/org/sopt/kareer/domain/roadmap/repository/PhaseRepositoryTest.java b/src/test/java/org/sopt/kareer/domain/roadmap/repository/PhaseRepositoryTest.java index 0951cf7..8cb6cf4 100644 --- a/src/test/java/org/sopt/kareer/domain/roadmap/repository/PhaseRepositoryTest.java +++ b/src/test/java/org/sopt/kareer/domain/roadmap/repository/PhaseRepositoryTest.java @@ -11,6 +11,7 @@ import org.sopt.kareer.domain.roadmap.entity.Roadmap; import org.sopt.kareer.domain.roadmap.entity.enums.PhaseActionType; import org.sopt.kareer.domain.roadmap.entity.enums.PhaseStatus; +import org.sopt.kareer.domain.roadmap.entity.enums.RoadmapActiveStatus; import org.sopt.kareer.domain.roadmap.fixture.PhaseActionFixture; import org.sopt.kareer.domain.roadmap.fixture.PhaseFixture; import org.sopt.kareer.global.config.QuerydslConfig; @@ -155,7 +156,7 @@ class ExistsByIdAndMemberId { @DisplayName("phaseId와 memberId가 일치하는 Phase가 존재하면 true를 반환한다.") void existsByIdAndMemberId_true() { // when - boolean result = phaseRepository.existsByIdAndRoadmap_Member_Id(phase1.getId(), member1.getId()); + boolean result = phaseRepository.existsByIdAndRoadmap_Member_IdAndRoadmap_Status(phase1.getId(), member1.getId(), RoadmapActiveStatus.ACTIVE); // then assertThat(result).isTrue(); @@ -168,7 +169,7 @@ void existsByIdAndMemberId_false() { Member member2 = memberRepository.save(MemberFixture.getMember("test-provider-id-2")); // when - boolean result = phaseRepository.existsByIdAndRoadmap_Member_Id(phase1.getId(), member2.getId()); + boolean result = phaseRepository.existsByIdAndRoadmap_Member_IdAndRoadmap_Status(phase1.getId(), member2.getId(), RoadmapActiveStatus.ACTIVE); // then assertThat(result).isFalse(); From cf86f5707e6bfbb6efc442809c1a5cb6dfce7ea8 Mon Sep 17 00:00:00 2001 From: dlwjddus1112 Date: Thu, 16 Apr 2026 21:45:17 +0900 Subject: [PATCH 19/19] =?UTF-8?q?[Chore]=20#238=20=EC=9D=B4=EC=A0=84=20?= =?UTF-8?q?=EB=B2=84=EC=A0=84=20=EC=98=A8=EB=B3=B4=EB=94=A9=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kareer/domain/member/entity/Member.java | 37 +------------------ 1 file changed, 1 insertion(+), 36 deletions(-) diff --git a/src/main/java/org/sopt/kareer/domain/member/entity/Member.java b/src/main/java/org/sopt/kareer/domain/member/entity/Member.java index 2a21ea0..9bdbf16 100644 --- a/src/main/java/org/sopt/kareer/domain/member/entity/Member.java +++ b/src/main/java/org/sopt/kareer/domain/member/entity/Member.java @@ -74,42 +74,7 @@ public class Member extends BaseEntity { private String preparationStatus; private String fieldsOfInterest; - - // 프론트 온보딩 구현 완료 후 삭제 예정 - public void updateInfo(String name, - LocalDate birthDate, - Country country, - String university, - EnglishLevel englishLevel, - String fieldsOfInterests, - String preparationStatuses, - LanguageLevel languageLevel, - Degree degree, - LocalDate expectedGraduationDate, - String primaryMajor, - String secondaryMajor, - String targetJob, - String targetJobSkill, - String personalBackground) { - assertPendingStatus(); - this.name = name; - this.birthDate = birthDate; - this.countryCode = country.getCountryName(); - this.universityCode = university; - this.englishLevelCode = englishLevel.getDescription(); - this.fieldsOfInterest = fieldsOfInterests; - this.preparationStatus = preparationStatuses; - this.languageLevel = languageLevel; - this.degreeCode = degree.getDescription(); - this.expectedGraduationDate = expectedGraduationDate; - this.primaryMajorCode = primaryMajor; - this.secondaryMajor = secondaryMajor; - this.targetJob = targetJob; - this.targetJobSkill = targetJobSkill; - this.status = MemberStatus.ACTIVE; - this.personalBackground = personalBackground; - } - + public void updateInfoV2(String name, LocalDate birthDate, String countryCode,