Skip to content

Commit f699817

Browse files
committed
[Refactor] 페이지네이션 1-based로 수정 및 시나리오 initãdata생성
1 parent 5d85e32 commit f699817

File tree

6 files changed

+218
-16
lines changed

6 files changed

+218
-16
lines changed

back/src/main/java/com/back/domain/scenario/controller/ScenarioController.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22

33
import com.back.domain.scenario.dto.*;
44
import com.back.domain.scenario.service.ScenarioService;
5+
import com.back.global.common.PageResponse;
56
import com.back.global.security.CustomUserDetails;
67
import io.swagger.v3.oas.annotations.Operation;
78
import io.swagger.v3.oas.annotations.Parameter;
89
import io.swagger.v3.oas.annotations.tags.Tag;
910
import jakarta.validation.Valid;
1011
import lombok.RequiredArgsConstructor;
11-
import org.springframework.data.domain.Page;
1212
import org.springframework.data.domain.Pageable;
1313
import org.springframework.http.HttpStatus;
1414
import org.springframework.http.ResponseEntity;
@@ -92,14 +92,14 @@ public ResponseEntity<TimelineResponse> getScenarioTimeline(
9292
}
9393

9494
@GetMapping("/baselines")
95-
@Operation(summary = "베이스라인 목록 조회", description = "사용자의 베이스라인 목록을 페이지네이션으로 조회합니다.")
96-
public ResponseEntity<Page<BaselineListResponse>> getBaselines(
95+
@Operation(summary = "베이스라인 목록 조회", description = "사용자의 베이스라인 목록을 페이지네이션으로 조회합니다. (1-based 페이지네이션)")
96+
public ResponseEntity<PageResponse<BaselineListResponse>> getBaselines(
9797
@AuthenticationPrincipal CustomUserDetails userDetails,
9898
Pageable pageable
9999
) {
100100
Long userId = getUserId(userDetails);
101101

102-
Page<BaselineListResponse> baselines = scenarioService.getBaselines(userId, pageable);
102+
PageResponse<BaselineListResponse> baselines = scenarioService.getBaselines(userId, pageable);
103103

104104
return ResponseEntity.ok(baselines);
105105
}

back/src/main/java/com/back/domain/scenario/entity/Scenario.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@ public class Scenario extends BaseEntity {
3434
@JoinColumn(name = "decision_line_id", unique = true)
3535
private DecisionLine decisionLine;
3636

37-
// 시나리오 비교 분석 대상 베이스 시나리오 (선택 경로의 베이스라인과 연결)
38-
@OneToOne(fetch = FetchType.LAZY)
39-
@JoinColumn(name = "base_line_id", unique = true)
37+
// 시나리오가 속한 베이스라인 (하나의 BaseLine에 여러 Scenario 가능)
38+
@ManyToOne(fetch = FetchType.LAZY)
39+
@JoinColumn(name = "base_line_id", nullable = false)
4040
private BaseLine baseLine;
4141

4242
// 시나리오 처리 상태 (PENDING, PROCESSING, COMPLETED, FAILED)

back/src/main/java/com/back/domain/scenario/service/ScenarioService.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import com.back.global.ai.dto.result.BaseScenarioResult;
1616
import com.back.global.ai.dto.result.DecisionScenarioResult;
1717
import com.back.global.ai.service.AiService;
18+
import com.back.global.common.PageResponse;
1819
import com.back.global.exception.ApiException;
1920
import com.back.global.exception.ErrorCode;
2021
import com.fasterxml.jackson.core.type.TypeReference;
@@ -104,9 +105,13 @@ public ScenarioStatusResponse createScenario(Long userId, ScenarioCreateRequest
104105

105106
// 새 시나리오 생성 (DataIntegrityViolationException 처리)
106107
try {
108+
// DecisionLine에서 BaseLine 가져오기
109+
BaseLine baseLine = decisionLine.getBaseLine();
110+
107111
Scenario scenario = Scenario.builder()
108112
.user(decisionLine.getUser())
109113
.decisionLine(decisionLine)
114+
.baseLine(baseLine) // DecisionLine의 BaseLine 연결
110115
.status(ScenarioStatus.PENDING)
111116
.build();
112117

@@ -327,12 +332,15 @@ public ScenarioCompareResponse compareScenarios(Long baseId, Long compareId, Lon
327332

328333
// 베이스라인 목록 조회 (페이지네이션 지원)
329334
@Transactional(readOnly = true)
330-
public Page<BaselineListResponse> getBaselines(Long userId, Pageable pageable) {
335+
public PageResponse<BaselineListResponse> getBaselines(Long userId, Pageable pageable) {
331336
// 사용자별 베이스라인 조회 (BaseNode들과 함께 fetch)
332337
Page<BaseLine> baseLines = baseLineRepository.findAllByUserIdWithBaseNodes(userId, pageable);
333338

334339
// BaseLine -> BaselineListResponse 변환
335-
return baseLines.map(this::convertToBaselineListResponse);
340+
Page<BaselineListResponse> responsePage = baseLines.map(this::convertToBaselineListResponse);
341+
342+
// PageResponse로 변환 (1-based 페이지네이션)
343+
return PageResponse.of(responsePage);
336344
}
337345

338346
/**

back/src/main/java/com/back/global/initdata/InitData.java

Lines changed: 191 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,21 @@
88
import com.back.domain.node.dto.decision.DecNodeDto;
99
import com.back.domain.node.dto.decision.DecisionNodeFromBaseRequest;
1010
import com.back.domain.node.dto.decision.DecisionNodeNextRequest;
11+
import com.back.domain.node.entity.BaseLine;
12+
import com.back.domain.node.entity.DecisionLine;
1113
import com.back.domain.node.entity.NodeCategory;
14+
import com.back.domain.node.repository.BaseLineRepository;
15+
import com.back.domain.node.repository.DecisionLineRepository;
1216
import com.back.domain.node.service.NodeService;
1317
import com.back.domain.post.entity.Post;
1418
import com.back.domain.post.enums.PostCategory;
1519
import com.back.domain.post.repository.PostRepository;
20+
import com.back.domain.scenario.entity.SceneType;
21+
import com.back.domain.scenario.entity.Scenario;
22+
import com.back.domain.scenario.entity.ScenarioStatus;
23+
import com.back.domain.scenario.entity.Type;
24+
import com.back.domain.scenario.repository.SceneTypeRepository;
25+
import com.back.domain.scenario.repository.ScenarioRepository;
1626
import com.back.domain.user.entity.Gender;
1727
import com.back.domain.user.entity.Mbti;
1828
import com.back.domain.user.entity.Role;
@@ -29,8 +39,9 @@
2939
import java.util.UUID;
3040

3141
/**
32-
* [요약] 기동 시 admin·user1 생성 → user1에 베이스라인(총7: 헤더+피벗5+테일) 1개와 결정라인(총5 노드) 1개 시드 주입.
42+
* [요약] 기동 시 admin·user1 생성 → user1에 베이스라인(총7: 헤더+피벗5+테일) 1개와 결정라인 2개(첫 번째 6노드, 두 번째 2노드) 시드 주입.
3343
* 게시글 30개(일반20 + 투표10)과 댓글 14개(마지막 2개 글에 각 7개) 생성.
44+
* 시나리오 3개(베이스 1개 + 완성 1개 + 처리중 1개)와 지표 10개(완성된 시나리오 2개에 각 5개씩) 생성.
3445
*/
3546
@Component
3647
@RequiredArgsConstructor
@@ -43,6 +54,11 @@ public class InitData implements CommandLineRunner {
4354
private final PostRepository postRepository;
4455
private final CommentRepository commentRepository;
4556

57+
private final BaseLineRepository baseLineRepository;
58+
private final DecisionLineRepository decisionLineRepository;
59+
private final ScenarioRepository scenarioRepository;
60+
private final SceneTypeRepository sceneTypeRepository;
61+
4662
// user1을 만들고 베이스라인(7)과 결정라인(5)을 시드로 주입한다
4763
@Override
4864
public void run(String... args) {
@@ -161,7 +177,7 @@ public void run(String... args) {
161177
)
162178
);
163179

164-
nodeService.createDecisionNodeNext(
180+
DecNodeDto d4 = nodeService.createDecisionNodeNext(
165181
new DecisionNodeNextRequest(
166182
user1.getId(),
167183
d3.id(),
@@ -175,6 +191,36 @@ public void run(String... args) {
175191
)
176192
);
177193

194+
// 두 번째 DecisionLine 생성 (processingScenario 테스트용)
195+
DecNodeDto d5 = nodeService.createDecisionNodeFromBase(
196+
new DecisionNodeFromBaseRequest(
197+
user1.getId(),
198+
baseLineId,
199+
1, // 두 번째 피벗
200+
null,
201+
0,
202+
NodeCategory.CAREER,
203+
"스타트업 창업",
204+
List.of("단독 창업", "공동 창업"),
205+
0,
206+
"기술 스타트업 설립 선택지"
207+
)
208+
);
209+
210+
DecNodeDto d6 = nodeService.createDecisionNodeNext(
211+
new DecisionNodeNextRequest(
212+
user1.getId(),
213+
d5.id(),
214+
NodeCategory.FINANCE,
215+
"초기 투자 유치",
216+
null,
217+
List.of("엔젤 투자", "시드 투자"),
218+
0,
219+
0,
220+
"초기 자금 확보 전략"
221+
)
222+
);
223+
178224
if (postRepository.count() > 0) {
179225
return;
180226
}
@@ -238,5 +284,148 @@ public void run(String... args) {
238284
}
239285
}
240286
commentRepository.saveAll(comments);
287+
288+
// ========== Scenario InitData 생성 ==========
289+
290+
// 시나리오 데이터가 이미 있으면 스킵
291+
if (scenarioRepository.count() > 0) {
292+
return;
293+
}
294+
295+
// BaseLine 조회 (위에서 생성한 baseLineId 사용)
296+
BaseLine baseLine = baseLineRepository.findById(baseLineId)
297+
.orElseThrow(() -> new IllegalStateException("BaseLine not found"));
298+
299+
// DecisionLine 조회 (user1의 DecisionLine - BaseLine에 연결된 것)
300+
List<DecisionLine> decisionLines = decisionLineRepository.findByBaseLine_Id(baseLineId);
301+
if (decisionLines.size() < 2) {
302+
throw new IllegalStateException("Expected 2 DecisionLines but found: " + decisionLines.size());
303+
}
304+
305+
DecisionLine decisionLine1 = decisionLines.get(0); // 첫 번째 DecisionLine (완성 시나리오용)
306+
DecisionLine decisionLine2 = decisionLines.get(1); // 두 번째 DecisionLine (처리중 시나리오용)
307+
308+
// 1. 베이스 시나리오 생성 (현재 삶 기준)
309+
Scenario baseScenario = Scenario.builder()
310+
.user(user1)
311+
.baseLine(baseLine)
312+
.decisionLine(null) // 베이스 시나리오는 DecisionLine 없음
313+
.status(ScenarioStatus.COMPLETED)
314+
.job("주니어 백엔드 개발자")
315+
.total(350)
316+
.summary("현재 커리어 경로를 유지하며 안정적으로 성장하는 시나리오입니다.")
317+
.description("""
318+
대학 졸업 후 백엔드 개발자로 시작하여 주니어에서 시니어로 성장하는 과정입니다.
319+
안정적인 중견기업에서 경력을 쌓으며, 스프링 부트와 API 설계 전문성을 키워나갑니다.
320+
팀 내에서 신뢰받는 개발자로 성장하며, 워라밸을 유지하면서 건강한 개발 생활을 이어갑니다.
321+
""")
322+
.timelineTitles("""
323+
[
324+
{"year":2025,"title":"시니어 개발자 승진"},
325+
{"year":2027,"title":"테크 리드 역할 수행"},
326+
{"year":2030,"title":"개발팀 리더"}
327+
]
328+
""")
329+
.img("https://picsum.photos/seed/base-scenario/400/300")
330+
.build();
331+
scenarioRepository.save(baseScenario);
332+
333+
// 베이스 시나리오 지표 생성 (5개)
334+
createSceneTypes(baseScenario, 70, 75, 70, 65, 70);
335+
336+
// 2. 완성된 시나리오 생성 (첫 번째 DecisionLine 기반)
337+
Scenario completedScenario = Scenario.builder()
338+
.user(user1)
339+
.baseLine(baseLine) // 베이스 시나리오와 같은 BaseLine 참조
340+
.decisionLine(decisionLine1)
341+
.status(ScenarioStatus.COMPLETED)
342+
.job("클라우드 아키텍트")
343+
.total(430)
344+
.summary("클라우드와 보안 전문성을 갖춘 시니어 아키텍트로 성장하는 시나리오입니다.")
345+
.description("""
346+
AWS/GCP 클라우드 플랫폼 심화 학습과 보안 인증을 통해 시스템 아키텍트로 성장합니다.
347+
대용량 트래픽 처리 경험과 인프라 자동화 능력을 갖추어 기술 리더로 인정받습니다.
348+
컨설팅 프로젝트와 기술 강연을 통해 업계 전문가로 자리매김하며,
349+
궁극적으로 스타트업 CTO 또는 대기업 기술 이사로 성장합니다.
350+
""")
351+
.timelineTitles("""
352+
[
353+
{"year":2025,"title":"AWS Solutions Architect 자격증 취득"},
354+
{"year":2026,"title":"보안 전문가 인증 (CISSP)"},
355+
{"year":2028,"title":"솔루션 아키텍트 승진"},
356+
{"year":2031,"title":"기술 이사 (CTO)"}
357+
]
358+
""")
359+
.img("https://picsum.photos/seed/decision-scenario/400/300")
360+
.build();
361+
scenarioRepository.save(completedScenario);
362+
363+
// 완성 시나리오 지표 생성 (더 높은 점수)
364+
createSceneTypes(completedScenario, 90, 85, 80, 88, 87);
365+
366+
// 3. 처리 중 시나리오 생성 (두 번째 DecisionLine 기반, 폴링 테스트용)
367+
Scenario processingScenario = Scenario.builder()
368+
.user(user1)
369+
.baseLine(baseLine)
370+
.decisionLine(decisionLine2) // 두 번째 DecisionLine 사용
371+
.status(ScenarioStatus.PROCESSING)
372+
.build();
373+
scenarioRepository.save(processingScenario);
374+
}
375+
376+
/**
377+
* 시나리오에 대한 5개 지표(SceneType) 데이터를 생성합니다.
378+
* @param scenario 대상 시나리오
379+
* @param eco 경제 점수
380+
* @param happy 행복 점수
381+
* @param rel 관계 점수
382+
* @param career 직업 점수
383+
* @param health 건강 점수
384+
*/
385+
private void createSceneTypes(Scenario scenario, int eco, int happy, int rel, int career, int health) {
386+
List<SceneType> sceneTypes = List.of(
387+
SceneType.builder()
388+
.scenario(scenario)
389+
.type(Type.경제)
390+
.point(eco)
391+
.analysis(eco >= 85
392+
? "클라우드 전문가로 높은 연봉과 컨설팅 수입을 통해 경제적 자유를 확보했습니다."
393+
: "안정적인 중견기업 재직으로 평균 이상의 경제력을 유지하고 있습니다.")
394+
.build(),
395+
SceneType.builder()
396+
.scenario(scenario)
397+
.type(Type.행복)
398+
.point(happy)
399+
.analysis(happy >= 80
400+
? "전문성 인정과 도전적인 업무를 통해 높은 직무 만족도를 느낍니다."
401+
: "업무 만족도가 높고 워라밸이 좋은 환경에서 일하고 있습니다.")
402+
.build(),
403+
SceneType.builder()
404+
.scenario(scenario)
405+
.type(Type.관계)
406+
.point(rel)
407+
.analysis(rel >= 80
408+
? "리더십 역할을 통해 업계 네트워크를 넓히고 멘토 관계를 형성했습니다."
409+
: "팀원들과 원만한 관계를 유지하며 개인 시간도 충분히 확보하고 있습니다.")
410+
.build(),
411+
SceneType.builder()
412+
.scenario(scenario)
413+
.type(Type.직업)
414+
.point(career)
415+
.analysis(career >= 85
416+
? "클라우드 및 보안 분야 최고 전문가로 인정받고 있습니다."
417+
: "백엔드 개발 전문성은 확보했으나 리더십 경험이 다소 부족합니다.")
418+
.build(),
419+
SceneType.builder()
420+
.scenario(scenario)
421+
.type(Type.건강)
422+
.point(health)
423+
.analysis(health >= 85
424+
? "체계적인 건강 관리와 규칙적인 운동 루틴을 유지하고 있습니다."
425+
: "규칙적인 생활과 적당한 운동으로 건강을 유지하고 있습니다.")
426+
.build()
427+
);
428+
429+
sceneTypeRepository.saveAll(sceneTypes);
241430
}
242431
}

back/src/test/java/com/back/domain/scenario/controller/ScenarioControllerTest.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
1616
import org.springframework.boot.test.context.SpringBootTest;
1717
import org.springframework.boot.test.mock.mockito.MockBean;
18+
import com.back.global.common.PageResponse;
1819
import org.springframework.data.domain.Page;
1920
import org.springframework.data.domain.PageImpl;
2021
import org.springframework.data.domain.PageRequest;
@@ -263,7 +264,8 @@ class GetBaselines {
263264
)
264265
);
265266

266-
Page<BaselineListResponse> mockPageResponse = new PageImpl<>(content, PageRequest.of(0, 10), content.size());
267+
Page<BaselineListResponse> page = new PageImpl<>(content, PageRequest.of(0, 10), content.size());
268+
PageResponse<BaselineListResponse> mockPageResponse = PageResponse.of(page);
267269

268270
given(scenarioService.getBaselines(eq(1L), any()))
269271
.willReturn(mockPageResponse);
@@ -274,11 +276,13 @@ class GetBaselines {
274276
.param("size", "10"))
275277
.andDo(print())
276278
.andExpect(status().isOk())
277-
.andExpect(jsonPath("$.content").isArray())
278-
.andExpect(jsonPath("$.content[0].baselineId").value(200))
279-
.andExpect(jsonPath("$.content[0].title").value("대학 졸업 이후"))
279+
.andExpect(jsonPath("$.items").isArray())
280+
.andExpect(jsonPath("$.page").value(1)) // 1-based pagination
281+
.andExpect(jsonPath("$.size").value(10))
280282
.andExpect(jsonPath("$.totalElements").value(2))
281-
.andExpect(jsonPath("$.totalPages").value(1));
283+
.andExpect(jsonPath("$.totalPages").value(1))
284+
.andExpect(jsonPath("$.items[0].baselineId").value(200))
285+
.andExpect(jsonPath("$.items[0].title").value("대학 졸업 이후"));
282286
}
283287
}
284288

back/src/test/java/com/back/domain/user/controller/UserInfoControllerTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ void t1() throws Exception {
113113

114114
Scenario scenario = Scenario.builder()
115115
.user(testUser)
116+
.baseLine(testBaseLine) // BaseLine 추가
116117
.decisionLine(testDecisionLine)
117118
.status(ScenarioStatus.COMPLETED)
118119
.job("Software Engineer")

0 commit comments

Comments
 (0)