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..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 @@ -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; @@ -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 603ec4e..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 @@ -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; @@ -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/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..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 @@ -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; @@ -10,6 +10,6 @@ public class QuestionsResponseDto { private List basicQuestions; - private List specialQuestions; + private List specialQuestions; } 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..ff85084 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; @@ -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 ========== 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