Skip to content

Commit 46cc08f

Browse files
committed
refactor: 권한 검증 로직 변경
1 parent c63d62f commit 46cc08f

File tree

5 files changed

+97
-54
lines changed

5 files changed

+97
-54
lines changed
Lines changed: 45 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
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
@@ -20,6 +21,7 @@
2021
@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: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public static MentorRoadmapResponse from(MentorRoadmap mentorRoadmap) {
2323

2424
return new MentorRoadmapResponse(
2525
mentorRoadmap.getId(),
26-
mentorRoadmap.getMentorId(),
26+
mentorRoadmap.getMentor().getId(),
2727
mentorRoadmap.getTitle(),
2828
mentorRoadmap.getDescription(),
2929
nodeResponses,

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

Lines changed: 6 additions & 4 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,8 +32,8 @@ 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<>();
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
1520
WHERE mr.id = :id
16-
ORDER BY n.stepOrder ASC
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-
WHERE mr.mentorId = :mentorId
24-
ORDER BY n.stepOrder ASC
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/service/MentorRoadmapService.java

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.back.domain.roadmap.roadmap.service;
22

3+
import com.back.domain.member.mentor.entity.Mentor;
4+
import com.back.domain.member.mentor.repository.MentorRepository;
35
import com.back.domain.roadmap.roadmap.dto.request.MentorRoadmapSaveRequest;
46
import com.back.domain.roadmap.roadmap.dto.request.RoadmapNodeRequest;
57
import com.back.domain.roadmap.roadmap.dto.response.MentorRoadmapSaveResponse;
@@ -27,43 +29,48 @@
2729
public class MentorRoadmapService {
2830
private final MentorRoadmapRepository mentorRoadmapRepository;
2931
private final TaskRepository taskRepository;
32+
private final MentorRepository mentorRepository;
3033

3134
// 멘토 로드맵 생성
3235
@Transactional
3336
public MentorRoadmapSaveResponse create(Long mentorId, MentorRoadmapSaveRequest request) {
37+
// 멘토 존재 확인
38+
Mentor mentor = mentorRepository.findById(mentorId)
39+
.orElseThrow(() -> new ServiceException("404", "멘토를 찾을 수 없습니다."));
40+
3441
// 멘토가 이미 로드맵을 가지고 있는지 확인
35-
if (mentorRoadmapRepository.existsByMentorId(mentorId)) {
42+
if (mentorRoadmapRepository.existsByMentor(mentor)) {
3643
throw new ServiceException("409", "이미 로드맵이 존재합니다. 멘토는 하나의 로드맵만 생성할 수 있습니다.");
3744
}
3845

39-
// 공통 검증
46+
// 공통 검증(노드 개수, stepOrder 연속성)
4047
validateRequest(request);
4148

4249
// MentorRoadmap 생성 및 저장 (로드맵 ID 확보)
43-
MentorRoadmap mentorRoadmap = new MentorRoadmap(mentorId, request.title(), request.description());
50+
MentorRoadmap mentorRoadmap = new MentorRoadmap(mentor, request.title(), request.description());
4451
mentorRoadmap = mentorRoadmapRepository.save(mentorRoadmap);
4552

4653
// roadmapId를 포함한 노드 생성 및 추가
4754
List<RoadmapNode> allNodes = createValidatedNodesWithRoadmapId(request.nodes(), mentorRoadmap.getId());
4855
mentorRoadmap.addNodes(allNodes);
4956

5057
// 최종 저장 (노드들 CASCADE INSERT)
51-
mentorRoadmap = saveRoadmap(mentorRoadmap);
58+
mentorRoadmap = mentorRoadmapRepository.save(mentorRoadmap);
5259

5360
log.info("멘토 로드맵 생성 완료 - 멘토 ID: {}, 로드맵 ID: {}, 노드 수: {} (cascade 활용)",
5461
mentorId, mentorRoadmap.getId(), mentorRoadmap.getNodes().size());
5562

5663
return new MentorRoadmapSaveResponse(
5764
mentorRoadmap.getId(),
58-
mentorRoadmap.getMentorId(),
65+
mentorRoadmap.getMentor().getId(),
5966
mentorRoadmap.getTitle(),
6067
mentorRoadmap.getDescription(),
6168
mentorRoadmap.getNodes().size(),
6269
mentorRoadmap.getCreateDate()
6370
);
6471
}
6572

66-
// 멘토 ID로 멘토 로드맵 상세 조회
73+
// 로드맵 ID로 멘토 로드맵 상세 조회
6774
@Transactional(readOnly = true)
6875
public MentorRoadmapResponse getById(Long id) {
6976
// 로드맵과 노드들을 한 번에 조회 (성능 최적화)
@@ -73,6 +80,16 @@ public MentorRoadmapResponse getById(Long id) {
7380
return MentorRoadmapResponse.from(mentorRoadmap);
7481
}
7582

83+
// 멘토 ID로 멘토 로드맵 상세 조회 (미래 API 확장성 대비)
84+
@Transactional(readOnly = true)
85+
public MentorRoadmapResponse getByMentorId(Long mentorId) {
86+
// 멘토 ID로 로드맵과 노드들을 한 번에 조회 (성능 최적화)
87+
MentorRoadmap mentorRoadmap = mentorRoadmapRepository.findByMentorIdWithNodes(mentorId)
88+
.orElseThrow(() -> new ServiceException("404", "해당 멘토의 로드맵을 찾을 수 없습니다."));
89+
90+
return MentorRoadmapResponse.from(mentorRoadmap);
91+
}
92+
7693
// 멘토 로드맵 수정
7794
@Transactional
7895
public MentorRoadmapSaveResponse update(Long id, Long mentorId, MentorRoadmapSaveRequest request) {
@@ -81,7 +98,7 @@ public MentorRoadmapSaveResponse update(Long id, Long mentorId, MentorRoadmapSav
8198
.orElseThrow(() -> new ServiceException("404", "로드맵을 찾을 수 없습니다."));
8299

83100
// 권한 확인 - 본인의 로드맵만 수정 가능
84-
if (!mentorRoadmap.getMentorId().equals(mentorId)) {
101+
if (!mentorRoadmap.getMentor().getId().equals(mentorId)) {
85102
throw new ServiceException("403", "본인의 로드맵만 수정할 수 있습니다.");
86103
}
87104

@@ -98,14 +115,14 @@ public MentorRoadmapSaveResponse update(Long id, Long mentorId, MentorRoadmapSav
98115
mentorRoadmap.addNodes(allNodes);
99116

100117
// 최종 저장 (노드들 CASCADE INSERT)
101-
mentorRoadmap = saveRoadmap(mentorRoadmap);
118+
mentorRoadmap = mentorRoadmapRepository.save(mentorRoadmap);
102119

103120
log.info("멘토 로드맵 수정 완료 - 로드맵 ID: {}, 노드 수: {} (cascade 활용)",
104121
mentorRoadmap.getId(), mentorRoadmap.getNodes().size());
105122

106123
return new MentorRoadmapSaveResponse(
107124
mentorRoadmap.getId(),
108-
mentorRoadmap.getMentorId(),
125+
mentorRoadmap.getMentor().getId(),
109126
mentorRoadmap.getTitle(),
110127
mentorRoadmap.getDescription(),
111128
mentorRoadmap.getNodes().size(),
@@ -120,7 +137,7 @@ public void delete(Long roadmapId, Long mentorId) {
120137
.orElseThrow(() -> new ServiceException("404", "로드맵을 찾을 수 없습니다."));
121138

122139
// 권한 확인
123-
if (!mentorRoadmap.getMentorId().equals(mentorId)) {
140+
if (!mentorRoadmap.getMentor().getId().equals(mentorId)) {
124141
throw new ServiceException("403", "본인의 로드맵만 삭제할 수 있습니다.");
125142
}
126143

@@ -164,11 +181,6 @@ private void validateStepOrderSequence(List<RoadmapNodeRequest> nodes) {
164181
}
165182
}
166183

167-
// 로드맵 저장 (노드들도 cascade로 함께 저장)
168-
private MentorRoadmap saveRoadmap(MentorRoadmap mentorRoadmap) {
169-
// 노드에 roadmapId가 이미 설정된 상태로 한 번만 저장
170-
return mentorRoadmapRepository.save(mentorRoadmap);
171-
}
172184

173185

174186
// Task 유효성 검증 후 RoadmapNode 리스트 생성 (roadmapId 포함)

0 commit comments

Comments
 (0)