Skip to content

Commit 3eae4a5

Browse files
authored
[Refactor] 검증 로직 수정 (#105)
* feat: 멘토 로드맵 수정 * refactor: 권한 검증 로직 변경 * chore: 주석 수정
1 parent e8a8d4c commit 3eae4a5

File tree

10 files changed

+253
-102
lines changed

10 files changed

+253
-102
lines changed
Lines changed: 46 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,27 @@
11
package com.back.domain.roadmap.roadmap.controller;
22

3-
import com.back.domain.member.member.entity.Member;
3+
import com.back.domain.member.member.service.MemberStorage;
4+
import com.back.domain.member.mentor.entity.Mentor;
45
import com.back.domain.roadmap.roadmap.dto.request.MentorRoadmapSaveRequest;
56
import com.back.domain.roadmap.roadmap.dto.response.MentorRoadmapSaveResponse;
67
import com.back.domain.roadmap.roadmap.dto.response.MentorRoadmapResponse;
78
import com.back.domain.roadmap.roadmap.service.MentorRoadmapService;
8-
import com.back.global.exception.ServiceException;
99
import com.back.global.rq.Rq;
1010
import com.back.global.rsData.RsData;
1111
import io.swagger.v3.oas.annotations.Operation;
1212
import io.swagger.v3.oas.annotations.tags.Tag;
1313
import jakarta.validation.Valid;
1414
import lombok.RequiredArgsConstructor;
15+
import org.springframework.security.access.prepost.PreAuthorize;
1516
import org.springframework.web.bind.annotation.*;
1617

1718
@RestController
1819
@RequestMapping("/mentor-roadmaps")
1920
@RequiredArgsConstructor
20-
@Tag(name = "MentorRoadmap", description = "멘토 로드맵 관리 API")
21+
@Tag(name = "MentorRoadmapController", description = "멘토 로드맵 API")
2122
public class MentorRoadmapController {
2223
private final MentorRoadmapService mentorRoadmapService;
24+
private final MemberStorage memberStorage;
2325
private final Rq rq;
2426

2527
@Operation(
@@ -35,15 +37,16 @@ public class MentorRoadmapController {
3537
사용 시나리오:
3638
1. TaskController로 Task 검색
3739
2. Task 선택 시 TaskId와 TaskName 획득
38-
3. Task 없는 경우 TaskId null, TaskName 직접 입력
39-
4. 노드 설명과 입력
40+
3. Task 없는 경우 TaskId는 null, TaskName 직접 입력
41+
4. description(Task에 대한 멘토의 경험, 조언, 학습 방법 등) 입력
4042
"""
4143
)
4244
@PostMapping
45+
@PreAuthorize("hasRole('MENTOR')")
4346
public RsData<MentorRoadmapSaveResponse> create(@Valid @RequestBody MentorRoadmapSaveRequest request) {
44-
Member member = validateMentorAuth();
47+
Mentor mentor = memberStorage.findMentorByMember(rq.getActor());
4548

46-
MentorRoadmapSaveResponse response = mentorRoadmapService.create(member.getId(), request);
49+
MentorRoadmapSaveResponse response = mentorRoadmapService.create(mentor.getId(), request);
4750

4851
return new RsData<>(
4952
"201",
@@ -53,17 +56,19 @@ public RsData<MentorRoadmapSaveResponse> create(@Valid @RequestBody MentorRoadma
5356
}
5457

5558
@Operation(
56-
summary = "멘토 로드맵 상세 조회",
59+
summary = "멘토 로드맵 상세 조회 (로드맵 ID)",
5760
description = """
5861
로드맵 ID로 멘토 로드맵 상세 정보를 조회합니다.
62+
로그인한 사용자만 조회할 수 있습니다.
5963
6064
반환 정보:
6165
- 로드맵 기본 정보 (로드맵 ID, 멘토 ID, 제목, 설명, 생성일, 수정일 등)
6266
- 모든 노드 정보 (stepOrder 순으로 정렬)
6367
"""
6468
)
6569
@GetMapping("/{id}")
66-
public RsData<MentorRoadmapResponse> getByMentorId(@PathVariable Long id) {
70+
@PreAuthorize("isAuthenticated()")
71+
public RsData<MentorRoadmapResponse> getById(@PathVariable Long id) {
6772
MentorRoadmapResponse response = mentorRoadmapService.getById(id);
6873

6974
return new RsData<>(
@@ -73,12 +78,38 @@ public RsData<MentorRoadmapResponse> getByMentorId(@PathVariable Long id) {
7378
);
7479
}
7580

81+
@Operation(
82+
summary = "멘토 로드맵 상세 조회 (멘토 ID)",
83+
description = """
84+
멘토 ID로 해당 멘토의 로드맵 상세 정보를 조회합니다.
85+
로그인한 사용자만 조회할 수 있습니다.
86+
87+
반환 정보:
88+
- 로드맵 기본 정보 (로드맵 ID, 멘토 ID, 제목, 설명, 생성일, 수정일 등)
89+
- 모든 노드 정보 (stepOrder 순으로 정렬)
90+
91+
주의: 멘토가 로드맵을 생성하지 않았다면 404 에러가 발생합니다.
92+
"""
93+
)
94+
@GetMapping("/mentor/{mentorId}")
95+
@PreAuthorize("isAuthenticated()")
96+
public RsData<MentorRoadmapResponse> getByMentorId(@PathVariable Long mentorId) {
97+
MentorRoadmapResponse response = mentorRoadmapService.getByMentorId(mentorId);
98+
99+
return new RsData<>(
100+
"200",
101+
"멘토 로드맵 조회 성공",
102+
response
103+
);
104+
}
105+
76106
@Operation(summary = "멘토 로드맵 수정", description = "로드맵 ID로 로드맵을 찾아 수정합니다. 본인이 생성한 로드맵만 수정할 수 있습니다.")
77107
@PutMapping("/{id}")
108+
@PreAuthorize("hasRole('MENTOR')")
78109
public RsData<MentorRoadmapSaveResponse> update(@PathVariable Long id, @Valid @RequestBody MentorRoadmapSaveRequest request) {
79-
Member member = validateMentorAuth();
110+
Mentor mentor = memberStorage.findMentorByMember(rq.getActor());
80111

81-
MentorRoadmapSaveResponse response = mentorRoadmapService.update(id, member.getId(), request);
112+
MentorRoadmapSaveResponse response = mentorRoadmapService.update(id, mentor.getId(), request);
82113

83114
return new RsData<>(
84115
"200",
@@ -89,11 +120,11 @@ public RsData<MentorRoadmapSaveResponse> update(@PathVariable Long id, @Valid @R
89120

90121
@Operation(summary = "멘토 로드맵 삭제", description = "로드맵 ID로 로드맵을 삭제합니다. 본인이 생성한 로드맵만 삭제할 수 있습니다.")
91122
@DeleteMapping("/{id}")
92-
public RsData<Void> delete( @PathVariable Long id) {
123+
@PreAuthorize("hasRole('MENTOR')")
124+
public RsData<Void> delete(@PathVariable Long id) {
125+
Mentor mentor = memberStorage.findMentorByMember(rq.getActor());
93126

94-
Member member = validateMentorAuth();
95-
96-
mentorRoadmapService.delete(id, member.getId());
127+
mentorRoadmapService.delete(id, mentor.getId());
97128

98129
return new RsData<>(
99130
"200",
@@ -102,15 +133,4 @@ public RsData<Void> delete( @PathVariable Long id) {
102133
);
103134
}
104135

105-
// 멘토 권한 검증
106-
private Member validateMentorAuth() {
107-
Member member = rq.getActor();
108-
if (member == null) {
109-
throw new ServiceException("401", "로그인이 필요합니다.");
110-
}
111-
if (member.getRole() != Member.Role.MENTOR) {
112-
throw new ServiceException("403", "멘토만 접근 가능합니다.");
113-
}
114-
return member;
115-
}
116136
}

back/src/main/java/com/back/domain/roadmap/roadmap/dto/response/MentorRoadmapResponse.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,13 @@ public record MentorRoadmapResponse(
1717
// 정적 팩터리 메서드 - MentorRoadmap로부터 Response DTO 생성
1818
public static MentorRoadmapResponse from(MentorRoadmap mentorRoadmap) {
1919
List<RoadmapNodeResponse> nodeResponses = mentorRoadmap.getNodes().stream()
20+
.sorted((n1, n2) -> Integer.compare(n1.getStepOrder(), n2.getStepOrder())) // stepOrder로 정렬
2021
.map(RoadmapNodeResponse::from)
2122
.toList();
2223

2324
return new MentorRoadmapResponse(
2425
mentorRoadmap.getId(),
25-
mentorRoadmap.getMentorId(),
26+
mentorRoadmap.getMentor().getId(),
2627
mentorRoadmap.getTitle(),
2728
mentorRoadmap.getDescription(),
2829
nodeResponses,

back/src/main/java/com/back/domain/roadmap/roadmap/dto/response/MentorRoadmapSaveResponse.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import java.time.LocalDateTime;
44

5-
// 순수 데이터 전송 객체 - 엔티티에 의존하지 않음
65
public record MentorRoadmapSaveResponse(
76
Long id,
87
Long mentorId,

back/src/main/java/com/back/domain/roadmap/roadmap/dto/response/RoadmapNodeResponse.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public static RoadmapNodeResponse from(RoadmapNode node) {
1616
return new RoadmapNodeResponse(
1717
node.getId(),
1818
node.getTask() != null ? node.getTask().getId() : null,
19-
node.getTask() != null ? node.getTask().getName() : node.getRawTaskName(),
19+
node.getTaskName(), // taskName 필드 직접 사용 (Task 엔티티 접근 불필요)
2020
node.getDescription(),
2121
node.getStepOrder(),
2222
node.getTask() != null

back/src/main/java/com/back/domain/roadmap/roadmap/entity/MentorRoadmap.java

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.back.domain.roadmap.roadmap.entity;
22

3+
import com.back.domain.member.mentor.entity.Mentor;
34
import com.back.global.jpa.BaseEntity;
45
import jakarta.persistence.*;
56
import lombok.Getter;
@@ -20,8 +21,9 @@ public class MentorRoadmap extends BaseEntity {
2021
@Column(name = "description", columnDefinition = "TEXT")
2122
private String description;
2223

23-
@Column(name = "mentor_id", nullable = false)
24-
private Long mentorId; // Mentor 엔티티 FK
24+
@ManyToOne(fetch = FetchType.LAZY)
25+
@JoinColumn(name = "mentor_id", nullable = false)
26+
private Mentor mentor;
2527

2628
@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
2729
@JoinColumn(name = "roadmap_id")
@@ -30,15 +32,18 @@ public class MentorRoadmap extends BaseEntity {
3032
private List<RoadmapNode> nodes;
3133

3234

33-
public MentorRoadmap(Long mentorId, String title, String description) {
34-
this.mentorId = mentorId;
35+
public MentorRoadmap(Mentor mentor, String title, String description) {
36+
this.mentor = mentor;
3537
this.title = title;
3638
this.description = description;
3739
this.nodes = new ArrayList<>();
3840
}
3941

4042
public RoadmapNode getRootNode() {
41-
return nodes.isEmpty() ? null : nodes.get(0);
43+
return nodes.stream()
44+
.filter(node -> node.getStepOrder() == 1)
45+
.findFirst()
46+
.orElse(null);
4247
}
4348

4449
// 노드 추가 헬퍼 메서드 (이미 완전히 초기화된 노드 추가)

back/src/main/java/com/back/domain/roadmap/roadmap/entity/RoadmapNode.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.back.domain.roadmap.task.entity.Task;
44
import com.back.global.jpa.BaseEntity;
55
import jakarta.persistence.*;
6+
import lombok.Builder;
67
import lombok.Getter;
78
import lombok.NoArgsConstructor;
89

@@ -36,7 +37,7 @@ public class RoadmapNode extends BaseEntity {
3637
private int stepOrder = 0;
3738

3839
@Column(name = "raw_task_name")
39-
private String rawTaskName; // 원본 Task 입력값
40+
private String taskName; // Task 이름 표시값(DB에 없는 Task 입력시 입력값 그대로 출력)
4041

4142
@Column(name = "description", columnDefinition = "TEXT")
4243
private String description;
@@ -49,15 +50,18 @@ public enum RoadmapType {
4950
MENTOR, JOB
5051
}
5152

52-
public RoadmapNode(String rawTaskName, String description, Task task, int stepOrder, long roadmapId, RoadmapType roadmapType) {
53-
this.rawTaskName = rawTaskName;
53+
// Builder 패턴 적용된 생성자
54+
@Builder
55+
public RoadmapNode(String taskName, String description, Task task, int stepOrder, long roadmapId, RoadmapType roadmapType) {
56+
this.taskName = taskName;
5457
this.description = description;
5558
this.task = task;
5659
this.stepOrder = stepOrder;
5760
this.roadmapId = roadmapId;
5861
this.roadmapType = roadmapType;
5962
}
6063

64+
6165
public void addChild(RoadmapNode child) {
6266
if (child == null) {
6367
throw new IllegalArgumentException("자식 노드는 null일 수 없습니다.");
Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.back.domain.roadmap.roadmap.repository;
22

3+
import com.back.domain.member.mentor.entity.Mentor;
34
import com.back.domain.roadmap.roadmap.entity.MentorRoadmap;
45
import org.springframework.data.jpa.repository.JpaRepository;
56
import org.springframework.data.jpa.repository.Query;
@@ -9,24 +10,32 @@
910

1011
public interface MentorRoadmapRepository extends JpaRepository<MentorRoadmap, Long> {
1112

12-
@Query("""
13+
/**
14+
* 로드맵 ID로 상세 조회 (노드 포함)
15+
* 로드맵 ID 기반 상세 조회 API용
16+
*/
17+
@Query("""
1318
SELECT mr FROM MentorRoadmap mr
1419
LEFT JOIN FETCH mr.nodes n
15-
LEFT JOIN FETCH n.task
1620
WHERE mr.id = :id
1721
""")
18-
Optional<MentorRoadmap> findByIdWithNodes(@Param("id") Long id);
22+
Optional<MentorRoadmap> findByIdWithNodes(@Param("id") Long id);
1923

24+
/**
25+
* 멘토 ID로 상세 조회 (노드 포함)
26+
* - 멘토 ID 기반 상세 조회 API용
27+
* - 멘토는 이미 WHERE 조건에서 활용되므로 별도 fetch 하지 않음
28+
*/
2029
@Query("""
2130
SELECT mr FROM MentorRoadmap mr
2231
LEFT JOIN FETCH mr.nodes n
23-
LEFT JOIN FETCH n.task
24-
WHERE mr.mentorId = :mentorId
32+
WHERE mr.mentor.id = :mentorId
2533
""")
2634
Optional<MentorRoadmap> findByMentorIdWithNodes(@Param("mentorId") Long mentorId);
2735

28-
// 기본 정보만 조회하는 메서드도 유지
29-
Optional<MentorRoadmap> findByMentorId(Long mentorId);
30-
31-
boolean existsByMentorId(Long mentorId);
36+
/**
37+
* 멘토의 로드맵 존재 여부 확인
38+
* - 로드맵 생성 시 중복 체크용
39+
*/
40+
boolean existsByMentor(Mentor mentor);
3241
}

back/src/main/java/com/back/domain/roadmap/roadmap/repository/RoadmapNodeRepository.java

Lines changed: 0 additions & 41 deletions
This file was deleted.

0 commit comments

Comments
 (0)