From bac5df4c79fec89c8e9a4cdc7a1ce30137e6b6d6 Mon Sep 17 00:00:00 2001 From: jihyo Date: Mon, 13 May 2024 11:36:55 +0900 Subject: [PATCH 1/3] =?UTF-8?q?chore:=20=EC=BB=A4=EB=B0=8B=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/QuestionRepositoryImpl.java | 7 +++---- .../presentation/question/api/QuestionApi.java | 15 --------------- 2 files changed, 3 insertions(+), 19 deletions(-) diff --git a/baebae-BE/src/main/java/com/web/baebaeBE/infra/question/repository/QuestionRepositoryImpl.java b/baebae-BE/src/main/java/com/web/baebaeBE/infra/question/repository/QuestionRepositoryImpl.java index e4ae32dd..67ad5cc3 100644 --- a/baebae-BE/src/main/java/com/web/baebaeBE/infra/question/repository/QuestionRepositoryImpl.java +++ b/baebae-BE/src/main/java/com/web/baebaeBE/infra/question/repository/QuestionRepositoryImpl.java @@ -12,7 +12,7 @@ @Repository @Primary @RequiredArgsConstructor -public class QuestionRepositoryImpl implements QuestionRepository{ +public class QuestionRepositoryImpl implements QuestionRepository { private final QuestionJpaRepository questionJpaRepository; @Override @@ -41,10 +41,9 @@ public Page findAllByMemberIdAndIsAnsweredFalse(Long memberId, Pageabl // 이 메서드는 답변되지 않은 질문만 필터링하여 반환합니다. return questionJpaRepository.findAllByMemberIdAndIsAnsweredFalse(memberId, pageable); } + @Override public void delete(Question questionEntity) { questionJpaRepository.delete(questionEntity); } -} - - +} \ No newline at end of file diff --git a/baebae-BE/src/main/java/com/web/baebaeBE/presentation/question/api/QuestionApi.java b/baebae-BE/src/main/java/com/web/baebaeBE/presentation/question/api/QuestionApi.java index e7f28604..19954d85 100644 --- a/baebae-BE/src/main/java/com/web/baebaeBE/presentation/question/api/QuestionApi.java +++ b/baebae-BE/src/main/java/com/web/baebaeBE/presentation/question/api/QuestionApi.java @@ -122,19 +122,4 @@ ResponseEntity updateQuestion( @PathVariable Long questionId, @RequestParam String content, @RequestParam boolean isAnswered); - - @Operation( - summary = "질문 삭제", - description = "특정 질문을 삭제합니다.", - security = @SecurityRequirement(name = "bearerAuth") - ) - @Parameter( - in = ParameterIn.HEADER, - name = "Authorization", required = true, - schema = @Schema(type = "string"), - description = "Bearer [Access 토큰]") - @ApiResponse(responseCode = "204", description = "질문 삭제 성공") - @RequestMapping(method = RequestMethod.DELETE, value = "/{questionId}") - ResponseEntity deleteQuestion( - @PathVariable Long questionId); } \ No newline at end of file From 46e04b5a862cb3f36a0c2ccc62001659b639bf03 Mon Sep 17 00:00:00 2001 From: jihyo Date: Mon, 13 May 2024 11:38:12 +0900 Subject: [PATCH 2/3] =?UTF-8?q?chore:=20=EC=BB=A4=EB=B0=8B=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- baebae-BE/src/main/resources/application.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/baebae-BE/src/main/resources/application.yml b/baebae-BE/src/main/resources/application.yml index 8812d8bc..bfc56e82 100644 --- a/baebae-BE/src/main/resources/application.yml +++ b/baebae-BE/src/main/resources/application.yml @@ -4,4 +4,3 @@ spring: profiles: active: deploy - From e6201cef01656ac7160b8d4425863ef69dba114e Mon Sep 17 00:00:00 2001 From: jihyo Date: Mon, 13 May 2024 15:39:41 +0900 Subject: [PATCH 3/3] =?UTF-8?q?fix:=20answer,=20question=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=20=EA=B5=AC=EC=A1=B0=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/answer/AnswerApplication.java | 69 ------- .../question/QuestionApplication.java | 59 ------ .../answer/controller}/AnswerController.java | 50 +++--- .../answer/controller}/api/AnswerApi.java | 10 +- .../answer/dto/AnswerCreateRequest.java | 3 +- .../answer/dto/AnswerDetailResponse.java | 2 +- .../answer/dto/AnswerResponse.java | 4 +- .../answer/entity/Answer.java | 4 +- .../answer/exception/AnswerError.java | 2 +- .../repository/AnswerJpaRepository.java | 4 +- .../answer/repository/AnswerMapper.java | 15 +- .../answer/repository/AnswerRepository.java | 4 +- .../repository/AnswerRepositoryImpl.java | 8 +- .../domain/answer/service/AnswerService.java | 122 +++++++++++++ .../answer/entity/CategorizedAnswer.java | 2 +- .../category/service/CategoryService.java | 6 +- .../baebaeBE/domain/member/entity/Member.java | 2 +- .../controller/QuestionController.java | 64 +++++++ .../question/controller}/api/QuestionApi.java | 88 ++++----- .../question/dto/QuestionCreateRequest.java | 3 +- .../question/dto/QuestionDetailResponse.java | 2 +- .../question/entity/Question.java | 2 +- .../question/exception/QuestionError.java | 2 +- .../repository/QuestionJpaRepository.java | 5 +- .../question/repository/QuestionMapper.java | 8 +- .../repository/QuestionRepository.java | 4 +- .../repository/QuestionRepositoryImpl.java | 4 +- .../question/service/QuestionService.java | 83 +++++++++ .../answer/service/AnswerService.java | 170 ------------------ .../question/service/QuestionService.java | 63 ------- .../firebase/FirebaseNotificationService.java | 4 +- .../question/QuestionController.java | 71 -------- baebae-BE/src/main/resources/application.yml | 2 + .../integration/question/QuestionTest.java | 9 +- 34 files changed, 379 insertions(+), 571 deletions(-) delete mode 100644 baebae-BE/src/main/java/com/web/baebaeBE/application/answer/AnswerApplication.java delete mode 100644 baebae-BE/src/main/java/com/web/baebaeBE/application/question/QuestionApplication.java rename baebae-BE/src/main/java/com/web/baebaeBE/{presentation/answer => domain/answer/controller}/AnswerController.java (51%) rename baebae-BE/src/main/java/com/web/baebaeBE/{presentation/answer => domain/answer/controller}/api/AnswerApi.java (93%) rename baebae-BE/src/main/java/com/web/baebaeBE/{presentation => domain}/answer/dto/AnswerCreateRequest.java (91%) rename baebae-BE/src/main/java/com/web/baebaeBE/{presentation => domain}/answer/dto/AnswerDetailResponse.java (97%) rename baebae-BE/src/main/java/com/web/baebaeBE/{presentation => domain}/answer/dto/AnswerResponse.java (81%) rename baebae-BE/src/main/java/com/web/baebaeBE/{infra => domain}/answer/entity/Answer.java (96%) rename baebae-BE/src/main/java/com/web/baebaeBE/{domain23 => domain}/answer/exception/AnswerError.java (93%) rename baebae-BE/src/main/java/com/web/baebaeBE/{infra => domain}/answer/repository/AnswerJpaRepository.java (78%) rename baebae-BE/src/main/java/com/web/baebaeBE/{infra => domain}/answer/repository/AnswerMapper.java (78%) rename baebae-BE/src/main/java/com/web/baebaeBE/{infra => domain}/answer/repository/AnswerRepository.java (80%) rename baebae-BE/src/main/java/com/web/baebaeBE/{infra => domain}/answer/repository/AnswerRepositoryImpl.java (79%) create mode 100644 baebae-BE/src/main/java/com/web/baebaeBE/domain/answer/service/AnswerService.java create mode 100644 baebae-BE/src/main/java/com/web/baebaeBE/domain/question/controller/QuestionController.java rename baebae-BE/src/main/java/com/web/baebaeBE/{presentation/question => domain/question/controller}/api/QuestionApi.java (54%) rename baebae-BE/src/main/java/com/web/baebaeBE/{presentation => domain}/question/dto/QuestionCreateRequest.java (87%) rename baebae-BE/src/main/java/com/web/baebaeBE/{presentation => domain}/question/dto/QuestionDetailResponse.java (95%) rename baebae-BE/src/main/java/com/web/baebaeBE/{infra => domain}/question/entity/Question.java (96%) rename baebae-BE/src/main/java/com/web/baebaeBE/{domain23 => domain}/question/exception/QuestionError.java (92%) rename baebae-BE/src/main/java/com/web/baebaeBE/{infra => domain}/question/repository/QuestionJpaRepository.java (75%) rename baebae-BE/src/main/java/com/web/baebaeBE/{infra => domain}/question/repository/QuestionMapper.java (80%) rename baebae-BE/src/main/java/com/web/baebaeBE/{infra => domain}/question/repository/QuestionRepository.java (83%) rename baebae-BE/src/main/java/com/web/baebaeBE/{infra => domain}/question/repository/QuestionRepositoryImpl.java (93%) create mode 100644 baebae-BE/src/main/java/com/web/baebaeBE/domain/question/service/QuestionService.java delete mode 100644 baebae-BE/src/main/java/com/web/baebaeBE/domain23/answer/service/AnswerService.java delete mode 100644 baebae-BE/src/main/java/com/web/baebaeBE/domain23/question/service/QuestionService.java delete mode 100644 baebae-BE/src/main/java/com/web/baebaeBE/presentation/question/QuestionController.java diff --git a/baebae-BE/src/main/java/com/web/baebaeBE/application/answer/AnswerApplication.java b/baebae-BE/src/main/java/com/web/baebaeBE/application/answer/AnswerApplication.java deleted file mode 100644 index 041bc391..00000000 --- a/baebae-BE/src/main/java/com/web/baebaeBE/application/answer/AnswerApplication.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.web.baebaeBE.application.answer; - -import com.web.baebaeBE.domain23.answer.exception.AnswerError; -import com.web.baebaeBE.domain23.answer.service.AnswerService; -import com.web.baebaeBE.domain.login.exception.LoginException; -import com.web.baebaeBE.global.error.exception.BusinessException; -import com.web.baebaeBE.infra.answer.entity.Answer; -import com.web.baebaeBE.infra.answer.repository.AnswerMapper; -import com.web.baebaeBE.domain.member.entity.Member; -import com.web.baebaeBE.domain.member.repository.MemberRepository; -import com.web.baebaeBE.infra.question.entity.Question; -import com.web.baebaeBE.infra.question.repository.QuestionRepository; -import com.web.baebaeBE.presentation.answer.dto.AnswerCreateRequest; -import com.web.baebaeBE.presentation.answer.dto.AnswerDetailResponse; -import com.web.baebaeBE.presentation.answer.dto.AnswerResponse; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Service; -import org.springframework.web.multipart.MultipartFile; - -import java.util.List; -import java.util.stream.Collectors; - -@Service -@RequiredArgsConstructor -public class AnswerApplication { - private final AnswerService answerService; - private final AnswerMapper answerMapper; - private final MemberRepository memberRepository; - private final QuestionRepository questionRepository; - - public AnswerDetailResponse createAnswer(AnswerCreateRequest request, Long memberId, MultipartFile imageFiles) { - Member member = memberRepository.findById(memberId) - .orElseThrow(() -> new BusinessException(LoginException.NOT_EXIST_MEMBER)); - System.out.println(request.getQuestionId()); - Question question = questionRepository.findById(request.getQuestionId()) - .orElseThrow(() -> new BusinessException(AnswerError.NO_EXIST_QUESTION)); - - Answer answerEntity = answerMapper.toEntity(request, question, member); - Answer savedAnswerEntity = answerService.createAnswer(answerEntity, imageFiles); - return answerMapper.toDomain(savedAnswerEntity, "나중에 수정 요망"); - } - - public List getAnswersByMemberId(Long memberId) { - List answers = answerService.getAnswersByMemberId(memberId); - return answers.stream() - .map(AnswerResponse::of) - .collect(Collectors.toList()); - } - - public Page getAllAnswers(Long memberId, Pageable pageable) { - Page answerPage = answerService.getAllAnswers(memberId, pageable); - return answerPage.map(answer -> answerMapper.toDomain(answer, "나중에 수정 요망")); - } - - public AnswerDetailResponse updateAnswer(Long answerId, AnswerCreateRequest request, MultipartFile imageFiles) { - Answer updatedAnswer = answerService.updateAnswer(answerId, request, imageFiles); - return answerMapper.toDomain(updatedAnswer, "나중에 수정 요망"); - } - - public void deleteAnswer(Long answerId) { - answerService.deleteAnswer(answerId); - } - - public void updateReactionCounts(Long answerId, int heartCount, int curiousCount, int sadCount) { - answerService.updateReactionCounts(answerId, heartCount, curiousCount, sadCount); - } -} diff --git a/baebae-BE/src/main/java/com/web/baebaeBE/application/question/QuestionApplication.java b/baebae-BE/src/main/java/com/web/baebaeBE/application/question/QuestionApplication.java deleted file mode 100644 index cd5ad3ce..00000000 --- a/baebae-BE/src/main/java/com/web/baebaeBE/application/question/QuestionApplication.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.web.baebaeBE.application.question; - -import com.web.baebaeBE.domain.login.exception.LoginException; -import com.web.baebaeBE.domain23.question.service.QuestionService; -import com.web.baebaeBE.global.error.exception.BusinessException; -import com.web.baebaeBE.domain.member.entity.Member; -import com.web.baebaeBE.domain.member.repository.MemberRepository; -import com.web.baebaeBE.infra.question.entity.Question; -import com.web.baebaeBE.infra.question.repository.QuestionMapper; -import com.web.baebaeBE.presentation.question.dto.QuestionCreateRequest; -import com.web.baebaeBE.presentation.question.dto.QuestionDetailResponse; -import jakarta.transaction.Transactional; -import lombok.RequiredArgsConstructor; - -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -@Transactional -public class QuestionApplication { - private final QuestionService questionService; - private final QuestionMapper questionMapper; - private final MemberRepository memberRepository; - - public QuestionDetailResponse createQuestion(QuestionCreateRequest request, Long memberId) { - Member member = memberRepository.findById(memberId) - .orElseThrow(() -> new BusinessException(LoginException.NOT_EXIST_MEMBER)); - - Question questionEntity = questionMapper.toEntity(request, member); - // 이제 멤버의 FCM 토큰을 사용하여 질문을 생성 - Question savedQuestionEntity = questionService.createQuestion(questionEntity); - return questionMapper.toDomain(savedQuestionEntity, member.getFcmToken()); - } - - public Page getAllQuestions(Long memberId, Pageable pageable) { - Page questionPage = questionService.getQuestionsByMemberId(memberId, pageable); - return questionPage.map(question -> questionMapper.toDomain(question, "appropriate token here")); - } - - public Page getAnsweredQuestions(Long memberId, Pageable pageable) { - Page questionPage = questionService.getAnsweredQuestions(memberId, pageable); - return questionPage.map(question -> questionMapper.toDomain(question, "appropriate token here")); - } - - public Page getUnansweredQuestions(Long memberId, Pageable pageable) { - Page questionPage = questionService.getUnansweredQuestions(memberId, pageable); - return questionPage.map(question -> questionMapper.toDomain(question, "appropriate token here")); - } - public QuestionDetailResponse updateQuestion(Long questionId, String content, Boolean isAnswered) { - Question updatedQuestion = questionService.updateQuestion(questionId, content, isAnswered); - return questionMapper.toDomain(updatedQuestion, updatedQuestion.getMember().getFcmToken()); - } - public void deleteQuestion(Long questionId) { - questionService.deleteQuestion(questionId); - } - -} \ No newline at end of file diff --git a/baebae-BE/src/main/java/com/web/baebaeBE/presentation/answer/AnswerController.java b/baebae-BE/src/main/java/com/web/baebaeBE/domain/answer/controller/AnswerController.java similarity index 51% rename from baebae-BE/src/main/java/com/web/baebaeBE/presentation/answer/AnswerController.java rename to baebae-BE/src/main/java/com/web/baebaeBE/domain/answer/controller/AnswerController.java index 918a7170..3a7f6226 100644 --- a/baebae-BE/src/main/java/com/web/baebaeBE/presentation/answer/AnswerController.java +++ b/baebae-BE/src/main/java/com/web/baebaeBE/domain/answer/controller/AnswerController.java @@ -1,14 +1,11 @@ -package com.web.baebaeBE.presentation.answer; +package com.web.baebaeBE.domain.answer.controller; -import com.google.firebase.database.annotations.NotNull; -import com.web.baebaeBE.application.answer.AnswerApplication; -import com.web.baebaeBE.infra.answer.entity.Answer; -import com.web.baebaeBE.presentation.answer.api.AnswerApi; -import com.web.baebaeBE.presentation.answer.dto.AnswerCreateRequest; -import com.web.baebaeBE.presentation.answer.dto.AnswerDetailResponse; -import com.web.baebaeBE.presentation.answer.dto.AnswerResponse; +import com.web.baebaeBE.domain.answer.controller.api.AnswerApi; +import com.web.baebaeBE.domain.answer.dto.AnswerCreateRequest; +import com.web.baebaeBE.domain.answer.dto.AnswerDetailResponse; +import com.web.baebaeBE.domain.answer.dto.AnswerResponse; +import com.web.baebaeBE.domain.answer.service.AnswerService; import io.swagger.v3.oas.annotations.Operation; -import jakarta.validation.Valid; import lombok.AllArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -24,52 +21,49 @@ @AllArgsConstructor @RequestMapping("/api/answers") public class AnswerController implements AnswerApi { - private final AnswerApplication answerApplication; + private final AnswerService answerService; @PostMapping(value = "/{memberId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity createAnswer(@PathVariable Long memberId, @RequestPart(value = "imageFile") MultipartFile imageFile, @RequestPart AnswerCreateRequest request) { - AnswerDetailResponse createdAnswer = answerApplication.createAnswer(request, memberId, imageFile); + AnswerDetailResponse createdAnswer = answerService.createAnswer(request, memberId, imageFile); return ResponseEntity.status(HttpStatus.CREATED).body(createdAnswer); } @GetMapping("/member/{memberId}") public ResponseEntity> getAnswersByMemberId(@PathVariable Long memberId) { - List answers = answerApplication.getAnswersByMemberId(memberId); + List answers = answerService.getAnswersByMemberId(memberId); return ResponseEntity.ok(answers); } - @GetMapping(value = "/{answerId}") + @GetMapping() public ResponseEntity> getAllAnswers(@RequestParam Long memberId, Pageable pageable) { - Page answers = answerApplication.getAllAnswers(memberId, pageable); + Page answers = answerService.getAllAnswers(memberId, pageable); return ResponseEntity.ok(answers.getContent()); } @PutMapping(value = "/{answerId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public ResponseEntity updateAnswer( - @PathVariable Long answerId, - @RequestPart(value = "imageFile") MultipartFile imageFile, - @RequestPart AnswerCreateRequest request) { - AnswerDetailResponse updatedAnswer = answerApplication.updateAnswer(answerId, request, imageFile); + public ResponseEntity updateAnswer(@PathVariable Long answerId, + @RequestPart(value = "imageFile") MultipartFile imageFile, + @RequestPart AnswerCreateRequest request) { + AnswerDetailResponse updatedAnswer = answerService.updateAnswer(answerId, request, imageFile); return ResponseEntity.ok(updatedAnswer); } @DeleteMapping("/{answerId}") - public ResponseEntity deleteAnswer( @PathVariable Long answerId) { - answerApplication.deleteAnswer(answerId); + public ResponseEntity deleteAnswer(@PathVariable Long answerId) { + answerService.deleteAnswer(answerId); return ResponseEntity.status(HttpStatus.NO_CONTENT).body(null); } @Operation(summary = "반응 알림") @PatchMapping("/{answerId}/react") - public ResponseEntity updateAnswerReactions( - @PathVariable Long answerId, - @RequestParam int heartCount, - @RequestParam int curiousCount, - @RequestParam int sadCount - ) { - answerApplication.updateReactionCounts(answerId, heartCount, curiousCount, sadCount); + public ResponseEntity updateAnswerReactions(@PathVariable Long answerId, + @RequestParam int heartCount, + @RequestParam int curiousCount, + @RequestParam int sadCount) { + answerService.updateReactionCounts(answerId, heartCount, curiousCount, sadCount); return ResponseEntity.ok().build(); } } \ No newline at end of file diff --git a/baebae-BE/src/main/java/com/web/baebaeBE/presentation/answer/api/AnswerApi.java b/baebae-BE/src/main/java/com/web/baebaeBE/domain/answer/controller/api/AnswerApi.java similarity index 93% rename from baebae-BE/src/main/java/com/web/baebaeBE/presentation/answer/api/AnswerApi.java rename to baebae-BE/src/main/java/com/web/baebaeBE/domain/answer/controller/api/AnswerApi.java index d25db9e9..2ff60c86 100644 --- a/baebae-BE/src/main/java/com/web/baebaeBE/presentation/answer/api/AnswerApi.java +++ b/baebae-BE/src/main/java/com/web/baebaeBE/domain/answer/controller/api/AnswerApi.java @@ -1,9 +1,8 @@ -package com.web.baebaeBE.presentation.answer.api; +package com.web.baebaeBE.domain.answer.controller.api; -import com.google.firebase.database.annotations.NotNull; -import com.web.baebaeBE.presentation.answer.dto.AnswerCreateRequest; -import com.web.baebaeBE.presentation.answer.dto.AnswerDetailResponse; -import com.web.baebaeBE.presentation.answer.dto.AnswerResponse; +import com.web.baebaeBE.domain.answer.dto.AnswerCreateRequest; +import com.web.baebaeBE.domain.answer.dto.AnswerDetailResponse; +import com.web.baebaeBE.domain.answer.dto.AnswerResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.enums.ParameterIn; @@ -12,7 +11,6 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.http.MediaType; diff --git a/baebae-BE/src/main/java/com/web/baebaeBE/presentation/answer/dto/AnswerCreateRequest.java b/baebae-BE/src/main/java/com/web/baebaeBE/domain/answer/dto/AnswerCreateRequest.java similarity index 91% rename from baebae-BE/src/main/java/com/web/baebaeBE/presentation/answer/dto/AnswerCreateRequest.java rename to baebae-BE/src/main/java/com/web/baebaeBE/domain/answer/dto/AnswerCreateRequest.java index 9ec513a3..98339a02 100644 --- a/baebae-BE/src/main/java/com/web/baebaeBE/presentation/answer/dto/AnswerCreateRequest.java +++ b/baebae-BE/src/main/java/com/web/baebaeBE/domain/answer/dto/AnswerCreateRequest.java @@ -1,6 +1,5 @@ -package com.web.baebaeBE.presentation.answer.dto; +package com.web.baebaeBE.domain.answer.dto; -import org.springframework.web.multipart.MultipartFile; import lombok.*; import java.util.List; diff --git a/baebae-BE/src/main/java/com/web/baebaeBE/presentation/answer/dto/AnswerDetailResponse.java b/baebae-BE/src/main/java/com/web/baebaeBE/domain/answer/dto/AnswerDetailResponse.java similarity index 97% rename from baebae-BE/src/main/java/com/web/baebaeBE/presentation/answer/dto/AnswerDetailResponse.java rename to baebae-BE/src/main/java/com/web/baebaeBE/domain/answer/dto/AnswerDetailResponse.java index 71a176d1..c04db7b0 100644 --- a/baebae-BE/src/main/java/com/web/baebaeBE/presentation/answer/dto/AnswerDetailResponse.java +++ b/baebae-BE/src/main/java/com/web/baebaeBE/domain/answer/dto/AnswerDetailResponse.java @@ -1,4 +1,4 @@ -package com.web.baebaeBE.presentation.answer.dto; +package com.web.baebaeBE.domain.answer.dto; import lombok.Getter; import lombok.Setter; diff --git a/baebae-BE/src/main/java/com/web/baebaeBE/presentation/answer/dto/AnswerResponse.java b/baebae-BE/src/main/java/com/web/baebaeBE/domain/answer/dto/AnswerResponse.java similarity index 81% rename from baebae-BE/src/main/java/com/web/baebaeBE/presentation/answer/dto/AnswerResponse.java rename to baebae-BE/src/main/java/com/web/baebaeBE/domain/answer/dto/AnswerResponse.java index 366b2d6f..0f8c6d82 100644 --- a/baebae-BE/src/main/java/com/web/baebaeBE/presentation/answer/dto/AnswerResponse.java +++ b/baebae-BE/src/main/java/com/web/baebaeBE/domain/answer/dto/AnswerResponse.java @@ -1,6 +1,6 @@ -package com.web.baebaeBE.presentation.answer.dto; +package com.web.baebaeBE.domain.answer.dto; -import com.web.baebaeBE.infra.answer.entity.Answer; +import com.web.baebaeBE.domain.answer.entity.Answer; import lombok.Getter; import lombok.Setter; diff --git a/baebae-BE/src/main/java/com/web/baebaeBE/infra/answer/entity/Answer.java b/baebae-BE/src/main/java/com/web/baebaeBE/domain/answer/entity/Answer.java similarity index 96% rename from baebae-BE/src/main/java/com/web/baebaeBE/infra/answer/entity/Answer.java rename to baebae-BE/src/main/java/com/web/baebaeBE/domain/answer/entity/Answer.java index 41c57537..e716c010 100644 --- a/baebae-BE/src/main/java/com/web/baebaeBE/infra/answer/entity/Answer.java +++ b/baebae-BE/src/main/java/com/web/baebaeBE/domain/answer/entity/Answer.java @@ -1,8 +1,8 @@ -package com.web.baebaeBE.infra.answer.entity; +package com.web.baebaeBE.domain.answer.entity; import com.web.baebaeBE.domain.categorized.answer.entity.CategorizedAnswer; import com.web.baebaeBE.domain.member.entity.Member; -import com.web.baebaeBE.infra.question.entity.Question; +import com.web.baebaeBE.domain.question.entity.Question; import jakarta.persistence.*; import lombok.*; import org.hibernate.annotations.OnDelete; diff --git a/baebae-BE/src/main/java/com/web/baebaeBE/domain23/answer/exception/AnswerError.java b/baebae-BE/src/main/java/com/web/baebaeBE/domain/answer/exception/AnswerError.java similarity index 93% rename from baebae-BE/src/main/java/com/web/baebaeBE/domain23/answer/exception/AnswerError.java rename to baebae-BE/src/main/java/com/web/baebaeBE/domain/answer/exception/AnswerError.java index b22b90ea..34df6fd0 100644 --- a/baebae-BE/src/main/java/com/web/baebaeBE/domain23/answer/exception/AnswerError.java +++ b/baebae-BE/src/main/java/com/web/baebaeBE/domain/answer/exception/AnswerError.java @@ -1,4 +1,4 @@ -package com.web.baebaeBE.domain23.answer.exception; +package com.web.baebaeBE.domain.answer.exception; import com.web.baebaeBE.global.error.ErrorCode; import lombok.Getter; diff --git a/baebae-BE/src/main/java/com/web/baebaeBE/infra/answer/repository/AnswerJpaRepository.java b/baebae-BE/src/main/java/com/web/baebaeBE/domain/answer/repository/AnswerJpaRepository.java similarity index 78% rename from baebae-BE/src/main/java/com/web/baebaeBE/infra/answer/repository/AnswerJpaRepository.java rename to baebae-BE/src/main/java/com/web/baebaeBE/domain/answer/repository/AnswerJpaRepository.java index 5a27022a..f48f2a28 100644 --- a/baebae-BE/src/main/java/com/web/baebaeBE/infra/answer/repository/AnswerJpaRepository.java +++ b/baebae-BE/src/main/java/com/web/baebaeBE/domain/answer/repository/AnswerJpaRepository.java @@ -1,6 +1,6 @@ -package com.web.baebaeBE.infra.answer.repository; +package com.web.baebaeBE.domain.answer.repository; -import com.web.baebaeBE.infra.answer.entity.Answer; +import com.web.baebaeBE.domain.answer.entity.Answer; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; diff --git a/baebae-BE/src/main/java/com/web/baebaeBE/infra/answer/repository/AnswerMapper.java b/baebae-BE/src/main/java/com/web/baebaeBE/domain/answer/repository/AnswerMapper.java similarity index 78% rename from baebae-BE/src/main/java/com/web/baebaeBE/infra/answer/repository/AnswerMapper.java rename to baebae-BE/src/main/java/com/web/baebaeBE/domain/answer/repository/AnswerMapper.java index 98036246..a5459572 100644 --- a/baebae-BE/src/main/java/com/web/baebaeBE/infra/answer/repository/AnswerMapper.java +++ b/baebae-BE/src/main/java/com/web/baebaeBE/domain/answer/repository/AnswerMapper.java @@ -1,10 +1,11 @@ -package com.web.baebaeBE.infra.answer.repository; +package com.web.baebaeBE.domain.answer.repository; -import com.web.baebaeBE.infra.answer.entity.Answer; +import com.web.baebaeBE.domain.answer.dto.AnswerResponse; +import com.web.baebaeBE.domain.answer.entity.Answer; import com.web.baebaeBE.domain.member.entity.Member; -import com.web.baebaeBE.infra.question.entity.Question; -import com.web.baebaeBE.presentation.answer.dto.AnswerCreateRequest; -import com.web.baebaeBE.presentation.answer.dto.AnswerDetailResponse; +import com.web.baebaeBE.domain.question.entity.Question; +import com.web.baebaeBE.domain.answer.dto.AnswerCreateRequest; +import com.web.baebaeBE.domain.answer.dto.AnswerDetailResponse; import lombok.AllArgsConstructor; import org.springframework.stereotype.Component; @@ -48,4 +49,8 @@ public AnswerDetailResponse toDomain(Answer answer, String fcmtoken) { fcmtoken ); } + + public AnswerResponse toResponse(Answer answer) { + return AnswerResponse.of(answer); + } } \ No newline at end of file diff --git a/baebae-BE/src/main/java/com/web/baebaeBE/infra/answer/repository/AnswerRepository.java b/baebae-BE/src/main/java/com/web/baebaeBE/domain/answer/repository/AnswerRepository.java similarity index 80% rename from baebae-BE/src/main/java/com/web/baebaeBE/infra/answer/repository/AnswerRepository.java rename to baebae-BE/src/main/java/com/web/baebaeBE/domain/answer/repository/AnswerRepository.java index 4e6d2e94..3aa4cead 100644 --- a/baebae-BE/src/main/java/com/web/baebaeBE/infra/answer/repository/AnswerRepository.java +++ b/baebae-BE/src/main/java/com/web/baebaeBE/domain/answer/repository/AnswerRepository.java @@ -1,6 +1,6 @@ -package com.web.baebaeBE.infra.answer.repository; +package com.web.baebaeBE.domain.answer.repository; -import com.web.baebaeBE.infra.answer.entity.Answer; +import com.web.baebaeBE.domain.answer.entity.Answer; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; diff --git a/baebae-BE/src/main/java/com/web/baebaeBE/infra/answer/repository/AnswerRepositoryImpl.java b/baebae-BE/src/main/java/com/web/baebaeBE/domain/answer/repository/AnswerRepositoryImpl.java similarity index 79% rename from baebae-BE/src/main/java/com/web/baebaeBE/infra/answer/repository/AnswerRepositoryImpl.java rename to baebae-BE/src/main/java/com/web/baebaeBE/domain/answer/repository/AnswerRepositoryImpl.java index a80400b6..d7644b17 100644 --- a/baebae-BE/src/main/java/com/web/baebaeBE/infra/answer/repository/AnswerRepositoryImpl.java +++ b/baebae-BE/src/main/java/com/web/baebaeBE/domain/answer/repository/AnswerRepositoryImpl.java @@ -1,6 +1,8 @@ -package com.web.baebaeBE.infra.answer.repository; +package com.web.baebaeBE.domain.answer.repository; -import com.web.baebaeBE.infra.answer.entity.Answer; +import com.web.baebaeBE.domain.answer.entity.Answer; +import com.web.baebaeBE.domain.answer.repository.AnswerJpaRepository; +import com.web.baebaeBE.domain.answer.repository.AnswerRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Primary; import org.springframework.data.domain.Page; @@ -12,7 +14,7 @@ @Repository @Primary -public class AnswerRepositoryImpl implements AnswerRepository{ +public class AnswerRepositoryImpl implements AnswerRepository { private final AnswerJpaRepository jpaRepository; @Autowired diff --git a/baebae-BE/src/main/java/com/web/baebaeBE/domain/answer/service/AnswerService.java b/baebae-BE/src/main/java/com/web/baebaeBE/domain/answer/service/AnswerService.java new file mode 100644 index 00000000..483f50d6 --- /dev/null +++ b/baebae-BE/src/main/java/com/web/baebaeBE/domain/answer/service/AnswerService.java @@ -0,0 +1,122 @@ +package com.web.baebaeBE.domain.answer.service; + +import com.web.baebaeBE.domain.answer.dto.AnswerDetailResponse; +import com.web.baebaeBE.domain.answer.dto.AnswerResponse; +import com.web.baebaeBE.domain.answer.exception.AnswerError; +import com.web.baebaeBE.domain.answer.repository.AnswerMapper; +import com.web.baebaeBE.domain.login.exception.LoginException; +import com.web.baebaeBE.domain.member.entity.Member; +import com.web.baebaeBE.domain.member.repository.MemberRepository; +import com.web.baebaeBE.domain.question.repository.QuestionRepository; +import com.web.baebaeBE.global.error.exception.BusinessException; +import com.web.baebaeBE.global.firebase.FirebaseNotificationService; +import com.web.baebaeBE.global.image.s3.S3ImageStorageService; +import com.web.baebaeBE.domain.answer.entity.Answer; +import com.web.baebaeBE.domain.answer.repository.AnswerRepository; +import com.web.baebaeBE.domain.question.entity.Question; +import com.web.baebaeBE.domain.answer.dto.AnswerCreateRequest; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +@RequiredArgsConstructor +@Service +public class AnswerService { + private final AnswerRepository answerRepository; + private final MemberRepository memberRepository; + private final QuestionRepository questionRepository; + private final FirebaseNotificationService firebaseNotificationService; + private final S3ImageStorageService s3ImageStorageService; + private final AnswerMapper answerMapper; + + @Transactional + public AnswerDetailResponse createAnswer(AnswerCreateRequest request, Long memberId, MultipartFile imageFile) { + Member member = memberRepository.findById(memberId) + .orElseThrow(() -> new BusinessException(LoginException.NOT_EXIST_MEMBER)); + Question question = questionRepository.findById(request.getQuestionId()) + .orElseThrow(() -> new BusinessException(AnswerError.NO_EXIST_QUESTION)); + + Answer answer = answerMapper.toEntity(request, question, member); + Answer savedAnswer = answerRepository.save(answer); + if (!imageFile.isEmpty()) { + try (InputStream inputStream = imageFile.getInputStream()) { + String imageUrl = s3ImageStorageService.uploadFile(member.getId().toString(), answer.getId().toString(), "image", 0, inputStream, imageFile.getSize(), imageFile.getContentType()); + answer.setImageFiles(List.of(imageUrl)); + } catch (IOException e) { + throw new BusinessException(AnswerError.IMAGE_PROCESSING_ERROR); + } + } + firebaseNotificationService.notifyNewAnswer(member, answer); + // 질문의 isAnswered 상태를 true로 업데이트 + question.setAnswered(true); + questionRepository.save(question); + + return answerMapper.toDomain(savedAnswer, "FCM token needed"); + } + + @Transactional + public List getAnswersByMemberId(Long memberId) { + List answers = answerRepository.findByMemberId(memberId); + return answers.stream() + .map(answerMapper::toResponse) + .collect(Collectors.toList()); + } + + @Transactional + public Page getAllAnswers(Long memberId, Pageable pageable) { + Page answerPage = answerRepository.findAllByMemberId(memberId, pageable); + + return answerPage.map(answer -> answerMapper.toDomain(answer, "FCM token needed")); + } + + @Transactional + public AnswerDetailResponse updateAnswer(Long answerId, AnswerCreateRequest request, MultipartFile imageFile) { + Answer answer = answerRepository.findByAnswerId(answerId) + .orElseThrow(() -> new BusinessException(AnswerError.NO_EXIST_ANSWER)); + answer.setContent(request.getContent()); + answer.setLinkAttachments(request.getLinkAttachments()); + answer.setMusicName(request.getMusicName()); + answer.setMusicSinger(request.getMusicSinger()); + if (!imageFile.isEmpty()) { + try (InputStream inputStream = imageFile.getInputStream()) { + String imageUrl = s3ImageStorageService.uploadFile(answer.getMember().getId().toString(), answerId.toString(), "image", 0, inputStream, imageFile.getSize(), imageFile.getContentType()); + answer.setImageFiles(List.of(imageUrl)); + } catch (IOException e) { + throw new BusinessException(AnswerError.IMAGE_PROCESSING_ERROR); + } + } + // 질문의 isAnswered 상태를 true로 업데이트 + Question question = answer.getQuestion(); + question.setAnswered(true); + + questionRepository.save(question); + answer = answerRepository.save(answer); + return answerMapper.toDomain(answer, "FCM token needed"); + } + + @Transactional + public void deleteAnswer(Long answerId) { + Answer answer = answerRepository.findByAnswerId(answerId) + .orElseThrow(() -> new IllegalArgumentException("No answer found with id " + answerId)); + answerRepository.delete(answer); + } + + @Transactional + public void updateReactionCounts(Long answerId, int heartCount, int curiousCount, int sadCount) { + Answer answer = answerRepository.findByAnswerId(answerId) + .orElseThrow(() -> new BusinessException(AnswerError.NO_EXIST_ANSWER)); + answer.setHeartCount(heartCount); + answer.setCuriousCount(curiousCount); + answer.setSadCount(sadCount); + answerRepository.save(answer); + } +} diff --git a/baebae-BE/src/main/java/com/web/baebaeBE/domain/categorized/answer/entity/CategorizedAnswer.java b/baebae-BE/src/main/java/com/web/baebaeBE/domain/categorized/answer/entity/CategorizedAnswer.java index 700649eb..f66f07ad 100644 --- a/baebae-BE/src/main/java/com/web/baebaeBE/domain/categorized/answer/entity/CategorizedAnswer.java +++ b/baebae-BE/src/main/java/com/web/baebaeBE/domain/categorized/answer/entity/CategorizedAnswer.java @@ -1,6 +1,6 @@ package com.web.baebaeBE.domain.categorized.answer.entity; -import com.web.baebaeBE.infra.answer.entity.Answer; +import com.web.baebaeBE.domain.answer.entity.Answer; import com.web.baebaeBE.domain.category.entity.Category; import jakarta.persistence.*; import lombok.*; diff --git a/baebae-BE/src/main/java/com/web/baebaeBE/domain/category/service/CategoryService.java b/baebae-BE/src/main/java/com/web/baebaeBE/domain/category/service/CategoryService.java index ccb55fa9..2e4a8e61 100644 --- a/baebae-BE/src/main/java/com/web/baebaeBE/domain/category/service/CategoryService.java +++ b/baebae-BE/src/main/java/com/web/baebaeBE/domain/category/service/CategoryService.java @@ -1,11 +1,11 @@ package com.web.baebaeBE.domain.category.service; -import com.web.baebaeBE.domain23.answer.exception.AnswerError; +import com.web.baebaeBE.domain.answer.exception.AnswerError; import com.web.baebaeBE.domain.category.exception.CategoryException; import com.web.baebaeBE.domain.login.exception.LoginException; import com.web.baebaeBE.global.error.exception.BusinessException; -import com.web.baebaeBE.infra.answer.entity.Answer; -import com.web.baebaeBE.infra.answer.repository.AnswerRepository; +import com.web.baebaeBE.domain.answer.entity.Answer; +import com.web.baebaeBE.domain.answer.repository.AnswerRepository; import com.web.baebaeBE.domain.categorized.answer.entity.CategorizedAnswer; import com.web.baebaeBE.domain.categorized.answer.repository.CategorizedAnswerRepository; import com.web.baebaeBE.domain.category.entity.Category; diff --git a/baebae-BE/src/main/java/com/web/baebaeBE/domain/member/entity/Member.java b/baebae-BE/src/main/java/com/web/baebaeBE/domain/member/entity/Member.java index b538c931..72357b7d 100644 --- a/baebae-BE/src/main/java/com/web/baebaeBE/domain/member/entity/Member.java +++ b/baebae-BE/src/main/java/com/web/baebaeBE/domain/member/entity/Member.java @@ -1,6 +1,6 @@ package com.web.baebaeBE.domain.member.entity; -import com.web.baebaeBE.infra.answer.entity.Answer; +import com.web.baebaeBE.domain.answer.entity.Answer; import jakarta.persistence.*; import java.time.LocalDateTime; diff --git a/baebae-BE/src/main/java/com/web/baebaeBE/domain/question/controller/QuestionController.java b/baebae-BE/src/main/java/com/web/baebaeBE/domain/question/controller/QuestionController.java new file mode 100644 index 00000000..2860d8a6 --- /dev/null +++ b/baebae-BE/src/main/java/com/web/baebaeBE/domain/question/controller/QuestionController.java @@ -0,0 +1,64 @@ +package com.web.baebaeBE.domain.question.controller; + +import com.web.baebaeBE.domain.question.controller.api.QuestionApi; +import com.web.baebaeBE.domain.question.dto.QuestionCreateRequest; +import com.web.baebaeBE.domain.question.dto.QuestionDetailResponse; +import com.web.baebaeBE.domain.question.service.QuestionService; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/questions") +public class QuestionController implements QuestionApi { + private final QuestionService questionService; + + public QuestionController(QuestionService questionService) { + this.questionService = questionService; + } + + @PostMapping("/member/{memberId}") + public ResponseEntity createQuestion( + @RequestBody QuestionCreateRequest request, @PathVariable Long memberId) { + QuestionDetailResponse createdQuestion = questionService.createQuestion(request, memberId); + return ResponseEntity.status(HttpStatus.CREATED).body(createdQuestion); + } + + @GetMapping("/member/{memberId}") + public ResponseEntity> getAllQuestions( + @PathVariable Long memberId, Pageable pageable) { + Page questions = questionService.getQuestionsByMemberId(memberId, pageable); + return ResponseEntity.ok(questions.getContent()); + } + + @GetMapping("/answered/{memberId}") + public ResponseEntity> getAnsweredQuestions( + @PathVariable Long memberId, Pageable pageable) { + Page questions = questionService.getAnsweredQuestions(memberId, pageable); + return ResponseEntity.ok(questions.getContent()); + } + + @GetMapping("/unanswered/{memberId}") + public ResponseEntity> getUnansweredQuestions( + @PathVariable Long memberId, Pageable pageable) { + Page questions = questionService.getUnansweredQuestions(memberId, pageable); + return ResponseEntity.ok(questions.getContent()); + } + + @PutMapping("/{questionId}") + public ResponseEntity updateQuestion( + @PathVariable Long questionId, @RequestParam String content, @RequestParam boolean isAnswered) { + questionService.updateQuestion(questionId, content, isAnswered); + return ResponseEntity.status(HttpStatus.NO_CONTENT).body(null); + } + + @DeleteMapping("/{questionId}") + public ResponseEntity deleteQuestion(@PathVariable Long questionId) { + questionService.deleteQuestion(questionId); + return ResponseEntity.status(HttpStatus.NO_CONTENT).body(null); + } +} diff --git a/baebae-BE/src/main/java/com/web/baebaeBE/presentation/question/api/QuestionApi.java b/baebae-BE/src/main/java/com/web/baebaeBE/domain/question/controller/api/QuestionApi.java similarity index 54% rename from baebae-BE/src/main/java/com/web/baebaeBE/presentation/question/api/QuestionApi.java rename to baebae-BE/src/main/java/com/web/baebaeBE/domain/question/controller/api/QuestionApi.java index 19954d85..e4dfc4f6 100644 --- a/baebae-BE/src/main/java/com/web/baebaeBE/presentation/question/api/QuestionApi.java +++ b/baebae-BE/src/main/java/com/web/baebaeBE/domain/question/controller/api/QuestionApi.java @@ -1,7 +1,7 @@ -package com.web.baebaeBE.presentation.question.api; +package com.web.baebaeBE.domain.question.controller.api; -import com.web.baebaeBE.presentation.question.dto.QuestionCreateRequest; -import com.web.baebaeBE.presentation.question.dto.QuestionDetailResponse; +import com.web.baebaeBE.domain.question.dto.QuestionCreateRequest; +import com.web.baebaeBE.domain.question.dto.QuestionDetailResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.enums.ParameterIn; @@ -12,7 +12,6 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; -import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -24,14 +23,9 @@ public interface QuestionApi { @Operation( summary = "질문 생성", - description = "새로운 질문을 생성합니다.", + description = "특정 회원에 대한 새로운 질문을 생성합니다.", security = @SecurityRequirement(name = "bearerAuth") ) - @Parameter( - in = ParameterIn.HEADER, - name = "Authorization", required = true, - schema = @Schema(type = "string"), - description = "Bearer [Access 토큰]") @ApiResponse(responseCode = "201", description = "질문 생성 성공", content = @Content(mediaType = "application/json", schema = @Schema(implementation = QuestionDetailResponse.class))) @@ -41,85 +35,67 @@ public interface QuestionApi { " \"errorCode\": \"T-002\",\n" + " \"message\": \"해당 토큰은 유효한 토큰이 아닙니다.\"\n" + "}"))) - @RequestMapping(method = RequestMethod.POST, value = "/member/{memberId}") + @PostMapping("/member/{memberId}") ResponseEntity createQuestion( - @RequestBody QuestionCreateRequest questionDTO, + @RequestBody QuestionCreateRequest request, @PathVariable Long memberId); -// @Operation( -// summary = "모든 질문 조회", -// description = "모든 질문을 페이지네이션으로 조회합니다.", -// security = @SecurityRequirement(name = "bearerAuth") -// ) -// @Parameter( -// in = ParameterIn.HEADER, -// name = "Authorization", required = true, -// schema = @Schema(type = "string"), -// description = "Bearer [Access 토큰]") -// @ApiResponse(responseCode = "200", description = "질문 조회 성공", -// content = @Content(mediaType = "application/json", -// schema = @Schema(implementation = QuestionDetailResponse.class))) -// @RequestMapping(method = RequestMethod.GET) -// ResponseEntity> getAllQuestions( -// @RequestParam Long memberId, -// Pageable pageable); + @Operation( + summary = "모든 질문 조회", + description = "특정 회원의 모든 질문을 페이지네이션으로 조회합니다.", + security = @SecurityRequirement(name = "bearerAuth") + ) + @ApiResponse(responseCode = "200", description = "질문 조회 성공", + content = @Content(mediaType = "application/json", + schema = @Schema(implementation = List.class))) + @GetMapping("/member/{memberId}") + ResponseEntity> getAllQuestions( + @PathVariable Long memberId, Pageable pageable); @Operation( summary = "답변된 질문 조회", - description = "답변된 모든 질문을 페이지네이션으로 조회합니다.", + description = "특정 회원의 답변된 모든 질문을 페이지네이션으로 조회합니다.", security = @SecurityRequirement(name = "bearerAuth") ) - @Parameter( - in = ParameterIn.HEADER, - name = "Authorization", required = true, - schema = @Schema(type = "string"), - description = "Bearer [Access 토큰]") - @ApiResponse(responseCode = "200", description = "답변된 질문 조회 성공", - content = @Content(mediaType = "application/json", - schema = @Schema(implementation = QuestionDetailResponse.class))) + @ApiResponse(responseCode = "200", description = "답변된 질문 조회 성공") @GetMapping("/answered/{memberId}") ResponseEntity> getAnsweredQuestions( @PathVariable Long memberId, Pageable pageable); @Operation( summary = "답변되지 않은 질문 조회", - description = "답변되지 않은 모든 질문을 페이지네이션으로 조회합니다.", + description = "특정 회원의 답변되지 않은 모든 질문을 페이지네이션으로 조회합니다.", security = @SecurityRequirement(name = "bearerAuth") ) - @Parameter( - in = ParameterIn.HEADER, - name = "Authorization", required = true, - schema = @Schema(type = "string"), - description = "Bearer [Access 토큰]") - @ApiResponse(responseCode = "200", description = "답변되지 않은 질문 조회 성공", - content = @Content(mediaType = "application/json", - schema = @Schema(implementation = QuestionDetailResponse.class))) + @ApiResponse(responseCode = "200", description = "답변되지 않은 질문 조회 성공") @GetMapping("/unanswered/{memberId}") ResponseEntity> getUnansweredQuestions( @PathVariable Long memberId, Pageable pageable); @Operation( summary = "질문 수정", - description = "기존 질문을 수정합니다.", + description = "기존 질문의 내용을 수정합니다.", security = @SecurityRequirement(name = "bearerAuth") ) - @Parameter( - in = ParameterIn.HEADER, - name = "Authorization", required = true, - schema = @Schema(type = "string"), - description = "Bearer [Access 토큰]") @ApiResponse(responseCode = "204", description = "질문 수정 성공") - @ApiResponse(responseCode = "401", description = "토큰 인증 실패", content = @Content(mediaType = "application/json", examples = @ExampleObject(value = "{\n" + " \"errorCode\": \"T-002\",\n" + " \"message\": \"해당 토큰은 유효한 토큰이 아닙니다.\"\n" + "}"))) - - @RequestMapping(method = RequestMethod.PUT, value = "/{questionId}") + @PutMapping("/{questionId}") ResponseEntity updateQuestion( @PathVariable Long questionId, @RequestParam String content, @RequestParam boolean isAnswered); + + @Operation( + summary = "질문 삭제", + description = "기존 질문을 삭제합니다.", + security = @SecurityRequirement(name = "bearerAuth") + ) + @ApiResponse(responseCode = "204", description = "질문 삭제 성공") + @DeleteMapping("/{questionId}") + ResponseEntity deleteQuestion(@PathVariable Long questionId); } \ No newline at end of file diff --git a/baebae-BE/src/main/java/com/web/baebaeBE/presentation/question/dto/QuestionCreateRequest.java b/baebae-BE/src/main/java/com/web/baebaeBE/domain/question/dto/QuestionCreateRequest.java similarity index 87% rename from baebae-BE/src/main/java/com/web/baebaeBE/presentation/question/dto/QuestionCreateRequest.java rename to baebae-BE/src/main/java/com/web/baebaeBE/domain/question/dto/QuestionCreateRequest.java index f22612e9..e919fdc2 100644 --- a/baebae-BE/src/main/java/com/web/baebaeBE/presentation/question/dto/QuestionCreateRequest.java +++ b/baebae-BE/src/main/java/com/web/baebaeBE/domain/question/dto/QuestionCreateRequest.java @@ -1,6 +1,5 @@ -package com.web.baebaeBE.presentation.question.dto; +package com.web.baebaeBE.domain.question.dto; -import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; diff --git a/baebae-BE/src/main/java/com/web/baebaeBE/presentation/question/dto/QuestionDetailResponse.java b/baebae-BE/src/main/java/com/web/baebaeBE/domain/question/dto/QuestionDetailResponse.java similarity index 95% rename from baebae-BE/src/main/java/com/web/baebaeBE/presentation/question/dto/QuestionDetailResponse.java rename to baebae-BE/src/main/java/com/web/baebaeBE/domain/question/dto/QuestionDetailResponse.java index 5f4d9113..6111eb3b 100644 --- a/baebae-BE/src/main/java/com/web/baebaeBE/presentation/question/dto/QuestionDetailResponse.java +++ b/baebae-BE/src/main/java/com/web/baebaeBE/domain/question/dto/QuestionDetailResponse.java @@ -1,4 +1,4 @@ -package com.web.baebaeBE.presentation.question.dto; +package com.web.baebaeBE.domain.question.dto; import java.time.LocalDateTime; diff --git a/baebae-BE/src/main/java/com/web/baebaeBE/infra/question/entity/Question.java b/baebae-BE/src/main/java/com/web/baebaeBE/domain/question/entity/Question.java similarity index 96% rename from baebae-BE/src/main/java/com/web/baebaeBE/infra/question/entity/Question.java rename to baebae-BE/src/main/java/com/web/baebaeBE/domain/question/entity/Question.java index f6bd5a46..f66f17a3 100644 --- a/baebae-BE/src/main/java/com/web/baebaeBE/infra/question/entity/Question.java +++ b/baebae-BE/src/main/java/com/web/baebaeBE/domain/question/entity/Question.java @@ -1,4 +1,4 @@ -package com.web.baebaeBE.infra.question.entity; +package com.web.baebaeBE.domain.question.entity; import com.web.baebaeBE.domain.member.entity.Member; import jakarta.persistence.*; diff --git a/baebae-BE/src/main/java/com/web/baebaeBE/domain23/question/exception/QuestionError.java b/baebae-BE/src/main/java/com/web/baebaeBE/domain/question/exception/QuestionError.java similarity index 92% rename from baebae-BE/src/main/java/com/web/baebaeBE/domain23/question/exception/QuestionError.java rename to baebae-BE/src/main/java/com/web/baebaeBE/domain/question/exception/QuestionError.java index 355a3126..673c6cc9 100644 --- a/baebae-BE/src/main/java/com/web/baebaeBE/domain23/question/exception/QuestionError.java +++ b/baebae-BE/src/main/java/com/web/baebaeBE/domain/question/exception/QuestionError.java @@ -1,4 +1,4 @@ -package com.web.baebaeBE.domain23.question.exception; +package com.web.baebaeBE.domain.question.exception; import com.web.baebaeBE.global.error.ErrorCode; import lombok.Getter; diff --git a/baebae-BE/src/main/java/com/web/baebaeBE/infra/question/repository/QuestionJpaRepository.java b/baebae-BE/src/main/java/com/web/baebaeBE/domain/question/repository/QuestionJpaRepository.java similarity index 75% rename from baebae-BE/src/main/java/com/web/baebaeBE/infra/question/repository/QuestionJpaRepository.java rename to baebae-BE/src/main/java/com/web/baebaeBE/domain/question/repository/QuestionJpaRepository.java index 658112b7..254d324b 100644 --- a/baebae-BE/src/main/java/com/web/baebaeBE/infra/question/repository/QuestionJpaRepository.java +++ b/baebae-BE/src/main/java/com/web/baebaeBE/domain/question/repository/QuestionJpaRepository.java @@ -1,10 +1,9 @@ -package com.web.baebaeBE.infra.question.repository; +package com.web.baebaeBE.domain.question.repository; -import com.web.baebaeBE.infra.question.entity.Question; +import com.web.baebaeBE.domain.question.entity.Question; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; public interface QuestionJpaRepository extends JpaRepository { Page findAllByMemberId(Long memberId, Pageable pageable); diff --git a/baebae-BE/src/main/java/com/web/baebaeBE/infra/question/repository/QuestionMapper.java b/baebae-BE/src/main/java/com/web/baebaeBE/domain/question/repository/QuestionMapper.java similarity index 80% rename from baebae-BE/src/main/java/com/web/baebaeBE/infra/question/repository/QuestionMapper.java rename to baebae-BE/src/main/java/com/web/baebaeBE/domain/question/repository/QuestionMapper.java index 6c83f326..3b49f62a 100644 --- a/baebae-BE/src/main/java/com/web/baebaeBE/infra/question/repository/QuestionMapper.java +++ b/baebae-BE/src/main/java/com/web/baebaeBE/domain/question/repository/QuestionMapper.java @@ -1,9 +1,9 @@ -package com.web.baebaeBE.infra.question.repository; +package com.web.baebaeBE.domain.question.repository; import com.web.baebaeBE.domain.member.entity.Member; -import com.web.baebaeBE.infra.question.entity.Question; -import com.web.baebaeBE.presentation.question.dto.QuestionCreateRequest; -import com.web.baebaeBE.presentation.question.dto.QuestionDetailResponse; +import com.web.baebaeBE.domain.question.entity.Question; +import com.web.baebaeBE.domain.question.dto.QuestionCreateRequest; +import com.web.baebaeBE.domain.question.dto.QuestionDetailResponse; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; diff --git a/baebae-BE/src/main/java/com/web/baebaeBE/infra/question/repository/QuestionRepository.java b/baebae-BE/src/main/java/com/web/baebaeBE/domain/question/repository/QuestionRepository.java similarity index 83% rename from baebae-BE/src/main/java/com/web/baebaeBE/infra/question/repository/QuestionRepository.java rename to baebae-BE/src/main/java/com/web/baebaeBE/domain/question/repository/QuestionRepository.java index 23b941e5..1e5fd955 100644 --- a/baebae-BE/src/main/java/com/web/baebaeBE/infra/question/repository/QuestionRepository.java +++ b/baebae-BE/src/main/java/com/web/baebaeBE/domain/question/repository/QuestionRepository.java @@ -1,6 +1,6 @@ -package com.web.baebaeBE.infra.question.repository; +package com.web.baebaeBE.domain.question.repository; -import com.web.baebaeBE.infra.question.entity.Question; +import com.web.baebaeBE.domain.question.entity.Question; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; diff --git a/baebae-BE/src/main/java/com/web/baebaeBE/infra/question/repository/QuestionRepositoryImpl.java b/baebae-BE/src/main/java/com/web/baebaeBE/domain/question/repository/QuestionRepositoryImpl.java similarity index 93% rename from baebae-BE/src/main/java/com/web/baebaeBE/infra/question/repository/QuestionRepositoryImpl.java rename to baebae-BE/src/main/java/com/web/baebaeBE/domain/question/repository/QuestionRepositoryImpl.java index 67ad5cc3..ed9ba15b 100644 --- a/baebae-BE/src/main/java/com/web/baebaeBE/infra/question/repository/QuestionRepositoryImpl.java +++ b/baebae-BE/src/main/java/com/web/baebaeBE/domain/question/repository/QuestionRepositoryImpl.java @@ -1,6 +1,6 @@ -package com.web.baebaeBE.infra.question.repository; +package com.web.baebaeBE.domain.question.repository; -import com.web.baebaeBE.infra.question.entity.Question; +import com.web.baebaeBE.domain.question.entity.Question; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Primary; import org.springframework.data.domain.Page; diff --git a/baebae-BE/src/main/java/com/web/baebaeBE/domain/question/service/QuestionService.java b/baebae-BE/src/main/java/com/web/baebaeBE/domain/question/service/QuestionService.java new file mode 100644 index 00000000..695d674c --- /dev/null +++ b/baebae-BE/src/main/java/com/web/baebaeBE/domain/question/service/QuestionService.java @@ -0,0 +1,83 @@ +package com.web.baebaeBE.domain.question.service; + +import com.web.baebaeBE.domain.login.exception.LoginException; +import com.web.baebaeBE.domain.member.entity.Member; +import com.web.baebaeBE.domain.member.repository.MemberRepository; +import com.web.baebaeBE.domain.question.dto.QuestionCreateRequest; +import com.web.baebaeBE.domain.question.dto.QuestionDetailResponse; +import com.web.baebaeBE.domain.question.exception.QuestionError; +import com.web.baebaeBE.domain.question.repository.QuestionMapper; +import com.web.baebaeBE.global.error.exception.BusinessException; +import com.web.baebaeBE.global.firebase.FirebaseNotificationService; +import com.web.baebaeBE.domain.question.entity.Question; +import com.web.baebaeBE.domain.question.repository.QuestionRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class QuestionService { + private final QuestionRepository questionRepository; + private final MemberRepository memberRepository; + private final QuestionMapper questionMapper; + private final FirebaseNotificationService firebaseNotificationService; + + @Transactional + public QuestionDetailResponse createQuestion(QuestionCreateRequest request, Long memberId) { + Member member = memberRepository.findById(memberId) + .orElseThrow(() -> new BusinessException(LoginException.NOT_EXIST_MEMBER)); + + Question question = questionMapper.toEntity(request, member); + Question savedQuestion = questionRepository.save(question); + if (member.getFcmToken() != null) { + firebaseNotificationService.notifyNewQuestion(member, question); + } + return questionMapper.toDomain(savedQuestion, member.getFcmToken()); + } + + @Transactional(readOnly = true) + public Page getQuestionsByMemberId(Long memberId, Pageable pageable) { + Page questions = questionRepository.findAllByMemberId(memberId, pageable); + return questions.map(question -> questionMapper.toDomain(question, question.getMember().getFcmToken())); + } + + @Transactional + public QuestionDetailResponse updateQuestion(Long questionId, String content, Boolean isAnswered) { + Question question = questionRepository.findById(questionId) + .orElseThrow(() -> new BusinessException(QuestionError.NO_EXIST_QUESTION)); + // 내용과 답변 상태 업데이트 + question.updateContent(content); + question.setAnswered(isAnswered); + // 질문 업데이트 후 저장 + Question updatedQuestion = questionRepository.save(question); + + // FCM 토큰이 있고 질문이 업데이트되었다면 알림 발송 + if (updatedQuestion.getMember().getFcmToken() != null) { + firebaseNotificationService.notifyNewQuestion(updatedQuestion.getMember(), updatedQuestion); + } + return questionMapper.toDomain(updatedQuestion, updatedQuestion.getMember().getFcmToken()); + + } + + @Transactional + public void deleteQuestion(Long questionId) { + Question question = questionRepository.findById(questionId) + .orElseThrow(() -> new BusinessException(QuestionError.NO_EXIST_QUESTION)); + questionRepository.delete(question); + } + + @Transactional(readOnly = true) + public Page getAnsweredQuestions(Long memberId, Pageable pageable) { + Page questions = questionRepository.findAllByMemberIdAndIsAnsweredTrue(memberId, pageable); + return questions.map(question -> questionMapper.toDomain(question, question.getMember().getFcmToken())); + } + + @Transactional(readOnly = true) + public Page getUnansweredQuestions(Long memberId, Pageable pageable) { + Page questions = questionRepository.findAllByMemberIdAndIsAnsweredFalse(memberId, pageable); + return questions.map(question -> questionMapper.toDomain(question, question.getMember().getFcmToken())); + } +} \ No newline at end of file diff --git a/baebae-BE/src/main/java/com/web/baebaeBE/domain23/answer/service/AnswerService.java b/baebae-BE/src/main/java/com/web/baebaeBE/domain23/answer/service/AnswerService.java deleted file mode 100644 index cc202afe..00000000 --- a/baebae-BE/src/main/java/com/web/baebaeBE/domain23/answer/service/AnswerService.java +++ /dev/null @@ -1,170 +0,0 @@ -package com.web.baebaeBE.domain23.answer.service; - -import com.web.baebaeBE.domain23.answer.exception.AnswerError; -import com.web.baebaeBE.global.error.exception.BusinessException; -import com.web.baebaeBE.global.firebase.FirebaseNotificationService; -import com.web.baebaeBE.global.image.s3.S3ImageStorageService; -import com.web.baebaeBE.infra.answer.entity.Answer; -import com.web.baebaeBE.infra.answer.repository.AnswerRepository; -import com.web.baebaeBE.infra.question.entity.Question; -import com.web.baebaeBE.presentation.answer.dto.AnswerCreateRequest; -import jakarta.transaction.Transactional; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Service; -import org.springframework.web.multipart.MultipartFile; - -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; - -@RequiredArgsConstructor -@Service -public class AnswerService { - private final AnswerRepository answerRepository; - private final FirebaseNotificationService firebaseNotificationService; - private final S3ImageStorageService s3ImageStorageService; - - @Transactional - public Answer createAnswer(Answer answer, MultipartFile imageFile) { - List imageUrls = new ArrayList<>(); - String musicAudioUrl = null; - Answer savedAnswer = answerRepository.save(answer); - - try { - // 이미지 파일 업로드 - if (!imageFile.isEmpty()) { - InputStream inputStream = imageFile.getInputStream(); - String imageUrl = s3ImageStorageService.uploadFile(answer.getMember().getId().toString(), answer.getId().toString(), "image", 0, inputStream, imageFile.getSize(), imageFile.getContentType()); - imageUrls.add(imageUrl); - } - - // 파일 URL을 Answer 엔티티에 저장 - answer.setImageFiles(imageUrls); - answer.setMusicAudio(answer.getMusicAudio()); - - } catch (Exception e) { - // 롤백 이후 파일 삭제 로직 - imageUrls.forEach(url -> s3ImageStorageService.deleteFileByUrl(url)); // 이미지 파일 삭제 - if (musicAudioUrl != null) { - s3ImageStorageService.deleteFileByUrl(musicAudioUrl); // 오디오 파일 삭제 - } - throw new RuntimeException("Failed to create answer", e); - } - // 새로운 답변 달리면 알림 생성 - firebaseNotificationService.notifyNewAnswer(answer.getMember(), answer); - // 질문의 isAnswered 상태를 true로 업데이트 - Question question = answer.getQuestion(); - question.setAnswered(true); - return savedAnswer; - } - - @Transactional - public Answer updateAnswer(Long answerId, AnswerCreateRequest request, MultipartFile imageFile) { - Answer answer = answerRepository.findByAnswerId(answerId) - .orElseThrow(() -> new BusinessException(AnswerError.NO_EXIST_ANSWER)); - - List urls = new ArrayList<>(); - try { - String memberId = answer.getMember().getId().toString(); - String fileType = "image"; - - // 이미지 파일 처리 - String fileName = "image_0.jpg"; - InputStream inputStream = imageFile.getInputStream(); - long size = imageFile.getSize(); - String contentType = imageFile.getContentType(); - String url = s3ImageStorageService.uploadFile(memberId, answerId.toString(), fileType, 0, inputStream, size, contentType); - urls.add(url); - - } catch (IOException e) { - throw new BusinessException(AnswerError.IMAGE_PROCESSING_ERROR); - } - - // 이미지 URL 리스트 업데이트 - answer.setImageFiles(urls); - - // 다른 필드 업데이트 - answer.setContent(request.getContent()); - answer.setLinkAttachments(request.getLinkAttachments()); - answer.setMusicName(request.getMusicName()); - answer.setMusicSinger(request.getMusicSinger()); - - // 데이터베이스 저장 - return answerRepository.save(answer); - } - - public List getAnswersByMemberId(Long memberId) { - return answerRepository.findByMemberId(memberId); - } - - @Transactional - public Page getAllAnswers(Long memberId, Pageable pageable) { - return answerRepository.findAllByMemberId(memberId, pageable); - } - - @Transactional - public void deleteAnswer(Long answerId) { - Answer answer = answerRepository.findByAnswerId(answerId) - .orElseThrow(() -> new IllegalArgumentException("No answer found with id " + answerId)); - - // S3 이미지 파일 삭제 - // https://kr.object.ncloudstorage.com/baebae-bucket/1/1/image_1.jpg - answer.getImageFiles().forEach(url -> { - String[] parts = url.split("/"); - - if (parts.length > 1) { - String fileKey = parts[parts.length - 1]; - String[] keyParts = fileKey.split("_"); - - if (keyParts.length > 1) { - int index = Integer.parseInt(keyParts[1].replace(".jpg", "")); - String memberId = parts[parts.length - 3]; - String fileType = keyParts[0]; - s3ImageStorageService.deleteFile(memberId, answerId.toString(), fileType, index); - } - } - }); - - //S3 오디오 파일 삭제 - // https://kr.object.ncloudstorage.com/baebae-bucket/1/2/audio.mp3 - String url = answer.getMusicAudio(); - String[] parts = url.split("/"); - - if (parts.length > 1) { - String[] file = parts[parts.length - 1].split("\\."); - String fileType = file[0]; - String memberId = parts[parts.length - 3]; - - s3ImageStorageService.deleteFile(memberId, answerId.toString(), fileType, 0); - } - - // answer 객체 삭제 - answerRepository.delete(answer); - } - - @Transactional - public void updateReactionCounts(Long answerId, int heartCount, int curiousCount, int sadCount) { - Answer answer = answerRepository.findByAnswerId(answerId) - .orElseThrow(() -> new BusinessException(AnswerError.NO_EXIST_ANSWER)); - - // 반응수 올라갈때마다 알림 전송 - if (answer.getHeartCount() != heartCount) { - firebaseNotificationService.notifyReaction(answer.getMember(), answer.getContent(), "heart"); - } - if (answer.getCuriousCount() != curiousCount) { - firebaseNotificationService.notifyReaction(answer.getMember(), answer.getContent(), "curious"); - } - if (answer.getSadCount() != sadCount) { - firebaseNotificationService.notifyReaction(answer.getMember(), answer.getContent(), "sad"); - } - - // 반응 수 업데이트 - answer.setHeartCount(heartCount); - answer.setCuriousCount(curiousCount); - answer.setSadCount(sadCount); - answerRepository.save(answer); - } -} diff --git a/baebae-BE/src/main/java/com/web/baebaeBE/domain23/question/service/QuestionService.java b/baebae-BE/src/main/java/com/web/baebaeBE/domain23/question/service/QuestionService.java deleted file mode 100644 index 71253bf1..00000000 --- a/baebae-BE/src/main/java/com/web/baebaeBE/domain23/question/service/QuestionService.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.web.baebaeBE.domain23.question.service; - -import com.web.baebaeBE.domain23.question.exception.QuestionError; -import com.web.baebaeBE.global.error.exception.BusinessException; -import com.web.baebaeBE.global.firebase.FirebaseNotificationService; -import com.web.baebaeBE.infra.question.entity.Question; -import com.web.baebaeBE.infra.question.repository.QuestionRepository; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -public class QuestionService { - private final QuestionRepository questionRepository; - private final FirebaseNotificationService firebaseNotificationService; - - @Transactional - public Question createQuestion(Question question) { - Question savedQuestion = questionRepository.save(question); - // 질문 생성 후 알림 발송, 멤버의 FCM 토큰을 사용 - firebaseNotificationService.notifyNewQuestion(question.getMember(), question); - return savedQuestion; - } - - @Transactional(readOnly = true) - public Page getQuestionsByMemberId(Long memberId, Pageable pageable) { - return questionRepository.findAllByMemberId(memberId, pageable); - } - - @Transactional(readOnly = true) - public Page getAnsweredQuestions(Long memberId, Pageable pageable) { - return questionRepository.findAllByMemberIdAndIsAnsweredTrue(memberId, pageable); - } - - @Transactional(readOnly = true) - public Page getUnansweredQuestions(Long memberId, Pageable pageable) { - return questionRepository.findAllByMemberIdAndIsAnsweredFalse(memberId, pageable); - } - - - - @Transactional - public Question updateQuestion(Long questionId, String content, Boolean isAnswered) { - Question questionEntity = questionRepository.findById(questionId) - .orElseThrow(() -> new BusinessException(QuestionError.NO_EXIST_QUESTION)); - questionEntity.updateContent(content); - Question updatedQuestion = questionRepository.save(questionEntity); - // 질문 업데이트 후 알림 - firebaseNotificationService.notifyNewQuestion(updatedQuestion.getMember(), updatedQuestion); - return updatedQuestion; - } - - @Transactional - public void deleteQuestion(Long questionId) { - Question questionEntity = questionRepository.findById(questionId) - .orElseThrow(() -> new BusinessException(QuestionError.NO_EXIST_QUESTION)); - questionRepository.delete(questionEntity); - } - -} \ No newline at end of file diff --git a/baebae-BE/src/main/java/com/web/baebaeBE/global/firebase/FirebaseNotificationService.java b/baebae-BE/src/main/java/com/web/baebaeBE/global/firebase/FirebaseNotificationService.java index 25994317..615eec23 100644 --- a/baebae-BE/src/main/java/com/web/baebaeBE/global/firebase/FirebaseNotificationService.java +++ b/baebae-BE/src/main/java/com/web/baebaeBE/global/firebase/FirebaseNotificationService.java @@ -1,8 +1,8 @@ package com.web.baebaeBE.global.firebase; import com.web.baebaeBE.domain.member.entity.Member; -import com.web.baebaeBE.infra.answer.entity.Answer; -import com.web.baebaeBE.infra.question.entity.Question; +import com.web.baebaeBE.domain.answer.entity.Answer; +import com.web.baebaeBE.domain.question.entity.Question; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; diff --git a/baebae-BE/src/main/java/com/web/baebaeBE/presentation/question/QuestionController.java b/baebae-BE/src/main/java/com/web/baebaeBE/presentation/question/QuestionController.java deleted file mode 100644 index aa7a64bf..00000000 --- a/baebae-BE/src/main/java/com/web/baebaeBE/presentation/question/QuestionController.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.web.baebaeBE.presentation.question; - -import com.web.baebaeBE.application.question.QuestionApplication; -import com.web.baebaeBE.presentation.question.api.QuestionApi; -import com.web.baebaeBE.presentation.question.dto.QuestionCreateRequest; -import com.web.baebaeBE.presentation.question.dto.QuestionDetailResponse; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import java.util.List; - -@RestController -@RequestMapping("/api/questions") -public class QuestionController implements QuestionApi { - private final QuestionApplication questionApplication; - - - @Autowired - public QuestionController(QuestionApplication questionApplication) { - this.questionApplication = questionApplication; - } - - @PostMapping("/member/{memberId}") - public ResponseEntity createQuestion( - @RequestBody QuestionCreateRequest questionDTO, @PathVariable Long memberId) { - - // 질문 생성 요청 - QuestionDetailResponse createdQuestion = questionApplication.createQuestion(questionDTO, memberId); - return ResponseEntity.status(HttpStatus.CREATED).body(createdQuestion); - } - - -// @GetMapping("/member/{memberId}") -// public ResponseEntity> getAllQuestions( -// @RequestParam Long memberId, Pageable pageable) { -// Page questions = questionApplication.getAllQuestions(memberId, pageable); -// return ResponseEntity.ok(questions.getContent()); -// } - - @GetMapping("/answered/{memberId}") - public ResponseEntity> getAnsweredQuestions( - @PathVariable Long memberId, Pageable pageable) { - Page questions = questionApplication.getAnsweredQuestions(memberId, pageable); - return ResponseEntity.ok(questions.getContent()); - } - - @GetMapping("/unanswered/{memberId}") - public ResponseEntity> getUnansweredQuestions( - @PathVariable Long memberId, Pageable pageable) { - Page questions = questionApplication.getUnansweredQuestions(memberId, pageable); - return ResponseEntity.ok(questions.getContent()); - } - - - @PutMapping("/{questionId}") - public ResponseEntity updateQuestion(@PathVariable Long questionId, @RequestParam String content, @RequestParam boolean isAnswered) { - questionApplication.updateQuestion(questionId, content, isAnswered); - return ResponseEntity.status(HttpStatus.NO_CONTENT).body(null); - } - - - @DeleteMapping("/{questionId}") - public ResponseEntity deleteQuestion(@PathVariable Long questionId) { - questionApplication.deleteQuestion(questionId); - return ResponseEntity.status(HttpStatus.NO_CONTENT).body(null); - } -} diff --git a/baebae-BE/src/main/resources/application.yml b/baebae-BE/src/main/resources/application.yml index 113a8a02..bfd568fb 100644 --- a/baebae-BE/src/main/resources/application.yml +++ b/baebae-BE/src/main/resources/application.yml @@ -5,3 +5,5 @@ spring: profiles: active: local +server: + url: http://localhost:8080 \ No newline at end of file diff --git a/baebae-BE/src/test/java/com/web/baebaeBE/integration/question/QuestionTest.java b/baebae-BE/src/test/java/com/web/baebaeBE/integration/question/QuestionTest.java index 7911c3cc..e004496a 100644 --- a/baebae-BE/src/test/java/com/web/baebaeBE/integration/question/QuestionTest.java +++ b/baebae-BE/src/test/java/com/web/baebaeBE/integration/question/QuestionTest.java @@ -5,13 +5,11 @@ import com.web.baebaeBE.domain.member.entity.Member; import com.web.baebaeBE.domain.member.entity.MemberType; import com.web.baebaeBE.domain.member.repository.MemberRepository; -import com.web.baebaeBE.infra.question.entity.Question; -import com.web.baebaeBE.infra.question.repository.QuestionRepository; -import com.web.baebaeBE.presentation.question.dto.QuestionCreateRequest; +import com.web.baebaeBE.domain.question.repository.QuestionRepository; +import com.web.baebaeBE.domain.question.dto.QuestionCreateRequest; import jakarta.transaction.Transactional; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; @@ -19,13 +17,12 @@ import org.springframework.http.MediaType; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; + import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.time.Duration; -import java.time.LocalDateTime; import java.util.Optional; @SpringBootTest()