Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 7 additions & 2 deletions src/main/java/com/arom/with_travel/domain/member/Member.java
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,9 @@ public static Member create(String memberName, String email, String oauthId) {
@OneToMany(mappedBy = "member")
private List<ChatPart> chatParts = new ArrayList<>();

@OneToMany(mappedBy = "member")
private List<Survey> surveys = new ArrayList<>();
// 회원 생성 직후 아직 설문이 없을 수 있어서 optional = true로 처리
@OneToOne(mappedBy = "member", optional = true)
private Survey survey;

@OneToOne(mappedBy = "member")
private Image image;
Expand Down Expand Up @@ -174,4 +175,8 @@ public void markAdditionalDataChecked() {
public void uploadImage(Image image){
this.image = image;
}

public void setSurvey(Survey survey) {
this.survey = survey;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ public class SignupWithSurveyRequestDto {
@NotNull
private MemberSignupRequestDto extraInfo; // 닉네임·성별·생년월일

@NotEmpty
private List<SurveyRequestDto> surveys;
@NotNull
private SurveyRequestDto survey;
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import com.arom.with_travel.domain.member.dto.request.SocialMemberVerificationRequest;
import com.arom.with_travel.domain.member.repository.MemberRepository;
import com.arom.with_travel.domain.survey.Survey;
import com.arom.with_travel.domain.survey.dto.request.SurveyRequestDto;
import com.arom.with_travel.domain.survey.repository.SurveyRepository;
import com.arom.with_travel.global.exception.BaseException;
import com.arom.with_travel.global.exception.error.ErrorCode;
Expand Down Expand Up @@ -54,10 +55,26 @@ public MemberSignupResponseDto registerWithSurvey(String email,
MemberSignupRequestDto extra = req.getExtraInfo();
member.updateExtraInfo(extra.getNickname(), extra.getBirthdate(), extra.getGender(), extra.getIntroduction());

req.getSurveys().forEach(sdto -> {
Survey survey = Survey.create(member, sdto.getAnswers());
surveyRepository.save(survey);
});
// req.getSurveys().forEach(sdto -> {
// Survey survey = Survey.create(member, sdto.getAnswers());
// surveyRepository.save(survey);
// });

SurveyRequestDto s = req.getSurvey(); // 단일 설문
Survey survey = surveyRepository.findByMemberIdAndIsDeletedFalse(member.getId())
.map(existing -> {
existing.update(
s.getEnergyLevel()
// 이후 섹션 늘리면 여기에 추가
);
return existing;
})
.orElseGet(() -> Survey.create(
member,
s.getEnergyLevel()
// 이후 섹션 늘리면 여기에 추가
));
surveyRepository.save(survey);

member.markAdditionalDataChecked();

Expand Down
49 changes: 26 additions & 23 deletions src/main/java/com/arom/with_travel/domain/survey/Survey.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
package com.arom.with_travel.domain.survey;

import com.arom.with_travel.domain.member.Member;
import com.arom.with_travel.domain.survey.enums.EnergyLevel;
import com.arom.with_travel.global.entity.BaseEntity;
import com.arom.with_travel.global.exception.BaseException;
import com.arom.with_travel.global.exception.error.ErrorCode;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull;
import lombok.AccessLevel;
Expand All @@ -12,9 +11,6 @@
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.SQLRestriction;

import java.util.ArrayList;
import java.util.List;

@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
Expand All @@ -26,28 +22,35 @@ public class Survey extends BaseEntity {
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

// Survey 엔티티가 answers 문자열 리스트를 갖고 있고, 이 값들을 별도의 테이블에 저장하도록 리팩토링
@ElementCollection(fetch = FetchType.EAGER)
@CollectionTable(name = "survey_answers",
joinColumns = @JoinColumn(name = "survey_id"))
@Column(name = "answer")
private List<String> answers = new ArrayList<>();

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
@OneToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "member_id", nullable = false)
private Member member;
Copy link

Copilot AI Aug 18, 2025

Choose a reason for hiding this comment

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

The @OnetoOne relationship with optional = false and nullable = false creates a constraint that every Survey must have a Member, but the Member entity shows optional = true for the reverse relationship. This asymmetry could cause issues during entity creation or when a member exists without a survey.

Copilot uses AI. Check for mistakes.

public static Survey create(Member member,List<String> answers) {
validateAnswers(answers);
@NotNull
@Enumerated(EnumType.STRING)
@Column(name = "energy_level", nullable = false, length = 50)
private EnergyLevel energyLevel;

private Survey(Member member, EnergyLevel energyLevel) {
this.energyLevel = energyLevel;
linkMember(member);
}

public static Survey create(Member member, EnergyLevel energyLevel) {
return new Survey(member, energyLevel);
}

Survey survey = new Survey();
survey.member = member;
survey.answers = answers;
return survey;
private void linkMember(Member newMember) {
if (this.member != null && this.member.getSurvey() == this) {
this.member.setSurvey(null);
}
this.member = newMember;
if (newMember.getSurvey() != this) {
newMember.setSurvey(this);
}
}

private static void validateAnswers(List<String> answers){
if(answers == null || answers.isEmpty())
throw BaseException.from(ErrorCode.INVALID_SURVEY_ANSWER);
public void update(EnergyLevel energyLevel) {
this.energyLevel = energyLevel;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.arom.with_travel.global.security.domain.AuthenticatedMember;
import com.arom.with_travel.global.security.domain.PrincipalDetails;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
Expand All @@ -25,22 +26,18 @@ public class SurveyController {

@PostNewSurvey
@PostMapping("/surveys")
public void createSurvey(@AuthenticationPrincipal PrincipalDetails principal,
@RequestBody SurveyRequestDto dto) {
AuthenticatedMember member = principal.getAuthenticatedMember();
surveyService.createSurvey(member.getEmail(), dto);
}
public void saveSurvey(@AuthenticationPrincipal PrincipalDetails principal,
@Valid @RequestBody SurveyRequestDto dto) {

@GetSingleSurvey
@GetMapping("/survey/{surveyId}")
public SurveyResponseDto getSurvey(@PathVariable Long surveyId) {
return surveyService.getSurvey(surveyId);
AuthenticatedMember member = principal.getAuthenticatedMember();
surveyService.saveSurvey(member.getEmail(), dto);
}

@GetMySurveys
@GetMapping("/surveys/my")
public List<SurveyResponseDto> getMySurveys(@AuthenticationPrincipal PrincipalDetails principal) {
public SurveyResponseDto getSurvey(@AuthenticationPrincipal PrincipalDetails principal) {

AuthenticatedMember member = principal.getAuthenticatedMember();
return surveyService.getSurveysByEmail(member.getEmail());
return surveyService.getSurvey(member.getEmail());
}
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
package com.arom.with_travel.domain.survey.dto.request;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import com.arom.with_travel.domain.survey.enums.EnergyLevel;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.List;

@Getter
@NoArgsConstructor
@AllArgsConstructor // <— 모든 필드를 초기화하는 생성자 추가
@AllArgsConstructor
@Builder
public class SurveyRequestDto {

@NotEmpty(message = "설문 답변은 최소 1개 이상이어야 합니다.")
private List<String> answers;
@NotNull(message = "energyLevel은 필수입니다.")
private EnergyLevel energyLevel;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

한가지 질문에 대해 여러 답변을 받는 경우엔 어떻게 되나요?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

제가 착각을 했는데 현재 DTO는 단일 선택만 요구하고 있어서 프론트에서 배열로 요청을 보내면(여러 답변을 받으면) 400 에러(Bad Request)가 납니다.
피그마에 중복 선택 불가로 나와있어서 이런 로직으로 구현했는데 다중 선택 로직도 추가해야할까요?

// @NotNull TravelGoal travelGoal,
// @NotNull TravelPace travelPace,
// @NotNull CommStyle commStyle,
// @NotNull Personality personality,
// @NotNull CompanionStyle companionStyle,
// @NotNull SpendPattern spendPattern
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.arom.with_travel.domain.survey.dto.response;

import com.arom.with_travel.domain.survey.Survey;
import com.arom.with_travel.domain.survey.enums.EnergyLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
Expand All @@ -14,13 +15,24 @@
@Builder
public class SurveyResponseDto {
private Long surveyId;
private String question;
private List<String> answers;
private Long memberId;

public static SurveyResponseDto from(Survey survey) {
private EnergyLevel energyLevel;
// 나중에 추가되면 주석 해제
// private TravelGoal travelGoal;
// private TravelPace travelPace;
// private CommStyle commStyle;
// private Personality personality;
// private CompanionStyle companionStyle;
// private SpendPattern spendPattern;

public static SurveyResponseDto from(Survey s) {
return SurveyResponseDto.builder()
.surveyId(survey.getId())
.answers(survey.getAnswers())
.surveyId(s.getId())
.memberId(s.getMember().getId())
.energyLevel(s.getEnergyLevel())
// .travelGoal(s.getTravelGoal())
// ...
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.arom.with_travel.domain.survey.enums;

import com.arom.with_travel.global.exception.BaseException;
import com.arom.with_travel.global.exception.error.ErrorCode;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import lombok.AllArgsConstructor;

import java.util.Arrays;

@AllArgsConstructor
public enum EnergyLevel implements SurveyEnum {

MORNING_PERSON("MORNING_PERSON", "#아침형인간"),
NIGHT_OWL("NIGHT_OWL", "#밤올빼미"),
ENERGIZER("ENERGIZER", "#에너자이저"),
HEALING_MODE("HEALING_MODE", "#힐링모드");

private final String code;
private final String label;

@Override public String getCode() { return code; }
@Override public String getLabel() { return label; }

// 응답으로는 code를 내보내기
@JsonValue
public String json() { return code; }

// 요청으로는 name/code 둘 다 허용
@JsonCreator
public static EnergyLevel from(String value) {
return Arrays.stream(values())
.filter(v -> v.name().equalsIgnoreCase(value) || v.code.equalsIgnoreCase(value))
.findFirst()
.orElseThrow(() -> BaseException.from(ErrorCode.INVALID_SURVEY_ENERGYLEVEL));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.arom.with_travel.domain.survey.enums;

public interface SurveyEnum {

String getCode();
String getLabel();
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
import com.arom.with_travel.domain.survey.Survey;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;
import java.util.Optional;

public interface SurveyRepository extends JpaRepository<Survey, Long> {

List<Survey> findByMember(Member member);
Optional<Survey> findByMemberIdAndIsDeletedFalse(Long memberId);
boolean existsByMemberIdAndIsDeletedFalse(Long memberId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,10 @@
import com.arom.with_travel.domain.survey.repository.SurveyRepository;
import com.arom.with_travel.global.exception.BaseException;
import com.arom.with_travel.global.exception.error.ErrorCode;
import com.arom.with_travel.global.security.domain.AuthenticatedMember;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
@RequiredArgsConstructor
public class SurveyService {
Expand All @@ -24,28 +21,45 @@ public class SurveyService {
private final MemberRepository memberRepository;

@Transactional
public void createSurvey(String email, SurveyRequestDto dto) {
public void saveSurvey(String email, SurveyRequestDto dto) {
Member member = memberRepository.findByEmail(email)
.orElseThrow(() -> BaseException.from(ErrorCode.MEMBER_NOT_FOUND));

Survey survey = Survey.create(member, dto.getAnswers());
surveyRepository.save(survey);
}
Survey survey = surveyRepository.findByMemberIdAndIsDeletedFalse(member.getId())
.map(existing -> {
existing.update(
dto.getEnergyLevel()
// dto.getTravelGoal(),
// dto.getTravelPace(),
// dto.getCommStyle(),
// dto.getPersonality(),
// dto.getCompanionStyle(),
// dto.getSpendPattern()
);
return existing;
})
.orElseGet(() -> Survey.create(
member,
dto.getEnergyLevel()
// dto.getTravelGoal(),
// dto.getTravelPace(),
// dto.getCommStyle(),
// dto.getPersonality(),
// dto.getCompanionStyle(),
// dto.getSpendPattern()
Copy link

Copilot AI Aug 18, 2025

Choose a reason for hiding this comment

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

The extensive commented-out code for additional survey fields makes the code difficult to read and maintain. Consider removing these comments and adding the fields when they are actually implemented.

Suggested change
// dto.getSpendPattern()
);
return existing;
})
.orElseGet(() -> Survey.create(
member,
dto.getEnergyLevel()

Copilot uses AI. Check for mistakes.
));

@Transactional(readOnly = true)
public SurveyResponseDto getSurvey(Long surveyId) {
Survey survey = surveyRepository.findById(surveyId)
.orElseThrow(() -> BaseException.from(ErrorCode.SURVEY_NOT_FOUND));
return SurveyResponseDto.from(survey);
surveyRepository.save(survey);
}

@Transactional(readOnly = true)
public List<SurveyResponseDto> getSurveysByEmail(String email) {
public SurveyResponseDto getSurvey(String email) {
Member member = memberRepository.findByEmail(email)
.orElseThrow(() -> BaseException.from(ErrorCode.MEMBER_NOT_FOUND));

return surveyRepository.findByMember(member).stream()
.map(SurveyResponseDto::from)
.toList();
Survey survey = surveyRepository.findByMemberIdAndIsDeletedFalse(member.getId())
.orElseThrow(() -> BaseException.from(ErrorCode.SURVEY_NOT_FOUND));

return SurveyResponseDto.from(survey);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ public enum ErrorCode {
INVALID_SURVEY_ANSWER("SVY-0001", "설문 답변이 비어있습니다.", ErrorDisplayType.POPUP),
OVER_ANSWER_LIMIT("SVY-0002", "답변 개수가 초과되었습니다..", ErrorDisplayType.POPUP),
INVALID_SURVEY_QUESTION("SVY-0003", "설문 질문이 비어있습니다.", ErrorDisplayType.POPUP),
INVALID_SURVEY_ENERGYLEVEL("SVY-0004", "에너지레벨 설문 답변이 비어있습니다.", ErrorDisplayType.POPUP),

// image
INVALID_IMG_TYPE("IMG-0000", "지원하지 않는 이미지 형식입니다.", ErrorDisplayType.POPUP),
Expand Down
Loading