Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
ea9e53f
[#107] feat(profile): Profile 엔티티 추가
shinheekim Dec 20, 2025
331648d
[#107] feat(profile): ProfileRepository 추가
shinheekim Dec 20, 2025
8b1d623
[#107] refactor(user): User 엔티티에서 프로필 관련 필드 제거
shinheekim Dec 20, 2025
45f5cef
[#107] refactor(profile): user 패키지에서 profile 패키지로 클래스 이동 및 리팩토링
shinheekim Dec 20, 2025
8e9beb5
[#107] feat(auth): OauthAuthService에서 User 생성 시 Profile도 함께 생성
shinheekim Dec 20, 2025
33704ca
[#125] feat(survey): 이전에 사용하던 방식 제거
jaejoong0529 Dec 21, 2025
f8a2141
[#125] feat(survey): 아바타 타입 및 설문 선택지 Enum 정의
jaejoong0529 Dec 21, 2025
50b0293
[#125] feat(survey): 설문 응답 및 성향 점수 엔티티 생성
jaejoong0529 Dec 21, 2025
af29c1a
[#125] feat(survey): 설문 응답 기반 성향 점수 계산 서비스 구현
jaejoong0529 Dec 21, 2025
eed431c
[#125] feat(survey): 성향 점수 기반 아바타 매칭 서비스 구현
jaejoong0529 Dec 21, 2025
6253302
[#125] feat(survey): 설문조사 DTO 클래스 및 Converter 정의
jaejoong0529 Dec 21, 2025
622dfd2
[#125] feat(survey): 설문조사 서비스 비즈니스 로직 구현
jaejoong0529 Dec 21, 2025
fe59a43
[#125] feat(survey): 설문조사 예외 처리 및 에러 코드 추가
jaejoong0529 Dec 21, 2025
8c1e090
[#125] feat(survey): 설문조사 API 컨트롤러 및 Swagger 문서화
jaejoong0529 Dec 21, 2025
dca4fa6
[#125] feat(survey): 아바타 매칭 조건 간략화
jaejoong0529 Dec 22, 2025
ee4da12
[#125] feat(survey): static factory 메서드 방식으로 일관성
jaejoong0529 Dec 22, 2025
019dc2b
[#125] feat(survey): 헬퍼메서드로 중복성 최소화
jaejoong0529 Dec 22, 2025
55bc9fd
[#125] feat(survey): 람다함수 개선
jaejoong0529 Dec 22, 2025
38d8301
[#125] test(survey): 테스트 코드 추가
jaejoong0529 Dec 22, 2025
c8d6a37
[#125] feat(survey): 설문조사 생성 200 -> 201로 변경
jaejoong0529 Dec 29, 2025
1370904
[#125] feat(survey): @Getter 대신 @Override 사용으로 가독성 향상
jaejoong0529 Dec 29, 2025
cb34f81
[#125] feat(survey): survey 메서드명 변경
jaejoong0529 Dec 29, 2025
2596851
[#125] feat(survey): 아바타 태그 내용 추가
jaejoong0529 Dec 29, 2025
a98f797
[#125] feat(survey): 선호활동 최대3개 고르는걸로 수정
jaejoong0529 Dec 29, 2025
e9e388f
[#107] refactor(profile): 다른 도메인에서 Profile 사용하도록 변경
shinheekim Dec 20, 2025
31f81fc
[#107] test(profile): Profile 도입에 따른 테스트 파일 업데이트
shinheekim Dec 20, 2025
d360165
[#107] feat(profile): 전화번호 인증 토큰 검증 메서드 추가
shinheekim Dec 28, 2025
cb15fc4
[#107] feat(profile): Profile 엔티티에 프로필 초기 설정 메서드 추가
shinheekim Dec 28, 2025
257ffe7
[#107] feat(profile): 프로필 설정 요청 DTO 생성
shinheekim Dec 28, 2025
eca6f09
[#107] feat(profile): 프로필 초기 설정 서비스 로직 구현
shinheekim Dec 28, 2025
7327f5c
[#107] feat(profile): 프로필 초기 설정 API 엔드포인트 추가
shinheekim Dec 28, 2025
a40c6f1
[#107] feat(profile): 테스트 코드 서비스명 변경 반영
shinheekim Dec 28, 2025
21a2cf1
[#107] refactor(profile): UserInfo DTO null 체크 제거
shinheekim Dec 28, 2025
e3eddca
[#107] docs(profile): 프로필 초기 설정 API docs swagger 추가
shinheekim Dec 28, 2025
d39b6fe
[#107] chore(profile): 현재는 불필요한 메서드 삭제
shinheekim Dec 29, 2025
edb026a
[#107] chore(profile): 기획상 변경으로 띄어쓰기 없는 랜덤 닉네임으로 생성
shinheekim Dec 29, 2025
1f8e5d3
[#107] chore(profile): UserId가 아닌 User객체로 Profile 조회
shinheekim Dec 29, 2025
2816dcf
[#107] chore(profile): 프로필 최초 저장 request 유효성 추가
shinheekim Dec 29, 2025
c3bf3fe
[#107] feat(profile): 닉네임 Race condition을 방지하기 위해 비관적 잠금 적용
shinheekim Dec 29, 2025
39810b8
[#107] test(profile): 프로필 최초 생성 단위 테스트 작성
shinheekim Dec 29, 2025
700cc6c
[#107] docs(profile): 프로필 관련 API 응답 구체적으로 작성
shinheekim Dec 29, 2025
dd5de71
[#127] docs(profile): 에러 응답 커스터마이징 및 프로필 에러 응답 수정(구체화)
shinheekim Dec 29, 2025
9a7eb3a
[#107] chore(profile): 에러 코드 enum 위치 일부 수정
shinheekim Dec 30, 2025
283f866
[#107] chore(profile): 불필요한 주석 제거
shinheekim Dec 30, 2025
b3523d6
[#107] chore(profile): 닉네임 검증 시 동시성 락으로 제어
shinheekim Dec 30, 2025
bcc7ceb
[#107] chore(profile): 프로필 초기 세팅 request dto에서 검증 강화
shinheekim Dec 30, 2025
97bf1cd
[#107] test(profile): 제거된 코드에 대한 테스트 코드 제거
shinheekim Dec 30, 2025
bbb491b
Merge pull request #128 from GIL-DONG-MU/feat/#107-signup-final-api
jaejoong0529 Dec 30, 2025
58a2baf
[#127] docs(swagger): 기존 CommonApiResponses.java 삭제
shinheekim Dec 30, 2025
6f89f11
Merge branch 'dev' into feat/#127-swagger-error-custiomize
shinheekim Dec 30, 2025
ad2e89e
[#127] docs(swagger): 기존 CommonApiResponses.java 삭제
shinheekim Dec 30, 2025
5715e77
Merge pull request #129 from GIL-DONG-MU/feat/#127-swagger-error-cust…
jaejoong0529 Dec 30, 2025
429537a
Merge branch 'feat/#125-survey-avatar-matching' of github.com:GIL-DON…
shinheekim Dec 30, 2025
11709ea
[#125] chore(survey): merge 해결
shinheekim Dec 30, 2025
553ef89
[#125] test(survey): user, profile 분리로 변경된 테스트 코드 수정
shinheekim Dec 30, 2025
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
Expand Up @@ -61,7 +61,7 @@ public enum ErrorCode {
INVALID_FILE_EXTENSION(HttpStatus.BAD_REQUEST, "허용되지 않는 파일 확장자입니다."),

// 설문 (SURVEY)
UNKNOWN_SURVEY_ANSWER_CODE(HttpStatus.BAD_REQUEST, "알 수 없는 설문조사 답변 코드입니다."),
SURVEY_RESULT_NOT_FOUND(HttpStatus.NOT_FOUND, "설문 결과를 찾을 수 없습니다."),

// 휴대폰 인증 (VERIFICATION)
SMS_SEND_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "SMS 발송에 실패했습니다."),
Expand Down
126 changes: 126 additions & 0 deletions src/main/java/com/dduru/gildongmu/survey/controller/SurveyApiDocs.java
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);
}
Comment on lines +24 to +38
Copy link

Copilot AI Dec 22, 2025

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.

Copilot uses AI. Check for mistakes.

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
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Performance concern: The EnumUtils.codeMap() method is called every time toEnum is invoked, which recreates the Map for each question during conversion. Consider caching the code map per enum type to avoid repeated stream operations, especially since this method is called 11+ times per survey submission.

Copilot uses AI. Check for mistakes.
}

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.

Loading