Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,25 +1,27 @@
package com.back.domain.roadmap.roadmap.controller;

import com.back.domain.member.member.entity.Member;
import com.back.domain.member.member.service.MemberStorage;
import com.back.domain.member.mentor.entity.Mentor;
import com.back.domain.roadmap.roadmap.dto.request.MentorRoadmapSaveRequest;
import com.back.domain.roadmap.roadmap.dto.response.MentorRoadmapSaveResponse;
import com.back.domain.roadmap.roadmap.dto.response.MentorRoadmapResponse;
import com.back.domain.roadmap.roadmap.service.MentorRoadmapService;
import com.back.global.exception.ServiceException;
import com.back.global.rq.Rq;
import com.back.global.rsData.RsData;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/mentor-roadmaps")
@RequiredArgsConstructor
@Tag(name = "MentorRoadmap", description = "멘토 로드맵 관리 API")
@Tag(name = "MentorRoadmapController", description = "멘토 로드맵 API")
public class MentorRoadmapController {
private final MentorRoadmapService mentorRoadmapService;
private final MemberStorage memberStorage;
private final Rq rq;

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

MentorRoadmapSaveResponse response = mentorRoadmapService.create(member.getId(), request);
MentorRoadmapSaveResponse response = mentorRoadmapService.create(mentor.getId(), request);

return new RsData<>(
"201",
Expand All @@ -53,17 +56,19 @@ public RsData<MentorRoadmapSaveResponse> create(@Valid @RequestBody MentorRoadma
}

@Operation(
summary = "멘토 로드맵 상세 조회",
summary = "멘토 로드맵 상세 조회 (로드맵 ID)",
description = """
로드맵 ID로 멘토 로드맵 상세 정보를 조회합니다.
로그인한 사용자만 조회할 수 있습니다.

반환 정보:
- 로드맵 기본 정보 (로드맵 ID, 멘토 ID, 제목, 설명, 생성일, 수정일 등)
- 모든 노드 정보 (stepOrder 순으로 정렬)
"""
)
@GetMapping("/{id}")
public RsData<MentorRoadmapResponse> getByMentorId(@PathVariable Long id) {
@PreAuthorize("isAuthenticated()")
public RsData<MentorRoadmapResponse> getById(@PathVariable Long id) {
MentorRoadmapResponse response = mentorRoadmapService.getById(id);

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

@Operation(
summary = "멘토 로드맵 상세 조회 (멘토 ID)",
description = """
멘토 ID로 해당 멘토의 로드맵 상세 정보를 조회합니다.
로그인한 사용자만 조회할 수 있습니다.

반환 정보:
- 로드맵 기본 정보 (로드맵 ID, 멘토 ID, 제목, 설명, 생성일, 수정일 등)
- 모든 노드 정보 (stepOrder 순으로 정렬)

주의: 멘토가 로드맵을 생성하지 않았다면 404 에러가 발생합니다.
"""
)
@GetMapping("/mentor/{mentorId}")
@PreAuthorize("isAuthenticated()")
public RsData<MentorRoadmapResponse> getByMentorId(@PathVariable Long mentorId) {
MentorRoadmapResponse response = mentorRoadmapService.getByMentorId(mentorId);

return new RsData<>(
"200",
"멘토 로드맵 조회 성공",
response
);
}

@Operation(summary = "멘토 로드맵 수정", description = "로드맵 ID로 로드맵을 찾아 수정합니다. 본인이 생성한 로드맵만 수정할 수 있습니다.")
@PutMapping("/{id}")
@PreAuthorize("hasRole('MENTOR')")
public RsData<MentorRoadmapSaveResponse> update(@PathVariable Long id, @Valid @RequestBody MentorRoadmapSaveRequest request) {
Member member = validateMentorAuth();
Mentor mentor = memberStorage.findMentorByMember(rq.getActor());

MentorRoadmapSaveResponse response = mentorRoadmapService.update(id, member.getId(), request);
MentorRoadmapSaveResponse response = mentorRoadmapService.update(id, mentor.getId(), request);

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

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

Member member = validateMentorAuth();

mentorRoadmapService.delete(id, member.getId());
mentorRoadmapService.delete(id, mentor.getId());

return new RsData<>(
"200",
Expand All @@ -102,15 +133,4 @@ public RsData<Void> delete( @PathVariable Long id) {
);
}

// 멘토 권한 검증
private Member validateMentorAuth() {
Member member = rq.getActor();
if (member == null) {
throw new ServiceException("401", "로그인이 필요합니다.");
}
if (member.getRole() != Member.Role.MENTOR) {
throw new ServiceException("403", "멘토만 접근 가능합니다.");
}
return member;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@ public record MentorRoadmapResponse(
// 정적 팩터리 메서드 - MentorRoadmap로부터 Response DTO 생성
public static MentorRoadmapResponse from(MentorRoadmap mentorRoadmap) {
List<RoadmapNodeResponse> nodeResponses = mentorRoadmap.getNodes().stream()
.sorted((n1, n2) -> Integer.compare(n1.getStepOrder(), n2.getStepOrder())) // stepOrder로 정렬
.map(RoadmapNodeResponse::from)
.toList();

return new MentorRoadmapResponse(
mentorRoadmap.getId(),
mentorRoadmap.getMentorId(),
mentorRoadmap.getMentor().getId(),
mentorRoadmap.getTitle(),
mentorRoadmap.getDescription(),
nodeResponses,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import java.time.LocalDateTime;

// 순수 데이터 전송 객체 - 엔티티에 의존하지 않음
public record MentorRoadmapSaveResponse(
Long id,
Long mentorId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public static RoadmapNodeResponse from(RoadmapNode node) {
return new RoadmapNodeResponse(
node.getId(),
node.getTask() != null ? node.getTask().getId() : null,
node.getTask() != null ? node.getTask().getName() : node.getRawTaskName(),
node.getTaskName(), // taskName 필드 직접 사용 (Task 엔티티 접근 불필요)
node.getDescription(),
node.getStepOrder(),
node.getTask() != null
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.back.domain.roadmap.roadmap.entity;

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

@Column(name = "mentor_id", nullable = false)
private Long mentorId; // Mentor 엔티티 FK
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "mentor_id", nullable = false)
private Mentor mentor;

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


public MentorRoadmap(Long mentorId, String title, String description) {
this.mentorId = mentorId;
public MentorRoadmap(Mentor mentor, String title, String description) {
this.mentor = mentor;
this.title = title;
this.description = description;
this.nodes = new ArrayList<>();
}

public RoadmapNode getRootNode() {
return nodes.isEmpty() ? null : nodes.get(0);
return nodes.stream()
.filter(node -> node.getStepOrder() == 1)
.findFirst()
.orElse(null);
}

// 노드 추가 헬퍼 메서드 (이미 완전히 초기화된 노드 추가)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.back.domain.roadmap.task.entity.Task;
import com.back.global.jpa.BaseEntity;
import jakarta.persistence.*;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

Expand Down Expand Up @@ -36,7 +37,7 @@ public class RoadmapNode extends BaseEntity {
private int stepOrder = 0;

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

@Column(name = "description", columnDefinition = "TEXT")
private String description;
Expand All @@ -49,15 +50,18 @@ public enum RoadmapType {
MENTOR, JOB
}

public RoadmapNode(String rawTaskName, String description, Task task, int stepOrder, long roadmapId, RoadmapType roadmapType) {
this.rawTaskName = rawTaskName;
// Builder 패턴 적용된 생성자
@Builder
public RoadmapNode(String taskName, String description, Task task, int stepOrder, long roadmapId, RoadmapType roadmapType) {
this.taskName = taskName;
this.description = description;
this.task = task;
this.stepOrder = stepOrder;
this.roadmapId = roadmapId;
this.roadmapType = roadmapType;
}


public void addChild(RoadmapNode child) {
if (child == null) {
throw new IllegalArgumentException("자식 노드는 null일 수 없습니다.");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.back.domain.roadmap.roadmap.repository;

import com.back.domain.member.mentor.entity.Mentor;
import com.back.domain.roadmap.roadmap.entity.MentorRoadmap;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
Expand All @@ -9,24 +10,32 @@

public interface MentorRoadmapRepository extends JpaRepository<MentorRoadmap, Long> {

@Query("""
/**
* 로드맵 ID로 상세 조회 (노드 포함)
* 로드맵 ID 기반 상세 조회 API용
*/
@Query("""
SELECT mr FROM MentorRoadmap mr
LEFT JOIN FETCH mr.nodes n
LEFT JOIN FETCH n.task
WHERE mr.id = :id
""")
Optional<MentorRoadmap> findByIdWithNodes(@Param("id") Long id);
Optional<MentorRoadmap> findByIdWithNodes(@Param("id") Long id);

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

// 기본 정보만 조회하는 메서드도 유지
Optional<MentorRoadmap> findByMentorId(Long mentorId);

boolean existsByMentorId(Long mentorId);
/**
* 멘토의 로드맵 존재 여부 확인
* - 로드맵 생성 시 중복 체크용
*/
boolean existsByMentor(Mentor mentor);
}

This file was deleted.

Loading