diff --git a/back/src/main/java/com/back/domain/node/controller/BaseLineController.java b/back/src/main/java/com/back/domain/node/controller/BaseLineController.java index d470f2a..c3c7732 100644 --- a/back/src/main/java/com/back/domain/node/controller/BaseLineController.java +++ b/back/src/main/java/com/back/domain/node/controller/BaseLineController.java @@ -6,14 +6,18 @@ */ package com.back.domain.node.controller; -import com.back.domain.node.dto.*; +import com.back.domain.node.dto.PivotListDto; +import com.back.domain.node.dto.TreeDto; import com.back.domain.node.dto.base.BaseLineBulkCreateRequest; import com.back.domain.node.dto.base.BaseLineBulkCreateResponse; +import com.back.domain.node.dto.base.BaseLineDto; import com.back.domain.node.dto.base.BaseNodeDto; import com.back.domain.node.service.NodeService; +import com.back.global.security.CustomUserDetails; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; import java.util.List; @@ -55,4 +59,13 @@ public ResponseEntity getTreeForBaseLine(@PathVariable Long baseLineId) TreeDto tree = nodeService.getTreeForBaseLine(baseLineId); return ResponseEntity.ok(tree); } + + // 내가 만든 베이스라인 목록 조회 + @GetMapping("/mine") + public ResponseEntity> getMyBaseLines( + @AuthenticationPrincipal CustomUserDetails me + ) { + List list = nodeService.getMyBaseLines(me.getId()); + return ResponseEntity.ok(list); + } } diff --git a/back/src/main/java/com/back/domain/node/dto/base/BaseLineDto.java b/back/src/main/java/com/back/domain/node/dto/base/BaseLineDto.java new file mode 100644 index 0000000..67eb784 --- /dev/null +++ b/back/src/main/java/com/back/domain/node/dto/base/BaseLineDto.java @@ -0,0 +1,8 @@ +package com.back.domain.node.dto.base; + +/** + * BaseLine 요약 DTO + * - 프론트 첫 리스트용 + */ + +public record BaseLineDto(Long id, String title) {} diff --git a/back/src/main/java/com/back/domain/node/mapper/NodeMappers.java b/back/src/main/java/com/back/domain/node/mapper/NodeMappers.java index 6d5de4f..92c7ca5 100644 --- a/back/src/main/java/com/back/domain/node/mapper/NodeMappers.java +++ b/back/src/main/java/com/back/domain/node/mapper/NodeMappers.java @@ -7,15 +7,16 @@ package com.back.domain.node.mapper; import com.back.domain.node.dto.base.BaseLineBulkCreateRequest; +import com.back.domain.node.dto.base.BaseLineDto; import com.back.domain.node.dto.base.BaseNodeCreateRequestDto; import com.back.domain.node.dto.base.BaseNodeDto; import com.back.domain.node.dto.decision.DecNodeDto; import com.back.domain.node.dto.decision.DecisionNodeCreateRequestDto; -import com.back.global.mapper.Mapper; -import com.back.global.mapper.TwoWayMapper; -import com.back.global.mapper.MappingException; import com.back.domain.node.entity.*; import com.back.domain.user.entity.User; +import com.back.global.mapper.Mapper; +import com.back.global.mapper.MappingException; +import com.back.global.mapper.TwoWayMapper; import java.util.ArrayList; import java.util.List; @@ -24,6 +25,16 @@ public final class NodeMappers { private NodeMappers() {} + // BaseLine -> BaseLineDto + public static final Mapper BASELINE_READ = e -> { + if (e == null) throw new MappingException("BaseLine is null"); + return new BaseLineDto( + e.getId(), + e.getTitle() + ); + }; + + // BaseNode -> BaseLineDto public static final Mapper BASE_READ = e -> { if (e == null) throw new MappingException("BaseNode is null"); diff --git a/back/src/main/java/com/back/domain/node/repository/BaseLineRepository.java b/back/src/main/java/com/back/domain/node/repository/BaseLineRepository.java index 0bb05de..04f2029 100644 --- a/back/src/main/java/com/back/domain/node/repository/BaseLineRepository.java +++ b/back/src/main/java/com/back/domain/node/repository/BaseLineRepository.java @@ -5,6 +5,7 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.List; import java.util.Optional; /** @@ -17,5 +18,8 @@ public interface BaseLineRepository extends JpaRepository { // Guest 베이스라인 1개 제한 확인용 boolean existsByUser_id(Long userId); + + List findByUser_IdOrderByIdDesc(Long userId); + boolean existsByUserAndTitle(User user, String title); } \ No newline at end of file diff --git a/back/src/main/java/com/back/domain/node/service/NodeQueryService.java b/back/src/main/java/com/back/domain/node/service/NodeQueryService.java index 6c349b4..18d8c05 100644 --- a/back/src/main/java/com/back/domain/node/service/NodeQueryService.java +++ b/back/src/main/java/com/back/domain/node/service/NodeQueryService.java @@ -9,7 +9,8 @@ */ package com.back.domain.node.service; -import com.back.domain.node.dto.*; +import com.back.domain.node.dto.TreeDto; +import com.back.domain.node.dto.base.BaseLineDto; import com.back.domain.node.dto.base.BaseNodeDto; import com.back.domain.node.dto.decision.DecNodeDto; import com.back.domain.node.dto.decision.DecisionLineDetailDto; @@ -18,6 +19,7 @@ import com.back.domain.node.entity.DecisionLine; import com.back.domain.node.entity.DecisionNode; import com.back.domain.node.mapper.NodeMappers; +import com.back.domain.node.repository.BaseLineRepository; import com.back.domain.node.repository.BaseNodeRepository; import com.back.domain.node.repository.DecisionLineRepository; import com.back.domain.node.repository.DecisionNodeRepository; @@ -42,6 +44,7 @@ public class NodeQueryService { private final BaseNodeRepository baseNodeRepository; private final DecisionNodeRepository decisionNodeRepository; private final DecisionLineRepository decisionLineRepository; + private final BaseLineRepository baseLineRepository; private final NodeDomainSupport support; // 가장 중요한: 특정 BaseLine 전체 트리 조회 @@ -127,4 +130,11 @@ public DecisionLineDetailDto getDecisionLineDetail(Long decisionLineId) { nodeDtos ); } + + public List getMyBaseLines(Long userId) { + return baseLineRepository.findByUser_IdOrderByIdDesc(userId) + .stream() + .map(NodeMappers.BASELINE_READ::map) + .toList(); + } } diff --git a/back/src/main/java/com/back/domain/node/service/NodeService.java b/back/src/main/java/com/back/domain/node/service/NodeService.java index e0ab224..55d9c30 100644 --- a/back/src/main/java/com/back/domain/node/service/NodeService.java +++ b/back/src/main/java/com/back/domain/node/service/NodeService.java @@ -8,6 +8,7 @@ import com.back.domain.node.dto.*; import com.back.domain.node.dto.base.BaseLineBulkCreateRequest; import com.back.domain.node.dto.base.BaseLineBulkCreateResponse; +import com.back.domain.node.dto.base.BaseLineDto; import com.back.domain.node.dto.base.BaseNodeDto; import com.back.domain.node.dto.decision.*; import lombok.RequiredArgsConstructor; @@ -74,4 +75,8 @@ public BaseNodeDto getBaseNode(Long baseNodeId) { public DecNodeDto forkFromDecision(ForkFromDecisionRequest request) { return decisionFlowService.forkFromDecision(request); } + + public List getMyBaseLines(Long id) { + return nodeQueryService.getMyBaseLines(id); + } } diff --git a/back/src/test/java/com/back/domain/node/controller/BaseLineControllerTest.java b/back/src/test/java/com/back/domain/node/controller/BaseLineControllerTest.java index 120bb3e..93a0be3 100644 --- a/back/src/test/java/com/back/domain/node/controller/BaseLineControllerTest.java +++ b/back/src/test/java/com/back/domain/node/controller/BaseLineControllerTest.java @@ -13,6 +13,7 @@ import com.back.domain.node.repository.BaseNodeRepository; import com.back.domain.user.entity.*; import com.back.domain.user.repository.UserRepository; +import com.back.global.security.CustomUserDetails; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.*; @@ -20,6 +21,9 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.jdbc.SqlConfig; import org.springframework.test.web.servlet.MockMvc; @@ -481,6 +485,70 @@ private Long saveAndGetBaseLineId() throws Exception { } } + @Nested + @DisplayName("내 베이스라인 목록(/mine)") + class BaseLine_Mine { + + @AfterEach + void clearCtx() { SecurityContextHolder.clearContext(); } + + @Test + @DisplayName("성공 : /base-lines/mine — 최소 1개 라인 생성 후 목록에 id/title이 포함되어 반환된다") + void success_mine_returnsList() throws Exception { + var created = mockMvc.perform(post("/api/v1/base-lines/bulk") + .contentType(MediaType.APPLICATION_JSON) + .content(sampleLineJson(userId))) + .andExpect(status().isCreated()) + .andReturn(); + + // ★ 인증 세팅 + var me = userRepository.findById(userId).orElseThrow(); + setAuth(new CustomUserDetails(me)); + + // when/then + mockMvc.perform(get("/api/v1/base-lines/mine")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].id").exists()) + .andExpect(jsonPath("$[0].title").exists()); + } + + @Test + @DisplayName("성공 : /base-lines/mine — 라인이 없는 사용자에겐 빈 배열([])을 반환한다") + void success_mine_emptyForUserWithoutLines() throws Exception { + // given: 라인 없는 사용자 + String uid = UUID.randomUUID().toString().substring(0, 8); + User emptyUser = User.builder() + .email("nouser_" + uid + "@test.local") + .role(Role.USER) + .birthdayAt(LocalDateTime.now().minusYears(20)) + .gender(Gender.F) + .mbti(Mbti.INFP) + .beliefs("NONE") + .authProvider(AuthProvider.LOCAL) + .nickname("nouser-" + uid) + .username("nouser-" + uid) + .build(); + userRepository.save(emptyUser); + + // ★ 인증 세팅 + setAuth(new CustomUserDetails(emptyUser)); + + // when/then + mockMvc.perform(get("/api/v1/base-lines/mine")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.length()").value(0)); + } + } + + + // 가장 중요한 함수 한줄 요약: SecurityContextHolder에 인증 토큰 세팅 + private void setAuth(CustomUserDetails cud) { + SecurityContext ctx = SecurityContextHolder.createEmptyContext(); + ctx.setAuthentication(new UsernamePasswordAuthenticationToken(cud, null, cud.getAuthorities())); + SecurityContextHolder.setContext(ctx); + } + + // (자주 쓰는) 정상 입력 샘플 JSON 생성 private String sampleLineJson(Long uid) { @@ -496,4 +564,7 @@ private String sampleLineJson(Long uid) { """.formatted(uid, NodeCategory.EDUCATION, NodeCategory.CAREER, NodeCategory.CAREER, NodeCategory.ETC); } + + + }