-
Notifications
You must be signed in to change notification settings - Fork 2
feat: 여행 설문조사 기반 아바타 매칭 시스템 구현 #126
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 19 commits
ea9e53f
331648d
8b1d623
45f5cef
8e9beb5
33704ca
f8a2141
50b0293
af29c1a
eed431c
6253302
622dfd2
fe59a43
8c1e090
dca4fa6
ee4da12
019dc2b
55bc9fd
38d8301
c8d6a37
1370904
cb34f81
2596851
a98f797
e9e388f
31f81fc
d360165
cb15fc4
257ffe7
eca6f09
7327f5c
a40c6f1
21a2cf1
e3eddca
d39b6fe
edb026a
1f8e5d3
2816dcf
c3bf3fe
39810b8
700cc6c
dd5de71
9a7eb3a
283f866
b3523d6
bcc7ceb
97bf1cd
bbb491b
58a2baf
6f89f11
ad2e89e
5715e77
429537a
11709ea
553ef89
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,126 @@ | ||
| package com.dduru.gildongmu.survey.controller; | ||
|
|
||
| import com.dduru.gildongmu.common.annotation.CommonApiResponses; | ||
| import com.dduru.gildongmu.common.dto.ApiResult; | ||
| import com.dduru.gildongmu.common.exception.ErrorResponse; | ||
| import com.dduru.gildongmu.survey.dto.SurveyRequest; | ||
| import com.dduru.gildongmu.survey.dto.SurveyResponse; | ||
| import io.swagger.v3.oas.annotations.Operation; | ||
| import io.swagger.v3.oas.annotations.Parameter; | ||
| import io.swagger.v3.oas.annotations.media.Content; | ||
| import io.swagger.v3.oas.annotations.media.ExampleObject; | ||
| import io.swagger.v3.oas.annotations.media.Schema; | ||
| import io.swagger.v3.oas.annotations.responses.ApiResponse; | ||
| import io.swagger.v3.oas.annotations.responses.ApiResponses; | ||
| import io.swagger.v3.oas.annotations.security.SecurityRequirement; | ||
| import io.swagger.v3.oas.annotations.tags.Tag; | ||
| import jakarta.validation.Valid; | ||
| import org.springframework.http.ResponseEntity; | ||
|
|
||
| @Tag(name = "Survey", description = "설문조사 API") | ||
| @SecurityRequirement(name = "JWT") | ||
| public interface SurveyApiDocs { | ||
|
|
||
| @Operation(summary = "설문조사 제출", description = "11개 질문의 선택지를 제출하고 성향 점수 및 아바타를 매칭합니다.") | ||
| @CommonApiResponses | ||
| @ApiResponses({ | ||
| @ApiResponse( | ||
| responseCode = "201", | ||
| description = "설문 제출 성공", | ||
| content = @Content( | ||
| mediaType = "application/json", | ||
| schema = @Schema(implementation = SurveyResponse.class) | ||
| ) | ||
| ), | ||
| @ApiResponse( | ||
| responseCode = "400", | ||
| description = "잘못된 요청 (유효하지 않은 답변 코드, 필수 질문 미응답, Q7 범위 초과 등)", | ||
| content = @Content( | ||
| mediaType = "application/json", | ||
| schema = @Schema(implementation = ErrorResponse.class), | ||
| examples = { | ||
| @ExampleObject( | ||
| name = "InvalidAnswerCode", | ||
| value = """ | ||
| { | ||
| "status": 400, | ||
| "data": { | ||
| "errorCode": "INVALID_INPUT_VALUE", | ||
| "field": null, | ||
| "message": "Q1 이동수단에 대한 유효하지 않은 답변 코드입니다: 99" | ||
| } | ||
| } | ||
| """ | ||
| ), | ||
| @ExampleObject( | ||
| name = "ValidationError", | ||
| value = """ | ||
| { | ||
| "status": 400, | ||
| "data": { | ||
| "errorCode": "INVALID_INPUT_VALUE", | ||
| "field": "q1", | ||
| "message": "Q1 이동수단은 필수입니다." | ||
| } | ||
| } | ||
| """ | ||
| ), | ||
| @ExampleObject( | ||
| name = "InvalidQ7Size", | ||
| value = """ | ||
| { | ||
| "status": 400, | ||
| "data": { | ||
| "errorCode": "INVALID_INPUT_VALUE", | ||
| "field": "q7", | ||
| "message": "Q7 선호활동은 최대 3개까지 선택할 수 있습니다." | ||
| } | ||
| } | ||
| """ | ||
| ) | ||
| } | ||
| ) | ||
| ) | ||
| }) | ||
| ResponseEntity<ApiResult<SurveyResponse>> submitSurvey( | ||
| @Parameter(hidden = true) Long userId, | ||
| @Valid SurveyRequest request | ||
| ); | ||
|
|
||
| @Operation(summary = "내 설문 결과 조회", description = "현재 사용자의 설문 결과 및 매칭된 아바타를 조회합니다.") | ||
| @CommonApiResponses | ||
| @ApiResponses({ | ||
| @ApiResponse( | ||
| responseCode = "200", | ||
| description = "조회 성공", | ||
| content = @Content( | ||
| mediaType = "application/json", | ||
| schema = @Schema(implementation = SurveyResponse.class) | ||
| ) | ||
| ), | ||
| @ApiResponse( | ||
| responseCode = "404", | ||
| description = "설문 결과를 찾을 수 없음", | ||
| content = @Content( | ||
| mediaType = "application/json", | ||
| schema = @Schema(implementation = ErrorResponse.class), | ||
| examples = @ExampleObject( | ||
| name = "SurveyResultNotFound", | ||
| value = """ | ||
| { | ||
| "status": 404, | ||
| "data": { | ||
| "errorCode": "SURVEY_RESULT_NOT_FOUND", | ||
| "field": null, | ||
| "message": "설문 결과를 찾을 수 없습니다." | ||
| } | ||
| } | ||
| """ | ||
| ) | ||
| ) | ||
| ) | ||
| }) | ||
| ResponseEntity<ApiResult<SurveyResponse>> getMySurveyResult( | ||
| @Parameter(hidden = true) Long userId | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| package com.dduru.gildongmu.survey.controller; | ||
|
|
||
| import com.dduru.gildongmu.common.annotation.CurrentUser; | ||
| import com.dduru.gildongmu.common.dto.ApiResult; | ||
| import com.dduru.gildongmu.survey.dto.SurveyRequest; | ||
| import com.dduru.gildongmu.survey.dto.SurveyResponse; | ||
| import com.dduru.gildongmu.survey.service.SurveyService; | ||
| import jakarta.validation.Valid; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.http.HttpStatus; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.web.bind.annotation.*; | ||
|
|
||
| @RestController | ||
| @RequiredArgsConstructor | ||
| @RequestMapping("/api/v1/surveys") | ||
| public class SurveyController implements SurveyApiDocs { | ||
|
|
||
| private final SurveyService surveyService; | ||
|
|
||
| @Override | ||
| @PostMapping | ||
| public ResponseEntity<ApiResult<SurveyResponse>> submitSurvey( | ||
| @CurrentUser Long userId, | ||
| @RequestBody @Valid SurveyRequest request | ||
| ) { | ||
| SurveyResponse response = surveyService.submitSurvey(userId, request); | ||
| return ResponseEntity.status(HttpStatus.CREATED).body(ApiResult.ok(response)); | ||
| } | ||
|
|
||
| @Override | ||
| @GetMapping("/me") | ||
| public ResponseEntity<ApiResult<SurveyResponse>> getMySurveyResult( | ||
| @CurrentUser Long userId | ||
| ) { | ||
| SurveyResponse response = surveyService.getMySurveyResult(userId); | ||
| return ResponseEntity.status(HttpStatus.OK).body(ApiResult.ok(response)); | ||
| } | ||
| } |
This file was deleted.
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| package com.dduru.gildongmu.survey.converter; | ||
|
|
||
| import com.dduru.gildongmu.common.enums.CodedEnum; | ||
| import com.dduru.gildongmu.common.enums.EnumUtils; | ||
| import com.dduru.gildongmu.survey.domain.Survey; | ||
| import com.dduru.gildongmu.survey.domain.enums.*; | ||
| import com.dduru.gildongmu.survey.dto.SurveyRequest; | ||
| import com.dduru.gildongmu.survey.exception.InvalidSurveyAnswerCodeException; | ||
| import com.dduru.gildongmu.user.domain.User; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| import java.util.List; | ||
| import java.util.stream.Collectors; | ||
|
|
||
| @Component | ||
| public class SurveyConverter { | ||
|
|
||
| public Survey toEntity(User user, SurveyRequest request) { | ||
| ParsedSurveyData parsed = parseRequest(request); | ||
| return Survey.createSurvey(user, parsed.q1(), parsed.q2(), parsed.q3(), parsed.q4(), parsed.q5(), | ||
| parsed.q6(), parsed.q7(), parsed.q8(), parsed.q9(), parsed.q10(), parsed.q11()); | ||
| } | ||
|
|
||
| public ParsedSurveyData parseRequest(SurveyRequest request) { | ||
| Question1Transport q1 = toEnum(Question1Transport.class, request.q1(), "Q1 이동수단"); | ||
| Question2Waiting q2 = toEnum(Question2Waiting.class, request.q2(), "Q2 웨이팅"); | ||
| Question3Stay q3 = toEnum(Question3Stay.class, request.q3(), "Q3 숙소"); | ||
| Question4Wakeup q4 = toEnum(Question4Wakeup.class, request.q4(), "Q4 기상시간"); | ||
| Question5Expense q5 = toEnum(Question5Expense.class, request.q5(), "Q5 경비관리"); | ||
| Question6Spend q6 = toEnum(Question6Spend.class, request.q6(), "Q6 소비태도"); | ||
| List<Question7Interest> q7 = toEnumList(Question7Interest.class, request.q7(), "Q7 선호활동"); | ||
| Question8Planning q8 = toEnum(Question8Planning.class, request.q8(), "Q8 계획성"); | ||
| Question9Menu q9 = toEnum(Question9Menu.class, request.q9(), "Q9 낯선메뉴"); | ||
| Question10Companion q10 = toEnum(Question10Companion.class, request.q10(), "Q10 동행제안"); | ||
| Question11Photo q11 = toEnum(Question11Photo.class, request.q11(), "Q11 사진"); | ||
|
|
||
| return new ParsedSurveyData(q1, q2, q3, q4, q5, q6, q7, q8, q9, q10, q11); | ||
| } | ||
|
|
||
| private <E extends Enum<E> & CodedEnum> E toEnum( | ||
| Class<E> enumClass, Integer code, String questionName) { | ||
| return EnumUtils.fromCode(enumClass, code) | ||
| .orElseThrow(() -> InvalidSurveyAnswerCodeException.of(questionName, code)); | ||
|
Comment on lines
+40
to
+43
|
||
| } | ||
|
|
||
| private <E extends Enum<E> & CodedEnum> List<E> toEnumList( | ||
| Class<E> enumClass, List<Integer> codes, String questionName) { | ||
| return codes.stream() | ||
| .map(code -> toEnum(enumClass, code, questionName)) | ||
| .collect(Collectors.toList()); | ||
| } | ||
|
|
||
| public record ParsedSurveyData( | ||
| Question1Transport q1, | ||
| Question2Waiting q2, | ||
| Question3Stay q3, | ||
| Question4Wakeup q4, | ||
| Question5Expense q5, | ||
| Question6Spend q6, | ||
| List<Question7Interest> q7, | ||
| Question8Planning q8, | ||
| Question9Menu q9, | ||
| Question10Companion q10, | ||
| Question11Photo q11 | ||
| ) { | ||
| } | ||
| } | ||
This file was deleted.
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The SurveyConverter.parseRequest method handles enum code conversion for 11 questions. Given the comprehensive test coverage in other parts of the codebase, consider adding tests to verify proper handling of invalid codes and ensure InvalidSurveyAnswerCodeException is thrown with appropriate messages.