diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 9e8e007..9118162 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -5,7 +5,11 @@ on: branches: - main -permissions: write-all + +permissions: + contents: read + checks: write + pull-requests: write env: AWS_REGION: ${{ secrets.AWS_REGION }} @@ -74,6 +78,42 @@ jobs: echo "${{ secrets.APPLICATION_INFRA }}" > ./application-infra.yml shell: bash - # Gradle clean bootJar + # clean과 test를 한 번에 실행 + - name: Test with Gradle + run: ./gradlew clean test + + # Jacoco 통합 리포트 생성 후 업로드 + - name: Generate Jacoco Root Report + run: ./gradlew jacocoRootReport + if: always() + + # bootJar 실행해서 jar 파일 생성 여부 확인 - name: Build with Gradle - run: ./gradlew clean api-module:bootJar + run: ./gradlew api-module:bootJar + + # Test 결과 업로드 + - name: Publish Test Results + uses: EnricoMi/publish-unit-test-result-action@v2 + if: always() + with: + files: '**/build/test-results/test/TEST-*.xml' + + - name: JUnit Report Action + uses: mikepenz/action-junit-report@v4 + if: always() + with: + report_paths: '**/build/test-results/test/TEST-*.xml' + + - name: Report test Coverage to PR + id: jacoco + uses: madrapps/jacoco-report@v1.6.1 + if: always() + continue-on-error: true # 실패해도 다음 단계 진행 (TODO: 커버리지 기준 충족 못할 시 실패 처리로 변경 예정) + with: + title: 📝 Test Coverage Report + paths: | + ${{ github.workspace }}/build/reports/jacoco/jacocoRootReport/jacocoRootReport.xml + token: ${{ secrets.GITHUB_TOKEN }} + min-coverage-overall: 70 + min-coverage-changed-files: 70 + update-comment: true \ No newline at end of file diff --git a/api-module/build.gradle b/api-module/build.gradle index 02a77fd..8df59cd 100644 --- a/api-module/build.gradle +++ b/api-module/build.gradle @@ -3,6 +3,7 @@ jar { enabled = false } springBoot { mainClass = 'hongik.triple.apimodule.ApiModuleApplication' + buildInfo() } dependencies { diff --git a/api-module/src/main/java/hongik/triple/apimodule/application/survey/SurveyService.java b/api-module/src/main/java/hongik/triple/apimodule/application/survey/SurveyService.java index cb1c483..1ab168a 100644 --- a/api-module/src/main/java/hongik/triple/apimodule/application/survey/SurveyService.java +++ b/api-module/src/main/java/hongik/triple/apimodule/application/survey/SurveyService.java @@ -1,49 +1,492 @@ package hongik.triple.apimodule.application.survey; +import hongik.triple.commonmodule.dto.survey.SurveyOptionDto; +import hongik.triple.commonmodule.dto.survey.SurveyQuestionDto; +import hongik.triple.commonmodule.dto.survey.SurveyReq; import hongik.triple.commonmodule.dto.survey.SurveyRes; +import hongik.triple.commonmodule.enumerate.SkinType; +import hongik.triple.domainmodule.domain.member.Member; +import hongik.triple.domainmodule.domain.member.repository.MemberRepository; +import hongik.triple.domainmodule.domain.survey.Survey; import hongik.triple.domainmodule.domain.survey.repository.SurveyRepository; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; @Service @RequiredArgsConstructor +@Transactional(readOnly = true) public class SurveyService { private final SurveyRepository surveyRepository; + private final MemberRepository memberRepository; - public SurveyRes registerSurvey() { + @Transactional + public SurveyRes registerSurvey(SurveyReq request) { // Validation + Member member = memberRepository.findById(request.memberId()) + .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 회원입니다.")); + + validateSurveyAnswers(request.answers()); // Business Logic + SkinType skinType = calculateSkinType(request.answers()); + Map processedBody = processSurveyBody(request.answers()); + + Survey survey = Survey.builder() + .member(member) + .body(processedBody) + .skinType(skinType) + .build(); + + Survey savedSurvey = surveyRepository.save(survey); // Response - return new SurveyRes(); // Replace with actual response data + return SurveyRes.builder() + .surveyId(savedSurvey.getSurveyId()) + .memberId(savedSurvey.getMember().getMemberId()) + .memberName(savedSurvey.getMember().getName()) + .skinType(savedSurvey.getSkinType()) + .body((Map) savedSurvey.getBody()) + .createdAt(savedSurvey.getCreatedAt()) + .modifiedAt(savedSurvey.getModifiedAt()) + .totalScore(calculateTotalScore(processedBody)) + .recommendation(generateRecommendation(skinType)) + .build(); } public SurveyRes getSurveyQuestions() { - // Validation - - // Business Logic + List questions = buildSurveyQuestions(); - // Response - return new SurveyRes(); // Replace with actual response data + return SurveyRes.builder() + .questions(questions) + .build(); } - public SurveyRes getSurveyList() { + public Page getSurveyList(Long memberId, Pageable pageable) { // Validation + if (memberId != null) { + memberRepository.findById(memberId) + .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 회원입니다.")); + } // Business Logic + Page surveys = (memberId != null) + ? surveyRepository.findByMember_MemberIdOrderByCreatedAtDesc(memberId, pageable) + : surveyRepository.findAllByOrderByCreatedAtDesc(pageable); // Response - return new SurveyRes(); // Replace with actual response data + return surveys.map(this::convertToSurveyRes); } public SurveyRes getSurveyDetail(Long surveyId) { // Validation + Survey survey = surveyRepository.findById(surveyId) + .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 설문조사입니다.")); - // Business Logic + // Business Logic & Response + return convertToSurveyRes(survey); + } - // Response - return new SurveyRes(); // Replace with actual response data + // Private helper methods + private void validateSurveyAnswers(Map answers) { + List questions = buildSurveyQuestions(); + + // 12개 필수 문항 확인 + for (SurveyQuestionDto question : questions) { + if (question.required() && !answers.containsKey(question.questionId())) { + throw new IllegalArgumentException("필수 질문에 대한 답변이 없습니다: " + question.questionText()); + } + } + + // 점수 범위 검증 (1-5) + for (Map.Entry entry : answers.entrySet()) { + try { + int score = Integer.parseInt(entry.getValue().toString()); + if (score < 1 || score > 5) { + throw new IllegalArgumentException("점수는 1-5 범위여야 합니다: " + entry.getKey()); + } + } catch (NumberFormatException e) { + throw new IllegalArgumentException("유효하지 않은 점수 형식입니다: " + entry.getKey()); + } + } + } + + private SkinType calculateSkinType(Map answers) { + int totalScore = 0; + int answerCount = 0; + + // 각 질문별 점수 계산 + for (Map.Entry entry : answers.entrySet()) { + String questionId = entry.getKey(); + Object answer = entry.getValue(); + + int score = calculateQuestionScore(questionId, answer); + if (score > 0) { + totalScore += score; + answerCount++; + } + } + + // 평균 점수 계산 + double averageScore = answerCount > 0 ? (double) totalScore / answerCount : 0; + + // 특정 문항들의 가중치를 고려한 세부 판단 + int comedoneScore = getComedoneRelatedScore(answers); // Q001, Q002, Q005, Q006 + int inflammationScore = getInflammationRelatedScore(answers); // Q003, Q004, Q007, Q008 + int pustuleScore = getPustuleRelatedScore(answers); // Q009, Q010 + int folliculitisScore = getFolliculitisRelatedScore(answers); // Q011, Q012 + + // 점수 기반 피부 타입 결정 (심각도 순으로 우선순위) + if (averageScore <= 2.0) { + return SkinType.NORMAL; + } else if (folliculitisScore >= 8) { // 모낭염 문항 평균 4점 이상 + return SkinType.FOLLICULITIS; + } else if (pustuleScore >= 8) { // 화농성 문항 평균 4점 이상 + return SkinType.PUSTULES; + } else if (inflammationScore >= 15) { // 염증성 문항 평균 3.75점 이상 + return SkinType.PAPULES; + } else if (comedoneScore >= 12) { // 좁쌀 문항 평균 3점 이상 + return SkinType.COMEDONES; + } else { + return SkinType.NORMAL; + } + } + + private int calculateQuestionScore(String questionId, Object answer) { + // 모든 질문이 1-5 척도로 통일 + try { + int score = Integer.parseInt(answer.toString()); + return Math.min(Math.max(score, 1), 5); + } catch (NumberFormatException e) { + return 1; // 기본값 + } + } + + private int getComedoneRelatedScore(Map answers) { + // 좁쌀여드름 관련 문항 (Q001, Q002, Q005, Q006) + String[] comedoneQuestions = {"Q001", "Q002", "Q005", "Q006"}; + int totalScore = 0; + + for (String questionId : comedoneQuestions) { + if (answers.containsKey(questionId)) { + totalScore += calculateQuestionScore(questionId, answers.get(questionId)); + } + } + return totalScore; + } + + private int getInflammationRelatedScore(Map answers) { + // 염증성 여드름 관련 문항 (Q003, Q004, Q007, Q008) + String[] inflammationQuestions = {"Q003", "Q004", "Q007", "Q008"}; + int totalScore = 0; + + for (String questionId : inflammationQuestions) { + if (answers.containsKey(questionId)) { + totalScore += calculateQuestionScore(questionId, answers.get(questionId)); + } + } + return totalScore; + } + + private int getPustuleRelatedScore(Map answers) { + // 화농성 여드름 관련 문항 (Q009, Q010) + String[] pustuleQuestions = {"Q009", "Q010"}; + int totalScore = 0; + + for (String questionId : pustuleQuestions) { + if (answers.containsKey(questionId)) { + totalScore += calculateQuestionScore(questionId, answers.get(questionId)); + } + } + return totalScore; + } + + private int getFolliculitisRelatedScore(Map answers) { + // 모낭염 관련 문항 (Q011, Q012) + String[] folliculitisQuestions = {"Q011", "Q012"}; + int totalScore = 0; + + for (String questionId : folliculitisQuestions) { + if (answers.containsKey(questionId)) { + totalScore += calculateQuestionScore(questionId, answers.get(questionId)); + } + } + return totalScore; + } + + private Map processSurveyBody(Map answers) { + Map processedBody = new HashMap<>(); + + for (Map.Entry entry : answers.entrySet()) { + String questionId = entry.getKey(); + Object answer = entry.getValue(); + int score = calculateQuestionScore(questionId, answer); + + Map questionResult = new HashMap<>(); + questionResult.put("answer", answer); + questionResult.put("score", score); + + processedBody.put(questionId, questionResult); + } + + // 카테고리별 점수도 함께 저장 + Map categoryScores = new HashMap<>(); + categoryScores.put("comedoneScore", getComedoneRelatedScore(answers)); + categoryScores.put("inflammationScore", getInflammationRelatedScore(answers)); + categoryScores.put("pustuleScore", getPustuleRelatedScore(answers)); + categoryScores.put("folliculitisScore", getFolliculitisRelatedScore(answers)); + + processedBody.put("categoryScores", categoryScores); + + return processedBody; + } + + private int calculateTotalScore(Map body) { + int totalScore = 0; + + for (Map.Entry entry : body.entrySet()) { + // categoryScores는 제외 + if (entry.getKey().equals("categoryScores")) { + continue; + } + + Object value = entry.getValue(); + if (value instanceof Map) { + Map questionResult = (Map) value; + Object scoreObj = questionResult.get("score"); + if (scoreObj instanceof Integer) { + totalScore += (Integer) scoreObj; + } + } + } + + return totalScore; + } + + private String generateRecommendation(SkinType skinType) { + switch (skinType) { + case NORMAL: + return "현재 피부 상태가 양호합니다. 기본적인 세안과 보습 관리를 지속하시고, 자외선 차단제를 꾸준히 사용하세요."; + case COMEDONES: + return "좁쌀여드름이 있습니다. BHA나 살리실산 성분의 각질 제거 제품을 사용하고, 논코메도제닉 제품으로 모공 관리에 집중하세요."; + case PUSTULES: + return "화농성 여드름이 있습니다. 벤조일 퍼옥사이드나 항생제 성분이 포함된 제품을 사용하고, 피부과 전문의 상담을 받아보세요."; + case PAPULES: + return "염증성 여드름이 있습니다. 자극적인 제품 사용을 피하고 니아신아마이드, 아젤라산 등의 진정 성분으로 관리하며, 피부과 치료를 권장합니다."; + case FOLLICULITIS: + return "모낭염이 의심됩니다. 면도 후 항균 토너를 사용하고, 청결한 관리와 함께 피부과 전문의 진료를 받으시기 바랍니다."; + default: + return "정확한 진단을 위해 피부과 전문의와 상담을 받아보세요."; + } + } + + private List buildSurveyQuestions() { + return Arrays.asList( + // 좁쌀여드름 관련 문항 (Q001-Q002, Q005-Q006) + new SurveyQuestionDto( + "Q001", + "얼굴에 작고 하얀 좁쌀 같은 것들이 얼마나 많이 있나요?", + "SCALE", + Arrays.asList( + new SurveyOptionDto("1", "전혀 없음", 1), + new SurveyOptionDto("2", "가끔 보임", 2), + new SurveyOptionDto("3", "보통", 3), + new SurveyOptionDto("4", "자주 보임", 4), + new SurveyOptionDto("5", "매우 많음", 5) + ), + true, + 1 + ), + + new SurveyQuestionDto( + "Q002", + "T존(이마, 코) 부위에 블랙헤드나 화이트헤드가 얼마나 많나요?", + "SCALE", + Arrays.asList( + new SurveyOptionDto("1", "전혀 없음", 1), + new SurveyOptionDto("2", "조금 있음", 2), + new SurveyOptionDto("3", "보통", 3), + new SurveyOptionDto("4", "많이 있음", 4), + new SurveyOptionDto("5", "매우 많음", 5) + ), + true, + 2 + ), + + // 염증성여드름 관련 문항 (Q003-Q004, Q007-Q008) + new SurveyQuestionDto( + "Q003", + "빨갛고 부어오른 여드름이 얼마나 자주 생기나요?", + "SCALE", + Arrays.asList( + new SurveyOptionDto("1", "거의 생기지 않음", 1), + new SurveyOptionDto("2", "가끔 생김", 2), + new SurveyOptionDto("3", "보통", 3), + new SurveyOptionDto("4", "자주 생김", 4), + new SurveyOptionDto("5", "항상 있음", 5) + ), + true, + 3 + ), + + new SurveyQuestionDto( + "Q004", + "여드름 부위에 통증이나 압통이 얼마나 심한가요?", + "SCALE", + Arrays.asList( + new SurveyOptionDto("1", "전혀 아프지 않음", 1), + new SurveyOptionDto("2", "살짝 아픔", 2), + new SurveyOptionDto("3", "보통", 3), + new SurveyOptionDto("4", "많이 아픔", 4), + new SurveyOptionDto("5", "매우 아픔", 5) + ), + true, + 4 + ), + + new SurveyQuestionDto( + "Q005", + "턱이나 입 주변에 작은 돌기들이 얼마나 많이 있나요?", + "SCALE", + Arrays.asList( + new SurveyOptionDto("1", "전혀 없음", 1), + new SurveyOptionDto("2", "조금 있음", 2), + new SurveyOptionDto("3", "보통", 3), + new SurveyOptionDto("4", "많이 있음", 4), + new SurveyOptionDto("5", "매우 많음", 5) + ), + true, + 5 + ), + + new SurveyQuestionDto( + "Q006", + "모공이 막힌 느낌이나 피부가 거칠어진 느낌이 얼마나 드나요?", + "SCALE", + Arrays.asList( + new SurveyOptionDto("1", "전혀 없음", 1), + new SurveyOptionDto("2", "가끔 있음", 2), + new SurveyOptionDto("3", "보통", 3), + new SurveyOptionDto("4", "자주 있음", 4), + new SurveyOptionDto("5", "항상 있음", 5) + ), + true, + 6 + ), + + new SurveyQuestionDto( + "Q007", + "여드름이 생긴 후 자국이나 흉터가 남는 정도는?", + "SCALE", + Arrays.asList( + new SurveyOptionDto("1", "전혀 남지 않음", 1), + new SurveyOptionDto("2", "가끔 남음", 2), + new SurveyOptionDto("3", "보통", 3), + new SurveyOptionDto("4", "자주 남음", 4), + new SurveyOptionDto("5", "항상 남음", 5) + ), + true, + 7 + ), + + new SurveyQuestionDto( + "Q008", + "여드름 주변 피부가 빨갛게 되는 정도는?", + "SCALE", + Arrays.asList( + new SurveyOptionDto("1", "전혀 빨갛지 않음", 1), + new SurveyOptionDto("2", "약간 빨감", 2), + new SurveyOptionDto("3", "보통", 3), + new SurveyOptionDto("4", "많이 빨감", 4), + new SurveyOptionDto("5", "매우 빨감", 5) + ), + true, + 8 + ), + + // 화농성여드름 관련 문항 (Q009-Q010) + new SurveyQuestionDto( + "Q009", + "고름이 찬 여드름(노란 고름이 보이는)이 얼마나 자주 생기나요?", + "SCALE", + Arrays.asList( + new SurveyOptionDto("1", "거의 생기지 않음", 1), + new SurveyOptionDto("2", "가끔 생김", 2), + new SurveyOptionDto("3", "보통", 3), + new SurveyOptionDto("4", "자주 생김", 4), + new SurveyOptionDto("5", "항상 있음", 5) + ), + true, + 9 + ), + + new SurveyQuestionDto( + "Q010", + "여드름에서 고름이나 분비물이 나오는 경우가 얼마나 많나요?", + "SCALE", + Arrays.asList( + new SurveyOptionDto("1", "전혀 없음", 1), + new SurveyOptionDto("2", "가끔 있음", 2), + new SurveyOptionDto("3", "보통", 3), + new SurveyOptionDto("4", "자주 있음", 4), + new SurveyOptionDto("5", "항상 있음", 5) + ), + true, + 10 + ), + + // 모낭염 관련 문항 (Q011-Q012) + new SurveyQuestionDto( + "Q011", + "털이 있는 부위(턱수염, 구레나룻 등)에 빨간 돌기나 염증이 얼마나 자주 생기나요?", + "SCALE", + Arrays.asList( + new SurveyOptionDto("1", "거의 생기지 않음", 1), + new SurveyOptionDto("2", "가끔 생김", 2), + new SurveyOptionDto("3", "보통", 3), + new SurveyOptionDto("4", "자주 생김", 4), + new SurveyOptionDto("5", "항상 있음", 5) + ), + true, + 11 + ), + + new SurveyQuestionDto( + "Q012", + "면도나 제모 후 빨간 반점이나 염증이 생기는 정도는?", + "SCALE", + Arrays.asList( + new SurveyOptionDto("1", "전혀 생기지 않음", 1), + new SurveyOptionDto("2", "가끔 생김", 2), + new SurveyOptionDto("3", "보통", 3), + new SurveyOptionDto("4", "자주 생김", 4), + new SurveyOptionDto("5", "항상 생김", 5) + ), + true, + 12 + ) + ); + } + + private SurveyRes convertToSurveyRes(Survey survey) { + return SurveyRes.builder() + .surveyId(survey.getSurveyId()) + .memberId(survey.getMember().getMemberId()) + .memberName(survey.getMember().getName()) + .skinType(survey.getSkinType()) + .body((Map) survey.getBody()) + .createdAt(survey.getCreatedAt()) + .modifiedAt(survey.getModifiedAt()) + .totalScore(calculateTotalScore((Map) survey.getBody())) + .recommendation(generateRecommendation(survey.getSkinType())) + .build(); } } diff --git a/api-module/src/main/java/hongik/triple/apimodule/presentation/survey/SurveyController.java b/api-module/src/main/java/hongik/triple/apimodule/presentation/survey/SurveyController.java index 2489d77..302d3ef 100644 --- a/api-module/src/main/java/hongik/triple/apimodule/presentation/survey/SurveyController.java +++ b/api-module/src/main/java/hongik/triple/apimodule/presentation/survey/SurveyController.java @@ -2,6 +2,7 @@ import hongik.triple.apimodule.application.survey.SurveyService; import hongik.triple.apimodule.global.common.ApplicationResponse; +import hongik.triple.commonmodule.dto.survey.SurveyReq; import hongik.triple.commonmodule.dto.survey.SurveyRes; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; @@ -10,10 +11,11 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PageableDefault; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/v1/survey") @@ -38,17 +40,17 @@ public ApplicationResponse getSurveyQuestions() { } @PostMapping("/response") - public ApplicationResponse registerSurvey() { - return ApplicationResponse.ok(surveyService.registerSurvey()); + public ApplicationResponse registerSurvey(@RequestBody SurveyReq request) { + return ApplicationResponse.ok(surveyService.registerSurvey(request)); } @GetMapping("/list") - public ApplicationResponse getSurveyList() { - return ApplicationResponse.ok(surveyService.getSurveyList()); + public ApplicationResponse getSurveyList(@AuthenticationPrincipal UserDetails userDetails, @PageableDefault Pageable pageable) { + return ApplicationResponse.ok(surveyService.getSurveyList(1L, pageable)); // TODO: userDetails -> memberId 추출 } @GetMapping("/detail/{surveyId}") - public ApplicationResponse getSurveyDetail(Long surveyId) { + public ApplicationResponse getSurveyDetail(@PathVariable(name = "surveyId") Long surveyId) { return ApplicationResponse.ok(surveyService.getSurveyDetail(surveyId)); } } diff --git a/build.gradle b/build.gradle index 97d10d2..f221e14 100644 --- a/build.gradle +++ b/build.gradle @@ -2,6 +2,8 @@ buildscript { ext { springBootVersion = '3.4.5' + springdocVersion = '2.3.0' + jjwtVersion = '0.12.6' } repositories { mavenCentral() @@ -38,8 +40,8 @@ subprojects { apply plugin: 'io.spring.dependency-management' apply plugin: 'jacoco' - group 'hongik.triple' - version '0.0.1-SNAPSHOT' + group = 'hongik.triple' + version = '0.0.1-SNAPSHOT' java { toolchain { @@ -80,10 +82,12 @@ subprojects { } jacocoTestReport { + dependsOn test + reports { - xml.required.set(false) - csv.required.set(false) - html.required.set(true) + xml.required = true + csv.required = false + html.required = true } } } diff --git a/common-module/src/main/java/hongik/triple/commonmodule/dto/survey/SurveyOptionDto.java b/common-module/src/main/java/hongik/triple/commonmodule/dto/survey/SurveyOptionDto.java new file mode 100644 index 0000000..8258bc3 --- /dev/null +++ b/common-module/src/main/java/hongik/triple/commonmodule/dto/survey/SurveyOptionDto.java @@ -0,0 +1,7 @@ +package hongik.triple.commonmodule.dto.survey; + +public record SurveyOptionDto( + String optionId, + String optionText, + int score +) {} \ No newline at end of file diff --git a/common-module/src/main/java/hongik/triple/commonmodule/dto/survey/SurveyQuestionDto.java b/common-module/src/main/java/hongik/triple/commonmodule/dto/survey/SurveyQuestionDto.java new file mode 100644 index 0000000..3e3a0d3 --- /dev/null +++ b/common-module/src/main/java/hongik/triple/commonmodule/dto/survey/SurveyQuestionDto.java @@ -0,0 +1,12 @@ +package hongik.triple.commonmodule.dto.survey; + +import java.util.List; + +public record SurveyQuestionDto( + String questionId, + String questionText, + String questionType, // MULTIPLE_CHOICE, SCALE, TEXT + List options, + boolean required, + int order +) {} \ No newline at end of file diff --git a/common-module/src/main/java/hongik/triple/commonmodule/dto/survey/SurveyReq.java b/common-module/src/main/java/hongik/triple/commonmodule/dto/survey/SurveyReq.java index bff6d86..1084cbf 100644 --- a/common-module/src/main/java/hongik/triple/commonmodule/dto/survey/SurveyReq.java +++ b/common-module/src/main/java/hongik/triple/commonmodule/dto/survey/SurveyReq.java @@ -1,5 +1,14 @@ package hongik.triple.commonmodule.dto.survey; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; + +import java.util.Map; + public record SurveyReq( -) { -} + @NotNull(message = "회원 ID는 필수입니다.") + Long memberId, + + @NotEmpty(message = "설문 응답은 필수입니다.") + Map answers +) {} diff --git a/common-module/src/main/java/hongik/triple/commonmodule/dto/survey/SurveyRes.java b/common-module/src/main/java/hongik/triple/commonmodule/dto/survey/SurveyRes.java index 0d2ec35..9fb724b 100644 --- a/common-module/src/main/java/hongik/triple/commonmodule/dto/survey/SurveyRes.java +++ b/common-module/src/main/java/hongik/triple/commonmodule/dto/survey/SurveyRes.java @@ -1,4 +1,22 @@ package hongik.triple.commonmodule.dto.survey; -public record SurveyRes() { -} +import hongik.triple.commonmodule.enumerate.SkinType; +import lombok.Builder; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +@Builder +public record SurveyRes( + Long surveyId, + Long memberId, + String memberName, + SkinType skinType, + Map body, + LocalDateTime createdAt, + LocalDateTime modifiedAt, + List questions, // 설문 질문 조회용 + Integer totalScore, // 리스트 조회용 + String recommendation +) {} diff --git a/domain-module/src/main/java/hongik/triple/domainmodule/domain/survey/repository/SurveyRepository.java b/domain-module/src/main/java/hongik/triple/domainmodule/domain/survey/repository/SurveyRepository.java index 3178856..ac7931f 100644 --- a/domain-module/src/main/java/hongik/triple/domainmodule/domain/survey/repository/SurveyRepository.java +++ b/domain-module/src/main/java/hongik/triple/domainmodule/domain/survey/repository/SurveyRepository.java @@ -1,7 +1,25 @@ package hongik.triple.domainmodule.domain.survey.repository; +import hongik.triple.commonmodule.enumerate.SkinType; import hongik.triple.domainmodule.domain.survey.Survey; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.time.LocalDateTime; +import java.util.List; public interface SurveyRepository extends JpaRepository { + + Page findByMember_MemberIdOrderByCreatedAtDesc(Long memberId, Pageable pageable); + + Page findAllByOrderByCreatedAtDesc(Pageable pageable); + + List findByMember_MemberIdAndSkinType(Long memberId, SkinType skinType); + + @Query("SELECT s FROM Survey s WHERE s.createdAt >= :startDate AND s.createdAt <= :endDate") + List findByCreatedAtBetween(@Param("startDate") LocalDateTime startDate, + @Param("endDate") LocalDateTime endDate); }