From 52b301075c7a0fcdef5fe1e875a9300c20777efd Mon Sep 17 00:00:00 2001 From: seungzzok <123801984+seungzzok@users.noreply.github.com> Date: Wed, 23 Apr 2025 22:56:42 +0900 Subject: [PATCH 1/3] =?UTF-8?q?refactor:=20local=20=EB=94=94=EB=A0=89?= =?UTF-8?q?=ED=86=A0=EB=A6=AC=20=EC=9C=84=EC=B9=98=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 변경 내용: entity -> dto 디렉토리로 위치 이동 - 이유: entity는 DB의 테이블과 매칭되는 JPA 객체를 다루는 디렉토리이기 때문에, 단순히 코드 안에서 사용되는 local 디렉토리는 dto 디렉토리가 더 적합할거라고 생각 --- .../admin/domain/controller/ChallengeController.java | 2 +- .../admin/domain/controller/ParticipationController.java | 2 +- .../domain/{entity => dto}/lcoal/ChallengeResponse.java | 2 +- .../domain/{entity => dto}/lcoal/ParticipationInfo.java | 2 +- .../lcoal/SpecialQuestionDto.java} | 5 +++-- .../writon/admin/domain/{entity => dto}/lcoal/Status.java | 2 +- .../admin/domain/{entity => dto}/lcoal/UserStatus.java | 2 +- .../dto/request/challenge/CreateChallengeRequestDto.java | 2 +- .../domain/dto/request/challenge/QuestionsRequestDto.java | 2 +- .../admin/domain/dto/response/auth/LoginResponseDto.java | 3 +-- .../response/challenge/CreateChallengeResponseDto.java | 2 +- .../dto/response/challenge/QuestionsResponseDto.java | 2 +- .../java/com/writon/admin/domain/service/AuthService.java | 5 +---- .../com/writon/admin/domain/service/ChallengeService.java | 8 ++++---- .../writon/admin/domain/service/ParticipationService.java | 3 +-- 15 files changed, 20 insertions(+), 24 deletions(-) rename src/main/java/com/writon/admin/domain/{entity => dto}/lcoal/ChallengeResponse.java (78%) rename src/main/java/com/writon/admin/domain/{entity => dto}/lcoal/ParticipationInfo.java (92%) rename src/main/java/com/writon/admin/domain/{entity/lcoal/SpecialQuestion.java => dto/lcoal/SpecialQuestionDto.java} (62%) rename src/main/java/com/writon/admin/domain/{entity => dto}/lcoal/Status.java (83%) rename src/main/java/com/writon/admin/domain/{entity => dto}/lcoal/UserStatus.java (80%) diff --git a/src/main/java/com/writon/admin/domain/controller/ChallengeController.java b/src/main/java/com/writon/admin/domain/controller/ChallengeController.java index 021e6d4..1d286ea 100644 --- a/src/main/java/com/writon/admin/domain/controller/ChallengeController.java +++ b/src/main/java/com/writon/admin/domain/controller/ChallengeController.java @@ -6,7 +6,7 @@ import com.writon.admin.domain.dto.response.challenge.ChallengeInfoResponseDto; import com.writon.admin.domain.dto.response.challenge.CreateChallengeResponseDto; import com.writon.admin.domain.dto.response.challenge.QuestionsResponseDto; -import com.writon.admin.domain.entity.lcoal.UserStatus; +import com.writon.admin.domain.dto.lcoal.UserStatus; import com.writon.admin.domain.service.ChallengeService; import com.writon.admin.global.response.SuccessDto; import java.util.List; diff --git a/src/main/java/com/writon/admin/domain/controller/ParticipationController.java b/src/main/java/com/writon/admin/domain/controller/ParticipationController.java index 2c86f9b..38788af 100644 --- a/src/main/java/com/writon/admin/domain/controller/ParticipationController.java +++ b/src/main/java/com/writon/admin/domain/controller/ParticipationController.java @@ -1,6 +1,6 @@ package com.writon.admin.domain.controller; -import com.writon.admin.domain.entity.lcoal.ParticipationInfo; +import com.writon.admin.domain.dto.lcoal.ParticipationInfo; import com.writon.admin.domain.service.EmailService; import com.writon.admin.domain.service.ParticipationService; import com.writon.admin.global.error.CustomException; diff --git a/src/main/java/com/writon/admin/domain/entity/lcoal/ChallengeResponse.java b/src/main/java/com/writon/admin/domain/dto/lcoal/ChallengeResponse.java similarity index 78% rename from src/main/java/com/writon/admin/domain/entity/lcoal/ChallengeResponse.java rename to src/main/java/com/writon/admin/domain/dto/lcoal/ChallengeResponse.java index a72d7f8..f2ef996 100644 --- a/src/main/java/com/writon/admin/domain/entity/lcoal/ChallengeResponse.java +++ b/src/main/java/com/writon/admin/domain/dto/lcoal/ChallengeResponse.java @@ -1,4 +1,4 @@ -package com.writon.admin.domain.entity.lcoal; +package com.writon.admin.domain.dto.lcoal; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/src/main/java/com/writon/admin/domain/entity/lcoal/ParticipationInfo.java b/src/main/java/com/writon/admin/domain/dto/lcoal/ParticipationInfo.java similarity index 92% rename from src/main/java/com/writon/admin/domain/entity/lcoal/ParticipationInfo.java rename to src/main/java/com/writon/admin/domain/dto/lcoal/ParticipationInfo.java index 0e2385c..d2432d4 100644 --- a/src/main/java/com/writon/admin/domain/entity/lcoal/ParticipationInfo.java +++ b/src/main/java/com/writon/admin/domain/dto/lcoal/ParticipationInfo.java @@ -1,4 +1,4 @@ -package com.writon.admin.domain.entity.lcoal; +package com.writon.admin.domain.dto.lcoal; import java.time.LocalDate; import lombok.AllArgsConstructor; diff --git a/src/main/java/com/writon/admin/domain/entity/lcoal/SpecialQuestion.java b/src/main/java/com/writon/admin/domain/dto/lcoal/SpecialQuestionDto.java similarity index 62% rename from src/main/java/com/writon/admin/domain/entity/lcoal/SpecialQuestion.java rename to src/main/java/com/writon/admin/domain/dto/lcoal/SpecialQuestionDto.java index 7cc8729..03c4d34 100644 --- a/src/main/java/com/writon/admin/domain/entity/lcoal/SpecialQuestion.java +++ b/src/main/java/com/writon/admin/domain/dto/lcoal/SpecialQuestionDto.java @@ -1,4 +1,4 @@ -package com.writon.admin.domain.entity.lcoal; +package com.writon.admin.domain.dto.lcoal; import java.util.List; import lombok.AllArgsConstructor; @@ -6,8 +6,9 @@ @Getter @AllArgsConstructor -public class SpecialQuestion { +public class SpecialQuestionDto { + private Long keywordId; private String keyword; private List questions; diff --git a/src/main/java/com/writon/admin/domain/entity/lcoal/Status.java b/src/main/java/com/writon/admin/domain/dto/lcoal/Status.java similarity index 83% rename from src/main/java/com/writon/admin/domain/entity/lcoal/Status.java rename to src/main/java/com/writon/admin/domain/dto/lcoal/Status.java index fda3cce..cc7f790 100644 --- a/src/main/java/com/writon/admin/domain/entity/lcoal/Status.java +++ b/src/main/java/com/writon/admin/domain/dto/lcoal/Status.java @@ -1,4 +1,4 @@ -package com.writon.admin.domain.entity.lcoal; +package com.writon.admin.domain.dto.lcoal; import java.time.LocalDate; import lombok.AllArgsConstructor; diff --git a/src/main/java/com/writon/admin/domain/entity/lcoal/UserStatus.java b/src/main/java/com/writon/admin/domain/dto/lcoal/UserStatus.java similarity index 80% rename from src/main/java/com/writon/admin/domain/entity/lcoal/UserStatus.java rename to src/main/java/com/writon/admin/domain/dto/lcoal/UserStatus.java index b8231fb..51bc83c 100644 --- a/src/main/java/com/writon/admin/domain/entity/lcoal/UserStatus.java +++ b/src/main/java/com/writon/admin/domain/dto/lcoal/UserStatus.java @@ -1,4 +1,4 @@ -package com.writon.admin.domain.entity.lcoal; +package com.writon.admin.domain.dto.lcoal; import java.util.List; import lombok.AllArgsConstructor; diff --git a/src/main/java/com/writon/admin/domain/dto/request/challenge/CreateChallengeRequestDto.java b/src/main/java/com/writon/admin/domain/dto/request/challenge/CreateChallengeRequestDto.java index 5034563..6418e67 100644 --- a/src/main/java/com/writon/admin/domain/dto/request/challenge/CreateChallengeRequestDto.java +++ b/src/main/java/com/writon/admin/domain/dto/request/challenge/CreateChallengeRequestDto.java @@ -1,6 +1,6 @@ package com.writon.admin.domain.dto.request.challenge; -import com.writon.admin.domain.entity.lcoal.SpecialQuestion; +import com.writon.admin.domain.dto.lcoal.SpecialQuestionDto; import java.time.LocalDate; import java.util.List; import lombok.AllArgsConstructor; diff --git a/src/main/java/com/writon/admin/domain/dto/request/challenge/QuestionsRequestDto.java b/src/main/java/com/writon/admin/domain/dto/request/challenge/QuestionsRequestDto.java index 603ec4e..367f403 100644 --- a/src/main/java/com/writon/admin/domain/dto/request/challenge/QuestionsRequestDto.java +++ b/src/main/java/com/writon/admin/domain/dto/request/challenge/QuestionsRequestDto.java @@ -1,6 +1,6 @@ package com.writon.admin.domain.dto.request.challenge; -import com.writon.admin.domain.entity.lcoal.SpecialQuestion; +import com.writon.admin.domain.dto.lcoal.SpecialQuestionDto; import java.util.List; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/src/main/java/com/writon/admin/domain/dto/response/auth/LoginResponseDto.java b/src/main/java/com/writon/admin/domain/dto/response/auth/LoginResponseDto.java index 576ee6c..7420b58 100644 --- a/src/main/java/com/writon/admin/domain/dto/response/auth/LoginResponseDto.java +++ b/src/main/java/com/writon/admin/domain/dto/response/auth/LoginResponseDto.java @@ -1,7 +1,6 @@ package com.writon.admin.domain.dto.response.auth; -import com.writon.admin.domain.entity.lcoal.ChallengeResponse; -import com.writon.admin.domain.entity.organization.AdminUser; +import com.writon.admin.domain.dto.lcoal.ChallengeResponse; import java.util.List; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/src/main/java/com/writon/admin/domain/dto/response/challenge/CreateChallengeResponseDto.java b/src/main/java/com/writon/admin/domain/dto/response/challenge/CreateChallengeResponseDto.java index faf5e1b..42502f8 100644 --- a/src/main/java/com/writon/admin/domain/dto/response/challenge/CreateChallengeResponseDto.java +++ b/src/main/java/com/writon/admin/domain/dto/response/challenge/CreateChallengeResponseDto.java @@ -1,6 +1,6 @@ package com.writon.admin.domain.dto.response.challenge; -import com.writon.admin.domain.entity.lcoal.ChallengeResponse; +import com.writon.admin.domain.dto.lcoal.ChallengeResponse; import java.util.List; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/src/main/java/com/writon/admin/domain/dto/response/challenge/QuestionsResponseDto.java b/src/main/java/com/writon/admin/domain/dto/response/challenge/QuestionsResponseDto.java index b6a6518..28f3d0c 100644 --- a/src/main/java/com/writon/admin/domain/dto/response/challenge/QuestionsResponseDto.java +++ b/src/main/java/com/writon/admin/domain/dto/response/challenge/QuestionsResponseDto.java @@ -1,6 +1,6 @@ package com.writon.admin.domain.dto.response.challenge; -import com.writon.admin.domain.entity.lcoal.SpecialQuestion; +import com.writon.admin.domain.dto.lcoal.SpecialQuestionDto; import java.util.List; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/src/main/java/com/writon/admin/domain/service/AuthService.java b/src/main/java/com/writon/admin/domain/service/AuthService.java index 1498659..28e2661 100644 --- a/src/main/java/com/writon/admin/domain/service/AuthService.java +++ b/src/main/java/com/writon/admin/domain/service/AuthService.java @@ -2,13 +2,11 @@ import com.writon.admin.domain.dto.request.auth.LoginRequestDto; import com.writon.admin.domain.dto.request.auth.SignUpRequestDto; -import com.writon.admin.domain.dto.request.auth.ReissueRequestDto; import com.writon.admin.domain.dto.response.auth.LoginResponseDto; -import com.writon.admin.domain.dto.response.auth.ReissueResponseDto; import com.writon.admin.domain.dto.response.auth.SignUpResponseDto; import com.writon.admin.domain.dto.wrapper.auth.LoginResponseWrapper; import com.writon.admin.domain.entity.challenge.Challenge; -import com.writon.admin.domain.entity.lcoal.ChallengeResponse; +import com.writon.admin.domain.dto.lcoal.ChallengeResponse; import com.writon.admin.domain.entity.organization.AdminUser; import com.writon.admin.domain.entity.organization.Organization; import com.writon.admin.domain.repository.challenge.ChallengeRepository; @@ -18,7 +16,6 @@ import com.writon.admin.global.config.auth.TokenProvider; import com.writon.admin.global.error.CustomException; import com.writon.admin.global.error.ErrorCode; -import jakarta.servlet.http.HttpServletRequest; import java.util.Collections; import java.util.List; import java.util.Optional; diff --git a/src/main/java/com/writon/admin/domain/service/ChallengeService.java b/src/main/java/com/writon/admin/domain/service/ChallengeService.java index e8fa8bf..b6b3452 100644 --- a/src/main/java/com/writon/admin/domain/service/ChallengeService.java +++ b/src/main/java/com/writon/admin/domain/service/ChallengeService.java @@ -5,11 +5,11 @@ import com.writon.admin.domain.dto.request.challenge.QuestionsRequestDto; import com.writon.admin.domain.dto.response.challenge.ChallengeInfoResponseDto; import com.writon.admin.domain.dto.response.challenge.QuestionsResponseDto; -import com.writon.admin.domain.entity.lcoal.SpecialQuestion; -import com.writon.admin.domain.entity.lcoal.ChallengeResponse; +import com.writon.admin.domain.dto.lcoal.SpecialQuestionDto; +import com.writon.admin.domain.dto.lcoal.ChallengeResponse; import com.writon.admin.domain.dto.response.challenge.CreateChallengeResponseDto; -import com.writon.admin.domain.entity.lcoal.Status; -import com.writon.admin.domain.entity.lcoal.UserStatus; +import com.writon.admin.domain.dto.lcoal.Status; +import com.writon.admin.domain.dto.lcoal.UserStatus; import com.writon.admin.domain.entity.activity.UserTemplate; import com.writon.admin.domain.entity.challenge.Challenge; import com.writon.admin.domain.entity.challenge.ChallengeDay; diff --git a/src/main/java/com/writon/admin/domain/service/ParticipationService.java b/src/main/java/com/writon/admin/domain/service/ParticipationService.java index 4c28d80..84e362e 100644 --- a/src/main/java/com/writon/admin/domain/service/ParticipationService.java +++ b/src/main/java/com/writon/admin/domain/service/ParticipationService.java @@ -1,7 +1,7 @@ package com.writon.admin.domain.service; import com.writon.admin.domain.entity.challenge.Challenge; -import com.writon.admin.domain.entity.lcoal.ParticipationInfo; +import com.writon.admin.domain.dto.lcoal.ParticipationInfo; import com.writon.admin.domain.entity.challenge.Email; import com.writon.admin.domain.entity.user.Affiliation; import com.writon.admin.domain.entity.user.User; @@ -22,7 +22,6 @@ import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import software.amazon.awssdk.services.s3.endpoints.internal.Value.Bool; @Service @RequiredArgsConstructor From 73291446d29fc51e328f61ca911553257b85919e Mon Sep 17 00:00:00 2001 From: seungzzok <123801984+seungzzok@users.noreply.github.com> Date: Wed, 23 Apr 2025 22:58:35 +0900 Subject: [PATCH 2/3] rename: SpecialQuestion -> SpecialQuestionDto MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 변경 이유: entity와 Dto를 구별하기 위해서 - entity는 기본 클래스명이고 Dto로 끝나는 클래스들은 전부 dto로 분류 --- .../domain/dto/request/challenge/CreateChallengeRequestDto.java | 2 +- .../admin/domain/dto/request/challenge/QuestionsRequestDto.java | 2 +- .../domain/dto/response/challenge/QuestionsResponseDto.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/writon/admin/domain/dto/request/challenge/CreateChallengeRequestDto.java b/src/main/java/com/writon/admin/domain/dto/request/challenge/CreateChallengeRequestDto.java index 6418e67..87f5702 100644 --- a/src/main/java/com/writon/admin/domain/dto/request/challenge/CreateChallengeRequestDto.java +++ b/src/main/java/com/writon/admin/domain/dto/request/challenge/CreateChallengeRequestDto.java @@ -15,7 +15,7 @@ public class CreateChallengeRequestDto { private LocalDate endDate; private List processDates; private List basicQuestions; - private List specialQuestions; + private List specialQuestions; private List emailList; } diff --git a/src/main/java/com/writon/admin/domain/dto/request/challenge/QuestionsRequestDto.java b/src/main/java/com/writon/admin/domain/dto/request/challenge/QuestionsRequestDto.java index 367f403..01ae795 100644 --- a/src/main/java/com/writon/admin/domain/dto/request/challenge/QuestionsRequestDto.java +++ b/src/main/java/com/writon/admin/domain/dto/request/challenge/QuestionsRequestDto.java @@ -10,6 +10,6 @@ public class QuestionsRequestDto { private List basicQuestions; - private List specialQuestions; + private List specialQuestions; } diff --git a/src/main/java/com/writon/admin/domain/dto/response/challenge/QuestionsResponseDto.java b/src/main/java/com/writon/admin/domain/dto/response/challenge/QuestionsResponseDto.java index 28f3d0c..2dde198 100644 --- a/src/main/java/com/writon/admin/domain/dto/response/challenge/QuestionsResponseDto.java +++ b/src/main/java/com/writon/admin/domain/dto/response/challenge/QuestionsResponseDto.java @@ -10,6 +10,6 @@ public class QuestionsResponseDto { private List basicQuestions; - private List specialQuestions; + private List specialQuestions; } From 1b35b47f16c8a1d3032ee4f909031600bdb52001 Mon Sep 17 00:00:00 2001 From: seungzzok <123801984+seungzzok@users.noreply.github.com> Date: Wed, 23 Apr 2025 23:08:21 +0900 Subject: [PATCH 3/3] =?UTF-8?q?fix:=20=EC=A7=88=EB=AC=B8=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=20=EC=97=90=EB=9F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 문제1: keyword text를 기준으로 처리하다 보니 중복 충돌 상황 발생 - 문제2: question을 text기반으로 DB 데이터와 일일이 비교하다 보니 불필요한 연산이 과도하게 발생 - 해결1-1: keywordId를 specialQuestionDto의 필드로 추가해서 모든 비교 관리를 id를 기준으로 진행 - 해결1-2: keywordId를 null로 받는 경우 새로 생성한 keyword임을 구별 - 해결2-1: request와 DB 데이터값의 질문 리스트들을 순서대로 한번만 비교한 후 생성, 수정을 한번에 진행 - 해결2-2: Map 자료구조와 groupingBy를 이용해서 keywordId를 기반의 QuestionList 조회를 시간복잡도 O(1)로 변경 - 추가 변경사항 - 질문 조회 메서드를 재사용해서 질문 수정 메서드의 중복코드 제거 - 일일이 DB에 접근하는 것이 아니라 deleteAll, saveAll 등을 사용해 일괄적으로 DB에 접근 --- .../domain/service/ChallengeService.java | 208 +++++++++--------- 1 file changed, 107 insertions(+), 101 deletions(-) diff --git a/src/main/java/com/writon/admin/domain/service/ChallengeService.java b/src/main/java/com/writon/admin/domain/service/ChallengeService.java index b6b3452..ff85084 100644 --- a/src/main/java/com/writon/admin/domain/service/ChallengeService.java +++ b/src/main/java/com/writon/admin/domain/service/ChallengeService.java @@ -30,8 +30,13 @@ import com.writon.admin.global.error.ErrorCode; import java.time.LocalDate; import java.util.ArrayList; +import java.util.Collections; import java.util.Comparator; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; import java.util.stream.Collectors; import java.util.LinkedHashMap; import lombok.RequiredArgsConstructor; @@ -76,10 +81,10 @@ public CreateChallengeResponseDto createChallenge(CreateChallengeRequestDto requ new Question(basicQuestion, "베이직 질문", challenge) ); } - for (SpecialQuestion specialQuestionResponse : requestDto.getSpecialQuestions()) { - Keyword keyword = keywordRepository.save(new Keyword(specialQuestionResponse.getKeyword())); + for (SpecialQuestionDto specialQuestionDto : requestDto.getSpecialQuestions()) { + Keyword keyword = keywordRepository.save(new Keyword(specialQuestionDto.getKeyword())); - for (String specialQuestion : specialQuestionResponse.getQuestions()) { + for (String specialQuestion : specialQuestionDto.getQuestions()) { questionRepository.save( new Question(specialQuestion, "스페셜 질문", challenge, keyword) ); @@ -144,28 +149,27 @@ public List getDashboard(Long challengeId) { // ========== Get Questions API ========== public QuestionsResponseDto getQuestions(Long challengeId) { - // 1. 질문 리스트 추출 List questionList = questionRepository.findByChallengeId(challengeId) .orElseThrow(() -> new CustomException((ErrorCode.ETC_ERROR))); - questionList.sort(Comparator.comparing(question -> - question.getKeyword() != null - ? question.getKeyword().getId() - : Long.MAX_VALUE)); // keyword 기준으로 정렬 List basicQuestions = questionList.stream() - .filter(question -> "베이직 질문".equals(question.getCategory())) + .filter(question -> question.getKeyword() == null) .map(Question::getQuestion) .toList(); - List specialQuestions = questionList.stream() + List specialQuestions = questionList.stream() .filter(question -> question.getKeyword() != null) .collect(Collectors.groupingBy( - question -> question.getKeyword().getKeyword(), + Question::getKeyword, LinkedHashMap::new, // 순서를 유지하기 위해 LinkedHashMap 사용 Collectors.mapping(Question::getQuestion, Collectors.toList()) )) .entrySet().stream() - .map(entry -> new SpecialQuestion(entry.getKey(), entry.getValue())) + .map(entry -> new SpecialQuestionDto( + entry.getKey().getId(), + entry.getKey().getKeyword(), + entry.getValue() + )) .toList(); return new QuestionsResponseDto(basicQuestions, specialQuestions); @@ -199,105 +203,107 @@ public QuestionsResponseDto putQuestions(Long challengeId, QuestionsRequestDto r List questionList = questionRepository.findByChallengeId(challengeId) .orElseThrow(() -> new CustomException((ErrorCode.ETC_ERROR))); - // 3. 기존 BasicQuestion 중 입력값에 없는 질문들을 삭제 - List basicQuestionsToDelete = questionList.stream() - .filter(question -> "베이직 질문".equals(question.getCategory()) && - !requestDto.getBasicQuestions().contains(question.getQuestion())) - .toList(); - - questionRepository.deleteAll(basicQuestionsToDelete); - - // 4. 새로 추가된 BasicQuestion 추가 - for (String basicQuestion : requestDto.getBasicQuestions()) { - boolean exists = questionList.stream() - .anyMatch(question -> "베이직 질문".equals(question.getCategory()) && - question.getQuestion().equals(basicQuestion)); - if (!exists) { - questionRepository.save( - new Question(basicQuestion, "베이직 질문", challenge) - ); + // 3. 베이직 질문 처리 + List requestBasicQuestions = requestDto.getBasicQuestions(); + List existingBasicQuestions = questionList.stream() + .filter(q -> q.getKeyword() == null).toList(); + + int basicMax = Math.max(requestBasicQuestions.size(), existingBasicQuestions.size()); + List toSaveBasic = new ArrayList<>(); + List toDeleteBasic = new ArrayList<>(); + + for (int i = 0; i < basicMax; i++) { + if (i < requestBasicQuestions.size() && i < existingBasicQuestions.size()) { + // 3-1. 수정 필요 여부 확인 + Question existing = existingBasicQuestions.get(i); + String newQuestion = requestBasicQuestions.get(i); + if (!existing.getQuestion().equals(newQuestion)) { + existing.setQuestion(newQuestion); + toSaveBasic.add(existing); + } + } else if (i < requestBasicQuestions.size()) { + // 3-2. 새로 추가해야 하는 경우 + toSaveBasic.add(new Question(requestBasicQuestions.get(i), "베이직 질문", challenge)); + } else { + // 3-3. 삭제해야 하는 경우 + toDeleteBasic.add(existingBasicQuestions.get(i)); } } - // 5. specialQuestion 처리 - for (SpecialQuestion specialQuestion : requestDto.getSpecialQuestions()) { - String keyword = specialQuestion.getKeyword(); - List questions = specialQuestion.getQuestions(); - - // 기존 질문 리스트에서 현재 specialQuestion의 키워드에 해당하는 질문들을 필터링 - List existingQuestions = questionList.stream() - .filter(question -> question.getKeyword() != null && - keyword.equals(question.getKeyword().getKeyword())) - .toList(); - - // 기존 질문 리스트 중에서 specialQuestion에 없는 질문들은 삭제 - List specialQuestionsToDelete = existingQuestions.stream() - .filter(question -> !questions.contains(question.getQuestion())) - .toList(); - questionRepository.deleteAll(specialQuestionsToDelete); - - // 새로 추가할 질문들 중 기존에 없는 질문들을 추가 - for (String questionText : questions) { - boolean exists = existingQuestions.stream() - .anyMatch(question -> question.getQuestion().equals(questionText)); - if (!exists) { - // 키워드가 없으면 추가 - Keyword keywordEntity = keywordRepository.findByKeyword(keyword) - .orElseGet(() -> { - Keyword newKeyword = new Keyword(keyword); - keywordRepository.save(newKeyword); - return newKeyword; - }); - - questionRepository.save( - new Question(questionText, "스페셜 질문", challenge, keywordEntity) - ); + // 3-4. 일괄적으로 DB에 반영 + questionRepository.deleteAll(toDeleteBasic); + questionRepository.saveAll(toSaveBasic); + + // 4. 스페셜 질문 처리 + List requestSpecialQuestions = requestDto.getSpecialQuestions(); + + Map> existingSpecialQuestionMap = questionList.stream() + .filter(q -> q.getKeyword() != null) + .collect(Collectors.groupingBy(q -> q.getKeyword().getId())); + + List toSaveSpecial = new ArrayList<>(); + List toDeleteSpecial = new ArrayList<>(); + + for (SpecialQuestionDto dto : requestSpecialQuestions) { + Long keywordId = dto.getKeywordId(); + String keywordName = dto.getKeyword(); + List newQuestions = dto.getQuestions(); + + // 4-1. keyword와 그에 해당하는 질문들 전부 생성 + if (keywordId == null) { + Keyword newKeyword = new Keyword(keywordName); + keywordRepository.save(newKeyword); + for (String q : newQuestions) { + toSaveSpecial.add(new Question(q, "스페셜 질문", challenge, newKeyword)); } - } - } - // 6. questionList에서 specialQuestion에 없는 키워드의 질문들을 삭제 - List specialQuestionsToDelete = questionList.stream() - .filter(question -> question.getKeyword() != null && - requestDto.getSpecialQuestions().stream() - .noneMatch(specialQuestion -> specialQuestion.getKeyword() - .equals(question.getKeyword().getKeyword()))) - .toList(); - questionRepository.deleteAll(specialQuestionsToDelete); - - // 7. questionList에서 삭제된 질문들의 키워드를 제거 - specialQuestionsToDelete.stream() - .map(Question::getKeyword) - .distinct() - .forEach(keyword -> { - if (questionRepository.countByKeyword(keyword) == 0) { - keywordRepository.delete(keyword); + } else { + List existingQuestions = existingSpecialQuestionMap.getOrDefault( + keywordId, + new ArrayList<>() + ); + + int specialMax = Math.max(existingQuestions.size(), newQuestions.size()); + + for (int i = 0; i < specialMax; i++) { + // 4-2. 수정 필요 여부 확인 + if (i < newQuestions.size() && i < existingQuestions.size()) { + Question existing = existingQuestions.get(i); + String newQuestion = newQuestions.get(i); + if (!existing.getQuestion().equals(newQuestion)) { + existing.setQuestion(newQuestion); + toSaveSpecial.add(existing); + } + } else if (i < newQuestions.size()) { + // 4-3. 새로 추가해야 하는 경우 + toSaveSpecial.add(new Question( + newQuestions.get(i), + "스페셜 질문", + challenge, + existingQuestions.get(0).getKeyword() + )); + } else { + // 4-4. 삭제해야 하는 경우 + toDeleteSpecial.add(existingQuestions.get(i)); } - }); + } - // 8. 수정된 질문 리스트 다시 조회 - List updatedQuestionList = questionRepository.findByChallengeId(challengeId) - .orElseThrow(() -> new CustomException(ErrorCode.ETC_ERROR)); + // 4-5. 처리된 키워드는 제거 (삭제 대상에서 제외됨) + existingSpecialQuestionMap.remove(keywordId); + } + } - // 9. 기본 질문과 스페셜 질문 분리 및 응답 생성 - List basicQuestions = updatedQuestionList.stream() - .filter(question -> "베이직 질문".equals(question.getCategory())) - .map(Question::getQuestion) - .toList(); + // 4-6. 남은 키워드는 요청에 없는 것으로 삭제 대상 + List toDeleteKeywords = new ArrayList<>(keywordRepository.findAllById( + existingSpecialQuestionMap.keySet())); - List specialQuestions = updatedQuestionList.stream() - .filter(question -> question.getKeyword() != null) - .collect(Collectors.groupingBy( - question -> question.getKeyword().getKeyword(), - LinkedHashMap::new, // 순서를 유지하기 위해 LinkedHashMap 사용 - Collectors.mapping(Question::getQuestion, Collectors.toList()) - )) - .entrySet().stream() - .map(entry -> new SpecialQuestion(entry.getKey(), entry.getValue())) - .toList(); + // 4-7. 일괄적으로 DB에 반영 + questionRepository.deleteAll(toDeleteSpecial); + questionRepository.saveAll(toSaveSpecial); + keywordRepository.deleteAll(toDeleteKeywords); - // 10. QuestionsResponseDto 생성 후 반환 - return new QuestionsResponseDto(basicQuestions, specialQuestions); + // 5. question 재조회 후 반환 + return getQuestions(challengeId); } // ========== Put Info API ==========