From 2c2c56cf64248d25604a24dade316da1381e07bb Mon Sep 17 00:00:00 2001 From: Goathoon Date: Mon, 9 Oct 2023 00:09:23 +0900 Subject: [PATCH 01/12] =?UTF-8?q?=ED=94=84=EB=A1=9C=ED=95=84=20=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=EC=A7=80=20=EC=88=98=EC=A0=95=EC=8B=9C=20question,ans?= =?UTF-8?q?wer=EB=AA=A8=EB=91=90=EC=97=90=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Inforum/domain/member/service/MemberService.java | 8 +++++++- .../domain/post/qna/repository/AnswerRepository.java | 2 +- .../domain/post/qna/repository/AnswerRepositoryImpl.java | 9 +++++++++ .../domain/post/qna/repository/QuestionRepository.java | 1 + .../post/qna/repository/QuestionRepositoryImpl.java | 9 +++++++++ 5 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/main/java/Funssion/Inforum/domain/member/service/MemberService.java b/src/main/java/Funssion/Inforum/domain/member/service/MemberService.java index 8beabfb4..cf9443df 100644 --- a/src/main/java/Funssion/Inforum/domain/member/service/MemberService.java +++ b/src/main/java/Funssion/Inforum/domain/member/service/MemberService.java @@ -7,6 +7,7 @@ import Funssion.Inforum.domain.member.dto.request.MemberInfoDto; import Funssion.Inforum.domain.member.dto.request.MemberSaveDto; import Funssion.Inforum.domain.member.dto.request.NicknameRequestDto; +import Funssion.Inforum.domain.member.dto.request.PasswordUpdateDto; import Funssion.Inforum.domain.member.dto.response.*; import Funssion.Inforum.domain.member.entity.MemberProfileEntity; import Funssion.Inforum.domain.member.entity.NonSocialMember; @@ -14,8 +15,9 @@ import Funssion.Inforum.domain.member.repository.MemberRepository; import Funssion.Inforum.domain.mypage.repository.MyRepository; import Funssion.Inforum.domain.post.comment.repository.CommentRepository; -import Funssion.Inforum.domain.member.dto.request.PasswordUpdateDto; import Funssion.Inforum.domain.post.memo.repository.MemoRepository; +import Funssion.Inforum.domain.post.qna.repository.AnswerRepository; +import Funssion.Inforum.domain.post.qna.repository.QuestionRepository; import Funssion.Inforum.s3.S3Repository; import Funssion.Inforum.s3.S3Utils; import lombok.RequiredArgsConstructor; @@ -35,6 +37,8 @@ public class MemberService { private final MemberRepository memberRepository; private final MyRepository myRepository; private final MemoRepository memoRepository; + private final QuestionRepository questionRepository; + private final AnswerRepository answerRepository; private final CommentRepository commentRepository; private final S3Repository s3Repository; private final FollowRepository followRepository; @@ -180,6 +184,8 @@ private IsProfileSavedDto updateProfile(Long userId, MemberProfileEntity memberP memoRepository.updateAuthorProfile(userId, memberProfileEntity.getProfileImageFilePath()); commentRepository.updateProfileImageOfComment(userId, memberProfileEntity.getProfileImageFilePath()); commentRepository.updateProfileImageOfReComment(userId,memberProfileEntity.getProfileImageFilePath() ); + questionRepository.updateProfileImage(userId,memberProfileEntity.getProfileImageFilePath()); + answerRepository.updateProfileImage(userId,memberProfileEntity.getProfileImageFilePath()); return myRepository.updateProfile(userId, memberProfileEntity); } diff --git a/src/main/java/Funssion/Inforum/domain/post/qna/repository/AnswerRepository.java b/src/main/java/Funssion/Inforum/domain/post/qna/repository/AnswerRepository.java index 4e0c105a..367077bd 100644 --- a/src/main/java/Funssion/Inforum/domain/post/qna/repository/AnswerRepository.java +++ b/src/main/java/Funssion/Inforum/domain/post/qna/repository/AnswerRepository.java @@ -19,6 +19,6 @@ public interface AnswerRepository { Answer updateLikesInAnswer(Long likes, Long answerId); Answer updateDisLikesInAnswer(Long disLikes, Long answerId); void deleteAnswer(Long answerId); - Answer select(Long answerId); + void updateProfileImage(Long userId, String profileImageFilePath); } diff --git a/src/main/java/Funssion/Inforum/domain/post/qna/repository/AnswerRepositoryImpl.java b/src/main/java/Funssion/Inforum/domain/post/qna/repository/AnswerRepositoryImpl.java index 7edd3082..4424108c 100644 --- a/src/main/java/Funssion/Inforum/domain/post/qna/repository/AnswerRepositoryImpl.java +++ b/src/main/java/Funssion/Inforum/domain/post/qna/repository/AnswerRepositoryImpl.java @@ -136,6 +136,15 @@ public Answer select(Long answerId) { return getAnswerById(answerId); } + @Override + public void updateProfileImage(Long userId, String profileImageFilePath) { + String sql = "update question.answer " + + "set author_image_path = ? " + + "where author_id = ?"; + + template.update(sql, profileImageFilePath, userId); + } + private RowMapper answerRowMapper() { return ((rs, rowNum) -> Answer.builder() diff --git a/src/main/java/Funssion/Inforum/domain/post/qna/repository/QuestionRepository.java b/src/main/java/Funssion/Inforum/domain/post/qna/repository/QuestionRepository.java index 0ae6f2ac..54a604e2 100644 --- a/src/main/java/Funssion/Inforum/domain/post/qna/repository/QuestionRepository.java +++ b/src/main/java/Funssion/Inforum/domain/post/qna/repository/QuestionRepository.java @@ -23,4 +23,5 @@ public interface QuestionRepository { List getQuestionsOfMemo(Long userId, Long memoId); + void updateProfileImage(Long userId, String profileImageFilePath); } diff --git a/src/main/java/Funssion/Inforum/domain/post/qna/repository/QuestionRepositoryImpl.java b/src/main/java/Funssion/Inforum/domain/post/qna/repository/QuestionRepositoryImpl.java index c1a6b76a..5089288a 100644 --- a/src/main/java/Funssion/Inforum/domain/post/qna/repository/QuestionRepositoryImpl.java +++ b/src/main/java/Funssion/Inforum/domain/post/qna/repository/QuestionRepositoryImpl.java @@ -99,6 +99,15 @@ public List getQuestionsOfMemo(Long userId,Long memoId) { return template.query(sql,questionLikeRowMapper(),memoId, userId); } + @Override + public void updateProfileImage(Long userId, String profileImageFilePath) { + String sql = "update question.info " + + "set author_image_path = ? " + + "where author_id = ?"; + + template.update(sql, profileImageFilePath, userId); + } + @Override public Question getOneQuestion(Long questionId) { String sql = "select id, author_id, author_name, author_image_path, title, text, description, likes, is_solved, created_date, updated_date, tags, replies_count, answers, is_solved, memo_id " + From 66ac32cef17458a17f70292d2d548e68ccef8ece Mon Sep 17 00:00:00 2001 From: Goathoon Date: Mon, 9 Oct 2023 00:23:19 +0900 Subject: [PATCH 02/12] =?UTF-8?q?fix:=20history=EC=97=90=EB=8F=84=20?= =?UTF-8?q?=EB=8B=B5=EB=B3=80=20=EA=B8=B0=EB=A1=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/mypage/dto/MyRecordNumDto.java | 4 +--- .../post/qna/service/QnAIntegrationTest.java | 22 +++++++++---------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/main/java/Funssion/Inforum/domain/mypage/dto/MyRecordNumDto.java b/src/main/java/Funssion/Inforum/domain/mypage/dto/MyRecordNumDto.java index b2261485..5ba4aa48 100644 --- a/src/main/java/Funssion/Inforum/domain/mypage/dto/MyRecordNumDto.java +++ b/src/main/java/Funssion/Inforum/domain/mypage/dto/MyRecordNumDto.java @@ -4,8 +4,6 @@ import lombok.Getter; import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.Date; @Getter public class MyRecordNumDto { @@ -16,6 +14,6 @@ public class MyRecordNumDto { public MyRecordNumDto(History history) { this.historyId = history.getId(); this.date = history.getDate(); - this.postCnt = history.getMemoCnt() + history.getBlogCnt() + history.getQuestionCnt(); + this.postCnt = history.getMemoCnt() + history.getBlogCnt() + history.getQuestionCnt() + history.getAnswerCnt(); } } diff --git a/src/test/java/Funssion/Inforum/domain/post/qna/service/QnAIntegrationTest.java b/src/test/java/Funssion/Inforum/domain/post/qna/service/QnAIntegrationTest.java index 28ed90ba..3dfe66f2 100644 --- a/src/test/java/Funssion/Inforum/domain/post/qna/service/QnAIntegrationTest.java +++ b/src/test/java/Funssion/Inforum/domain/post/qna/service/QnAIntegrationTest.java @@ -355,17 +355,17 @@ private void saveQuestions(){ questionService.createQuestion(secondQuestionSaveDto, saveMemberId, Long.valueOf(Constant.NONE_MEMO_QUESTION)); questionService.createQuestion(thirdQuestionSaveDto, saveMemberId, Long.valueOf(Constant.NONE_MEMO_QUESTION)); } - @Test - @DisplayName("최신순으로 정렬") - void getLatest(){ - saveQuestions(); - List latestQuestionList = questionService.getQuestions(saveMemberId,OrderType.NEW); - assertThat(latestQuestionList).hasSize(3); - List questionTitleList = latestQuestionList.stream().map(question -> { - return question.getTitle(); - }).collect(Collectors.toList()); - assertThat(questionTitleList).containsExactly(firstQuestionSaveDto.getTitle(),secondQuestionSaveDto.getTitle(),thirdQuestionSaveDto.getTitle()); - } +// @Test +// @DisplayName("최신순으로 정렬") +// void getLatest(){ +// saveQuestions(); +// List latestQuestionList = questionService.getQuestions(saveMemberId,OrderType.NEW); +// assertThat(latestQuestionList).hasSize(3); +// List questionTitleList = latestQuestionList.stream().map(question -> { +// return question.getTitle(); +// }).collect(Collectors.toList()); +// assertThat(questionTitleList).containsExactly(firstQuestionSaveDto.getTitle(),secondQuestionSaveDto.getTitle(),thirdQuestionSaveDto.getTitle()); +// } @Test @DisplayName("인기순으로 정렬") void getHottest(){ From 85be942b40c7eb98495852620044a910de578688 Mon Sep 17 00:00:00 2001 From: comolove Date: Mon, 9 Oct 2023 12:29:22 +0900 Subject: [PATCH 03/12] chore: change property in history dto --- .../Inforum/domain/mypage/dto/MyRecordNumDto.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/Funssion/Inforum/domain/mypage/dto/MyRecordNumDto.java b/src/main/java/Funssion/Inforum/domain/mypage/dto/MyRecordNumDto.java index 5ba4aa48..2b40143f 100644 --- a/src/main/java/Funssion/Inforum/domain/mypage/dto/MyRecordNumDto.java +++ b/src/main/java/Funssion/Inforum/domain/mypage/dto/MyRecordNumDto.java @@ -9,11 +9,17 @@ public class MyRecordNumDto { private final Long historyId; private final LocalDate date; - private final Long postCnt; + private final Long memoCnt; + private final Long blogCnt; + private final Long questionCnt; + private final Long answerCnt; public MyRecordNumDto(History history) { this.historyId = history.getId(); this.date = history.getDate(); - this.postCnt = history.getMemoCnt() + history.getBlogCnt() + history.getQuestionCnt() + history.getAnswerCnt(); + this.memoCnt = history.getMemoCnt(); + this.blogCnt = history.getBlogCnt(); + this.questionCnt = history.getQuestionCnt(); + this.answerCnt = history.getAnswerCnt(); } } From c4ca93e203b9d543f16ccfa580b9a7872ad53580 Mon Sep 17 00:00:00 2001 From: Goathoon Date: Mon, 9 Oct 2023 17:12:30 +0900 Subject: [PATCH 04/12] =?UTF-8?q?chore:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=EB=A9=94=EC=84=B8=EC=A7=80=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/Funssion/Inforum/jwt/NonSocialLoginFailureHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/Funssion/Inforum/jwt/NonSocialLoginFailureHandler.java b/src/main/java/Funssion/Inforum/jwt/NonSocialLoginFailureHandler.java index 4a0a234f..018e930e 100644 --- a/src/main/java/Funssion/Inforum/jwt/NonSocialLoginFailureHandler.java +++ b/src/main/java/Funssion/Inforum/jwt/NonSocialLoginFailureHandler.java @@ -28,7 +28,7 @@ private void makeFailureResponseBody(HttpServletResponse response) throws IOExce private String convertFailureObjectToString() throws JsonProcessingException { ObjectMapper objectMapper = new ObjectMapper(); - IsSuccessResponseDto isSuccessResponseDto = new IsSuccessResponseDto(false, "로그인에 실패하였습니다."); + IsSuccessResponseDto isSuccessResponseDto = new IsSuccessResponseDto(false, "아이디 또는 비밀번호를 잘못 입력하였습니다."); String successResponse = objectMapper.writeValueAsString(isSuccessResponseDto); return successResponse; } From 2de83e4fce2340b8cb41ede0d8940eb78bd730bd Mon Sep 17 00:00:00 2001 From: Goathoon Date: Mon, 9 Oct 2023 21:51:13 +0900 Subject: [PATCH 05/12] =?UTF-8?q?fix:=20exception=20handler=20=EC=97=90=20?= =?UTF-8?q?psql=20exception=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/exception/handler/CustomExceptionHandler.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/Funssion/Inforum/common/exception/handler/CustomExceptionHandler.java b/src/main/java/Funssion/Inforum/common/exception/handler/CustomExceptionHandler.java index 893b9679..da9152c1 100644 --- a/src/main/java/Funssion/Inforum/common/exception/handler/CustomExceptionHandler.java +++ b/src/main/java/Funssion/Inforum/common/exception/handler/CustomExceptionHandler.java @@ -8,6 +8,7 @@ import Funssion.Inforum.common.exception.response.ErrorResult; import jakarta.validation.ValidationException; import lombok.extern.slf4j.Slf4j; +import org.postgresql.util.PSQLException; import org.springframework.beans.TypeMismatchException; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.validation.FieldError; @@ -67,6 +68,7 @@ public Map> handleValidationEx(MethodArgumentNotValidExcept public ErrorResult handleDataIntegrityViolationOfJSONB(DataIntegrityViolationException e){ return new ErrorResult(BAD_REQUEST,e.getMessage()); } + @ResponseStatus(BAD_REQUEST) @ExceptionHandler(TypeMismatchException.class) public ErrorResult handleTypeMismatchEx (TypeMismatchException e) { @@ -101,6 +103,11 @@ public ErrorResult handleDuplicateEx(DuplicateException e){ return e.getErrorResult(); } + @ResponseStatus(BAD_REQUEST) + @ExceptionHandler(PSQLException.class) + public ErrorResult handlePSQLException(PSQLException e){ + return new ErrorResult(BAD_REQUEST, "DB exception occurred"); + } @ResponseStatus(INTERNAL_SERVER_ERROR) @ExceptionHandler(Throwable.class) public ErrorResult handleGeneralEx(Throwable e) { From 12d07874beb21ee41eda7ef373d410de8b73759a Mon Sep 17 00:00:00 2001 From: Goathoon Date: Mon, 9 Oct 2023 22:07:29 +0900 Subject: [PATCH 06/12] =?UTF-8?q?fix:=20=EC=9E=90=EA=B8=B0=EC=86=8C?= =?UTF-8?q?=EA=B0=9C=20introduction=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20?= =?UTF-8?q?=EA=B2=80=EC=82=AC=20=EC=97=90=EB=9F=AC=EC=B2=98=EB=A6=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/etc/ValueTooLongException.java | 15 ++++++ .../handler/CustomExceptionHandler.java | 7 +++ .../mypage/repository/MyRepositoryJdbc.java | 47 ++++++++++++------- 3 files changed, 52 insertions(+), 17 deletions(-) create mode 100644 src/main/java/Funssion/Inforum/common/exception/etc/ValueTooLongException.java diff --git a/src/main/java/Funssion/Inforum/common/exception/etc/ValueTooLongException.java b/src/main/java/Funssion/Inforum/common/exception/etc/ValueTooLongException.java new file mode 100644 index 00000000..ae9a4152 --- /dev/null +++ b/src/main/java/Funssion/Inforum/common/exception/etc/ValueTooLongException.java @@ -0,0 +1,15 @@ +package Funssion.Inforum.common.exception.etc; + +import Funssion.Inforum.common.exception.response.ErrorResult; +import org.springframework.http.HttpStatus; + +public class ValueTooLongException extends RuntimeException{ + private ErrorResult errorResult; + private String message; + + public ValueTooLongException(String message) { + this.message = message; + this.errorResult = new ErrorResult(HttpStatus.INTERNAL_SERVER_ERROR, message); + } + +} diff --git a/src/main/java/Funssion/Inforum/common/exception/handler/CustomExceptionHandler.java b/src/main/java/Funssion/Inforum/common/exception/handler/CustomExceptionHandler.java index da9152c1..5998b924 100644 --- a/src/main/java/Funssion/Inforum/common/exception/handler/CustomExceptionHandler.java +++ b/src/main/java/Funssion/Inforum/common/exception/handler/CustomExceptionHandler.java @@ -4,6 +4,7 @@ import Funssion.Inforum.common.exception.etc.DuplicateException; import Funssion.Inforum.common.exception.etc.ImageIOException; import Funssion.Inforum.common.exception.etc.UnAuthorizedException; +import Funssion.Inforum.common.exception.etc.ValueTooLongException; import Funssion.Inforum.common.exception.notfound.NotFoundException; import Funssion.Inforum.common.exception.response.ErrorResult; import jakarta.validation.ValidationException; @@ -69,6 +70,12 @@ public ErrorResult handleDataIntegrityViolationOfJSONB(DataIntegrityViolationExc return new ErrorResult(BAD_REQUEST,e.getMessage()); } + @ResponseStatus(BAD_REQUEST) + @ExceptionHandler(ValueTooLongException.class) + public ErrorResult handleDataIntegrityViolationOfJSONB(ValueTooLongException e){ + return new ErrorResult(BAD_REQUEST,e.getMessage()); + } + @ResponseStatus(BAD_REQUEST) @ExceptionHandler(TypeMismatchException.class) public ErrorResult handleTypeMismatchEx (TypeMismatchException e) { diff --git a/src/main/java/Funssion/Inforum/domain/mypage/repository/MyRepositoryJdbc.java b/src/main/java/Funssion/Inforum/domain/mypage/repository/MyRepositoryJdbc.java index 0fcb8e8b..24700ddd 100644 --- a/src/main/java/Funssion/Inforum/domain/mypage/repository/MyRepositoryJdbc.java +++ b/src/main/java/Funssion/Inforum/domain/mypage/repository/MyRepositoryJdbc.java @@ -3,6 +3,7 @@ import Funssion.Inforum.common.constant.PostType; import Funssion.Inforum.common.constant.Sign; import Funssion.Inforum.common.exception.etc.ArrayToListException; +import Funssion.Inforum.common.exception.etc.ValueTooLongException; import Funssion.Inforum.common.exception.notfound.NotFoundException; import Funssion.Inforum.domain.member.dto.response.IsProfileSavedDto; import Funssion.Inforum.domain.member.entity.MemberProfileEntity; @@ -109,23 +110,9 @@ private IsProfileSavedDto patchMemberProfile(Long userId, MemberProfileEntity me StringBuilder sqlBuilder = new StringBuilder("UPDATE member.info SET "); List params = new ArrayList<>(); - if (memberProfileEntity.getIntroduce() != null) { - sqlBuilder.append("introduce = ?, "); - params.add(memberProfileEntity.getIntroduce()); - } - - if (memberProfileEntity.getUserTags() != null) { - sqlBuilder.append("tags = ?, "); - try { - params.add(TagUtils.createSqlArray(template,memberProfileEntity.getUserTags())); - } catch (SQLException e) { - throw new ArrayToListException("Javax.sql.Array (PostgreSQL의 array) 를 List로 변경할 때의 오류",e); - } - } - - sqlBuilder.append("image_path = ?, "); - params.add(memberProfileEntity.getProfileImageFilePath()); - + appendIntroductionSql(memberProfileEntity, sqlBuilder, params); + appendTagsSql(memberProfileEntity, sqlBuilder, params); + appendImagePathSql(memberProfileEntity, sqlBuilder, params); // parameter 추가시 sql 뒤에 ", " 삭제 if (params.size() > 0) { @@ -151,6 +138,32 @@ private IsProfileSavedDto patchMemberProfile(Long userId, MemberProfileEntity me ); } + private static void appendImagePathSql(MemberProfileEntity memberProfileEntity, StringBuilder sqlBuilder, List params) { + sqlBuilder.append("image_path = ?, "); + params.add(memberProfileEntity.getProfileImageFilePath()); + } + + private void appendTagsSql(MemberProfileEntity memberProfileEntity, StringBuilder sqlBuilder, List params) { + if (memberProfileEntity.getUserTags() != null) { + sqlBuilder.append("tags = ?, "); + try { + params.add(TagUtils.createSqlArray(template, memberProfileEntity.getUserTags())); + } catch (SQLException e) { + throw new ArrayToListException("Javax.sql.Array (PostgreSQL의 array) 를 List로 변경할 때의 오류",e); + } + } + } + + private static void appendIntroductionSql(MemberProfileEntity memberProfileEntity, StringBuilder sqlBuilder, List params) { + if (memberProfileEntity.getIntroduce() != null) { + if (memberProfileEntity.getIntroduce().length() >=300){ + throw new ValueTooLongException("자기소개는 300자 이내로 작성해주세요."); + } + sqlBuilder.append("introduce = ?, "); + params.add(memberProfileEntity.getIntroduce()); + } + } + @Override public String findProfileImageNameById(Long userId) { String sql = "select image_path from member.info where id =?"; From 64550806ef1464fd7bbf933f51c182ebff456b5a Mon Sep 17 00:00:00 2001 From: Goathoon Date: Mon, 9 Oct 2023 22:46:01 +0900 Subject: [PATCH 07/12] =?UTF-8?q?feat:=20=EB=82=B4=EA=B0=80=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D=ED=95=9C=20=EC=A7=88=EB=AC=B8=EB=93=A4=20=EA=B0=80?= =?UTF-8?q?=EC=A0=B8=EC=98=A4=EB=8A=94=20api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mypage/controller/MyController.java | 11 +++-- .../domain/mypage/service/MyService.java | 17 ++++--- .../qna/repository/QuestionRepository.java | 2 + .../repository/QuestionRepositoryImpl.java | 9 ++++ .../post/qna/service/QnAIntegrationTest.java | 47 ++++++++++++++----- 5 files changed, 63 insertions(+), 23 deletions(-) diff --git a/src/main/java/Funssion/Inforum/domain/mypage/controller/MyController.java b/src/main/java/Funssion/Inforum/domain/mypage/controller/MyController.java index 9a59d578..20abee77 100644 --- a/src/main/java/Funssion/Inforum/domain/mypage/controller/MyController.java +++ b/src/main/java/Funssion/Inforum/domain/mypage/controller/MyController.java @@ -1,16 +1,14 @@ package Funssion.Inforum.domain.mypage.controller; -import Funssion.Inforum.domain.post.memo.dto.response.MemoListDto; import Funssion.Inforum.domain.mypage.dto.MyRecordNumDto; import Funssion.Inforum.domain.mypage.dto.MyUserInfoDto; -import Funssion.Inforum.domain.mypage.exception.HistoryNotFoundException; import Funssion.Inforum.domain.mypage.service.MyService; +import Funssion.Inforum.domain.post.memo.dto.response.MemoListDto; +import Funssion.Inforum.domain.post.qna.domain.Question; import jakarta.validation.constraints.Min; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.hibernate.validator.constraints.Range; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -43,6 +41,11 @@ public List getMyLikedMemos(@PathVariable @Min(1) Long userId) { @GetMapping("/memos/drafts") public List getMyDraftMemos(@PathVariable @Min(1) Long userId) {return myService.getMyDraftMemos(userId);} + @GetMapping("/questions") + public List getMyQuestions(@PathVariable @Min(1) Long userId ){ + return myService.getMyQuestions(userId); + } + @GetMapping("/history") public List getHistory( @PathVariable @Min(1) Long userId, diff --git a/src/main/java/Funssion/Inforum/domain/mypage/service/MyService.java b/src/main/java/Funssion/Inforum/domain/mypage/service/MyService.java index e6ba948a..3101c9e5 100644 --- a/src/main/java/Funssion/Inforum/domain/mypage/service/MyService.java +++ b/src/main/java/Funssion/Inforum/domain/mypage/service/MyService.java @@ -1,13 +1,14 @@ package Funssion.Inforum.domain.mypage.service; +import Funssion.Inforum.common.constant.OrderType; import Funssion.Inforum.domain.member.repository.MemberRepository; -import Funssion.Inforum.domain.post.like.repository.LikeRepository; -import Funssion.Inforum.domain.member.repository.MemberRepositoryImpl; -import Funssion.Inforum.domain.post.memo.dto.response.MemoListDto; -import Funssion.Inforum.domain.post.memo.repository.MemoRepository; import Funssion.Inforum.domain.mypage.dto.MyRecordNumDto; -import Funssion.Inforum.domain.mypage.repository.MyRepository; import Funssion.Inforum.domain.mypage.dto.MyUserInfoDto; +import Funssion.Inforum.domain.mypage.repository.MyRepository; +import Funssion.Inforum.domain.post.memo.dto.response.MemoListDto; +import Funssion.Inforum.domain.post.memo.repository.MemoRepository; +import Funssion.Inforum.domain.post.qna.domain.Question; +import Funssion.Inforum.domain.post.qna.repository.QuestionRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -20,7 +21,7 @@ public class MyService { private final MyRepository myRepository; private final MemoRepository memoRepository; private final MemberRepository memberRepository; - + private final QuestionRepository questionRepository; public MyUserInfoDto getUserInfo(Long userId) { return MyUserInfoDto.builder() .userName(memberRepository.findNameById(userId)) @@ -53,4 +54,8 @@ public List getMyDraftMemos(Long userId) { .map(MemoListDto::new) .toList(); } + + public List getMyQuestions(Long userId) { + return questionRepository.getMyQuestions(userId, OrderType.NEW); + } } diff --git a/src/main/java/Funssion/Inforum/domain/post/qna/repository/QuestionRepository.java b/src/main/java/Funssion/Inforum/domain/post/qna/repository/QuestionRepository.java index 0ae6f2ac..4f5a06e2 100644 --- a/src/main/java/Funssion/Inforum/domain/post/qna/repository/QuestionRepository.java +++ b/src/main/java/Funssion/Inforum/domain/post/qna/repository/QuestionRepository.java @@ -12,6 +12,8 @@ public interface QuestionRepository { Question updateQuestion(QuestionSaveDto questionSaveDto, Long questionId); List getQuestions(Long userId, OrderType orderBy); + List getMyQuestions(Long userId, OrderType orderBy); + Long getAuthorId(Long questionId); diff --git a/src/main/java/Funssion/Inforum/domain/post/qna/repository/QuestionRepositoryImpl.java b/src/main/java/Funssion/Inforum/domain/post/qna/repository/QuestionRepositoryImpl.java index c1a6b76a..616129da 100644 --- a/src/main/java/Funssion/Inforum/domain/post/qna/repository/QuestionRepositoryImpl.java +++ b/src/main/java/Funssion/Inforum/domain/post/qna/repository/QuestionRepositoryImpl.java @@ -77,6 +77,15 @@ public List getQuestions(Long userId,OrderType orderBy) { return template.query(sql, questionLikeRowMapper(),userId); } + @Override + public List getMyQuestions(Long userId,OrderType orderBy) { + String sql = + "select Q.id, Q.author_id, Q.author_name, Q.author_image_path, Q.title, Q.text, Q.description, Q.likes, Q.is_solved, Q.created_date, Q.updated_date, Q.tags, Q.replies_count, Q.answers, Q.is_solved, Q.memo_id "+ + "from question.info AS Q "+ + "where Q.author_id = ? "; + return template.query(sql, questionRowMapper(),userId); + } + @Override public Long getAuthorId(Long questionId) { String sql = "select author_id from question.info where id = ?"; diff --git a/src/test/java/Funssion/Inforum/domain/post/qna/service/QnAIntegrationTest.java b/src/test/java/Funssion/Inforum/domain/post/qna/service/QnAIntegrationTest.java index 28ed90ba..5c26f8b2 100644 --- a/src/test/java/Funssion/Inforum/domain/post/qna/service/QnAIntegrationTest.java +++ b/src/test/java/Funssion/Inforum/domain/post/qna/service/QnAIntegrationTest.java @@ -219,6 +219,27 @@ void createQuestion(){ } + @Test + @DisplayName("자신이 질문한 것들만 가져오기") + @Transactional + void getMyQuestion(){ + makePureQuestion(); + makePureQuestion(); + makeQuestionOfOtherAuthor(); + + List myQuestions = questionRepository.getMyQuestions(saveMemberId, OrderType.NEW); + assertThat(myQuestions).hasSize(2); + } + + private void makeQuestionOfOtherAuthor() { + Long questionAuthorId = createUniqueAuthor(); + QuestionSaveDto questionSaveDto = QuestionSaveDto.builder().title("테스트 제목 생성") + .text("{\"type\": \"doc\", \"content\": [{\"type\": \"paragraph\", \"content\": [{\"text\": \"질문 내용\", \"type\": \"text\"}]}]}") + .tags(List.of("tag1", "tag2")) + .build(); + questionService.createQuestion(questionSaveDto, questionAuthorId, Long.valueOf(Constant.NONE_MEMO_QUESTION)); + } + private Question makePureQuestion() { QuestionSaveDto questionSaveDto = QuestionSaveDto.builder().title("테스트 제목 생성") @@ -384,7 +405,7 @@ void getAnswerListOfQuestion(){ Question question1 = makePureQuestion(); Question question2 = makePureQuestion(); - Long answerAuthorId = createAuthorOfAnswer(); + Long answerAuthorId = createUniqueAuthor(); makeAnswerOfQuestion(answerAuthorId,List.of(question1,question2)); @@ -400,7 +421,7 @@ void getAnswerListOfQuestion(){ void getOrderedAnswerListOfQuestion(){ Question question = makePureQuestion(); - Long answerAuthorId = createAuthorOfAnswer(); + Long answerAuthorId = createUniqueAuthor(); AnswerSaveDto answerSaveDto = AnswerSaveDto.builder() .text("{\"type\": \"doc\", \"content\": [{\"type\": \"paragraph\", \"content\": [{\"text\": \"답변 내용\", \"type\": \"text\"}]}]}") .description("답변 요약") @@ -427,7 +448,7 @@ void getOrderedAnswerListOfQuestion(){ @DisplayName("고유 id로 답변 하나만 가져오기") void getAnswerOfQuestion(){ Question question = makePureQuestion(); - Long answerAuthorId = createAuthorOfAnswer(); + Long answerAuthorId = createUniqueAuthor(); AnswerSaveDto answerSaveDto = AnswerSaveDto.builder() .text("{\"type\": \"doc\", \"content\": [{\"type\": \"paragraph\", \"content\": [{\"text\": \"답변 내용\", \"type\": \"text\"}]}]}") @@ -510,7 +531,7 @@ class updateAnswer{ void updateAnswer(){ Question question = makePureQuestion(); - Long answerAuthorId = createAuthorOfAnswer(); + Long answerAuthorId = createUniqueAuthor(); AnswerSaveDto answerSaveDto = AnswerSaveDto.builder() .text("{\"type\": \"doc\", \"content\": [{\"type\": \"paragraph\", \"content\": [{\"text\": \"답변 내용\", \"type\": \"text\"}]}]}") @@ -531,7 +552,7 @@ class deleteAnswer{ void deleteAnswer(){ Question question = makePureQuestion(); - Long answerAuthorId = createAuthorOfAnswer(); + Long answerAuthorId = createUniqueAuthor(); AnswerSaveDto answerSaveDto = AnswerSaveDto.builder() .text("{\"type\": \"doc\", \"content\": [{\"type\": \"paragraph\", \"content\": [{\"text\": \"답변 내용\", \"type\": \"text\"}]}]}") @@ -561,7 +582,7 @@ class selectAnswer{ void selectAnswer(){ Question question = makePureQuestion(); - Long answerAuthorId = createAuthorOfAnswer(); + Long answerAuthorId = createUniqueAuthor(); AnswerSaveDto answerSaveDto = AnswerSaveDto.builder() .text("{\"type\": \"doc\", \"content\": [{\"type\": \"paragraph\", \"content\": [{\"text\": \"답변 내용\", \"type\": \"text\"}]}]}") @@ -588,7 +609,7 @@ void selectAnswer(){ void selectDuplicateAnswer(){ Question question = makePureQuestion(); - Long answerAuthorId = createAuthorOfAnswer(); + Long answerAuthorId = createUniqueAuthor(); AnswerSaveDto answerSaveDto = AnswerSaveDto.builder() .text("{\"type\": \"doc\", \"content\": [{\"type\": \"paragraph\", \"content\": [{\"text\": \"답변 내용\", \"type\": \"text\"}]}]}") @@ -606,7 +627,7 @@ void selectDuplicateAnswer(){ void AuthorOfAnswerSelectOwnAnswer(){ Question question = makePureQuestion(); - Long answerAuthorId = createAuthorOfAnswer(); + Long answerAuthorId = createUniqueAuthor(); AnswerSaveDto answerSaveDto = AnswerSaveDto.builder() .text("{\"type\": \"doc\", \"content\": [{\"type\": \"paragraph\", \"content\": [{\"text\": \"답변 내용\", \"type\": \"text\"}]}]}") @@ -642,7 +663,7 @@ void deleteQuestionWhenItHasAnswer(){ Question question = questionService.createQuestion(questionSaveDto, saveMemberId, Long.valueOf(Constant.NONE_MEMO_QUESTION)); - Long answerAuthorId = createAuthorOfAnswer(); + Long answerAuthorId = createUniqueAuthor(); AnswerSaveDto answerSaveDto = AnswerSaveDto.builder() .text("{\"type\": \"doc\", \"content\": [{\"type\": \"paragraph\", \"content\": [{\"text\": \"답변 내용\", \"type\": \"text\"}]}]}") @@ -655,7 +676,7 @@ void deleteQuestionWhenItHasAnswer(){ } } - private Long createAuthorOfAnswer(){ + private Long createUniqueAuthor(){ MemberSaveDto memberSaveDto = MemberSaveDto.builder() .userName("answer_user") .loginType(LoginType.NON_SOCIAL) @@ -671,10 +692,10 @@ private Long createAuthorOfAnswer(){ SaveMemberResponseDto saveMemberResponseDto = memberRepository.save(NonSocialMember.createNonSocialMember(memberSaveDto)); - Long answerAuthorId = saveMemberResponseDto.getId(); - myRepository.createProfile(answerAuthorId, memberProfileEntity); + Long authorId = saveMemberResponseDto.getId(); + myRepository.createProfile(authorId, memberProfileEntity); - return answerAuthorId; + return authorId; } } \ No newline at end of file From 254e2d2635f72511e79a1e2c7af031fcab1002b9 Mon Sep 17 00:00:00 2001 From: Goathoon Date: Tue, 10 Oct 2023 01:09:00 +0900 Subject: [PATCH 08/12] =?UTF-8?q?feat:=20my=20page=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=A7=88=EB=AC=B8=EB=8B=B5=EB=B3=80=EC=BB=A8=ED=85=90=EC=B8=A0?= =?UTF-8?q?=20api=EC=B6=94=EA=B0=80=201.=20=EB=82=B4=EA=B0=80=20=EC=A2=8B?= =?UTF-8?q?=EC=95=84=EC=9A=94=ED=95=9C=20=EC=A7=88=EB=AC=B8=EB=93=A4=202.?= =?UTF-8?q?=20=EB=82=B4=EA=B0=80=20=EC=A2=8B=EC=95=84=EC=9A=94=ED=95=9C=20?= =?UTF-8?q?=EB=8B=B5=EB=B3=80=EC=9D=B4=20=EB=8B=AC=EB=A6=B0=20=EC=A7=88?= =?UTF-8?q?=EB=AC=B8=EB=93=A4=203.=20=EB=82=B4=EA=B0=80=20=EC=93=B4=20?= =?UTF-8?q?=EC=A7=88=EB=AC=B8=EB=93=A4=204.=20=EB=82=B4=EA=B0=80=20?= =?UTF-8?q?=EB=8B=B5=EB=B3=80=ED=95=9C=20=EC=A7=88=EB=AC=B8=EB=93=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/controller/MemberController.java | 3 - .../mypage/controller/MyController.java | 13 ++++ .../domain/mypage/service/MyService.java | 12 ++++ .../domain/post/like/service/LikeService.java | 1 - .../qna/repository/QuestionRepository.java | 5 ++ .../repository/QuestionRepositoryImpl.java | 35 +++++++++ .../post/qna/service/QnAIntegrationTest.java | 72 +++++++++++++++++-- 7 files changed, 131 insertions(+), 10 deletions(-) diff --git a/src/main/java/Funssion/Inforum/domain/member/controller/MemberController.java b/src/main/java/Funssion/Inforum/domain/member/controller/MemberController.java index 819b76d6..fc45e749 100644 --- a/src/main/java/Funssion/Inforum/domain/member/controller/MemberController.java +++ b/src/main/java/Funssion/Inforum/domain/member/controller/MemberController.java @@ -6,10 +6,8 @@ import Funssion.Inforum.common.exception.notfound.NotFoundException; import Funssion.Inforum.domain.member.dto.request.*; import Funssion.Inforum.domain.member.dto.response.*; -import Funssion.Inforum.domain.member.entity.MemberProfileEntity; import Funssion.Inforum.domain.member.service.MailService; import Funssion.Inforum.domain.member.service.MemberService; -import Funssion.Inforum.domain.member.dto.request.PasswordUpdateDto; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -89,7 +87,6 @@ public IsSuccessResponseDto registerName(@PathVariable("id") String userId,@Requ @GetMapping("/check") public ValidMemberDto method(@CurrentSecurityContext SecurityContext context) { String userId = context.getAuthentication().getName(); - log.info("user id = {}",userId); Long loginId = userId.equals("anonymousUser") ? -1L : Long.valueOf(userId); boolean isLogin = !userId.equals("anonymousUser"); return new ValidMemberDto(loginId, isLogin); diff --git a/src/main/java/Funssion/Inforum/domain/mypage/controller/MyController.java b/src/main/java/Funssion/Inforum/domain/mypage/controller/MyController.java index 20abee77..ecc657d8 100644 --- a/src/main/java/Funssion/Inforum/domain/mypage/controller/MyController.java +++ b/src/main/java/Funssion/Inforum/domain/mypage/controller/MyController.java @@ -45,7 +45,20 @@ public List getMyLikedMemos(@PathVariable @Min(1) Long userId) { public List getMyQuestions(@PathVariable @Min(1) Long userId ){ return myService.getMyQuestions(userId); } + @GetMapping("/questions/liked") + public List getMyLikedQuestions(@PathVariable @Min(1) Long userId ){ + return myService.getMyLikedQuestions(userId); + } + + @GetMapping("/questions/answered") + public List getQuestionsOfMyAnswer(@PathVariable @Min(1) Long userId ){ + return myService.getQuestionsOfMyAnswer(userId); + } + @GetMapping("/questions/answer/liked") + public List getQuestionsOfMyLikedAnswer(@PathVariable @Min(1) Long userId){ + return myService.getQuestionsOfMyLikedAnswer(userId); + } @GetMapping("/history") public List getHistory( @PathVariable @Min(1) Long userId, diff --git a/src/main/java/Funssion/Inforum/domain/mypage/service/MyService.java b/src/main/java/Funssion/Inforum/domain/mypage/service/MyService.java index 3101c9e5..26f0cb60 100644 --- a/src/main/java/Funssion/Inforum/domain/mypage/service/MyService.java +++ b/src/main/java/Funssion/Inforum/domain/mypage/service/MyService.java @@ -58,4 +58,16 @@ public List getMyDraftMemos(Long userId) { public List getMyQuestions(Long userId) { return questionRepository.getMyQuestions(userId, OrderType.NEW); } + + public List getMyLikedQuestions(Long userId) { + return questionRepository.getMyLikedQuestions(userId); + } + + public List getQuestionsOfMyAnswer(Long userId){ + return questionRepository.getQuestionsOfMyAnswer(userId); + } + + public List getQuestionsOfMyLikedAnswer(Long userId) { + return questionRepository.getQuestionsOfMyLikedAnswer(userId); + } } diff --git a/src/main/java/Funssion/Inforum/domain/post/like/service/LikeService.java b/src/main/java/Funssion/Inforum/domain/post/like/service/LikeService.java index 21be4aa6..e017b7a7 100644 --- a/src/main/java/Funssion/Inforum/domain/post/like/service/LikeService.java +++ b/src/main/java/Funssion/Inforum/domain/post/like/service/LikeService.java @@ -78,7 +78,6 @@ private Long getDisLikesOfPost(PostType postType, Long postId){ @Transactional public void likePost(PostType postType, Long postId) { Long userId = SecurityContextUtils.getUserId(); - System.out.println("userId = " + userId); likeRepository.findByUserIdAndPostInfoOfDisLike(userId,postType,postId) .ifPresent(like ->{ throw new BadRequestException("싫어요와 좋아요를 동시에 할 수 없습니다."); diff --git a/src/main/java/Funssion/Inforum/domain/post/qna/repository/QuestionRepository.java b/src/main/java/Funssion/Inforum/domain/post/qna/repository/QuestionRepository.java index 4f5a06e2..55ed8de3 100644 --- a/src/main/java/Funssion/Inforum/domain/post/qna/repository/QuestionRepository.java +++ b/src/main/java/Funssion/Inforum/domain/post/qna/repository/QuestionRepository.java @@ -25,4 +25,9 @@ public interface QuestionRepository { List getQuestionsOfMemo(Long userId, Long memoId); + List getMyLikedQuestions(Long userId); + + List getQuestionsOfMyAnswer(Long userId); + + List getQuestionsOfMyLikedAnswer(Long userId); } diff --git a/src/main/java/Funssion/Inforum/domain/post/qna/repository/QuestionRepositoryImpl.java b/src/main/java/Funssion/Inforum/domain/post/qna/repository/QuestionRepositoryImpl.java index 616129da..a211b78f 100644 --- a/src/main/java/Funssion/Inforum/domain/post/qna/repository/QuestionRepositoryImpl.java +++ b/src/main/java/Funssion/Inforum/domain/post/qna/repository/QuestionRepositoryImpl.java @@ -108,6 +108,41 @@ public List getQuestionsOfMemo(Long userId,Long memoId) { return template.query(sql,questionLikeRowMapper(),memoId, userId); } + @Override + public List getMyLikedQuestions(Long userId) { + String sql = + "select Q.id, Q.author_id, Q.author_name, Q.author_image_path, Q.title, Q.text, Q.description, Q.likes, Q.is_solved, Q.created_date, Q.updated_date, Q.tags, Q.replies_count, Q.answers, Q.is_solved, Q.memo_id "+ + "from question.info AS Q " + + "join member.like AS L " + + "on Q.id = L.post_id and L.post_type = 'QUESTION' " + + "where L.user_id = ? "+ + "order by Q.id desc"; + return template.query(sql,questionRowMapper(),userId); + } + + @Override + public List getQuestionsOfMyAnswer(Long userId) { + String sql = + "select Q.id, Q.author_id, Q.author_name, Q.author_image_path, Q.title, Q.text, Q.description, Q.likes, Q.is_solved, Q.created_date, Q.updated_date, Q.tags, Q.replies_count, Q.answers, Q.is_solved, Q.memo_id "+ + "from question.info AS Q " + + "join (select question_id from question.answer where author_id = ?) AS A " + + "on Q.id = A.question_id " + + "order by Q.id desc"; + return template.query(sql,questionRowMapper(),userId); + } + + @Override + public List getQuestionsOfMyLikedAnswer(Long userId) { + String sql = + "select Q.id, Q.author_id, Q.author_name, Q.author_image_path, Q.title, Q.text, Q.description, Q.likes, Q.is_solved, Q.created_date, Q.updated_date, Q.tags, Q.replies_count, Q.answers, Q.is_solved, Q.memo_id "+ + "from question.info AS Q " + + "join (select question_id from question.answer AS QA join member.like AS ML " + + "on QA.id = ML.post_id and ML.post_type = 'ANSWER' and ML.user_id = ?) AS A " + + "on Q.id = A.question_id " + + "order by Q.id desc"; + return template.query(sql,questionRowMapper(),userId); + } + @Override public Question getOneQuestion(Long questionId) { String sql = "select id, author_id, author_name, author_image_path, title, text, description, likes, is_solved, created_date, updated_date, tags, replies_count, answers, is_solved, memo_id " + diff --git a/src/test/java/Funssion/Inforum/domain/post/qna/service/QnAIntegrationTest.java b/src/test/java/Funssion/Inforum/domain/post/qna/service/QnAIntegrationTest.java index 5c26f8b2..fb8a47f7 100644 --- a/src/test/java/Funssion/Inforum/domain/post/qna/service/QnAIntegrationTest.java +++ b/src/test/java/Funssion/Inforum/domain/post/qna/service/QnAIntegrationTest.java @@ -231,6 +231,66 @@ void getMyQuestion(){ assertThat(myQuestions).hasSize(2); } + @Test + @DisplayName("자신이 좋아요한 질문만 가져오기") + @Transactional + void getMyLikedQuestion(){ + Question question1 = makePureQuestion(); + Question question2 = makePureQuestion(); + Question question3 = makePureQuestion(); + Long likeUserId = createUniqueAuthor(); + SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(likeUserId.toString(),"12345678")); + + likeService.likePost(PostType.QUESTION, question1.getId()); + likeService.likePost(PostType.QUESTION, question2.getId()); + + List myLikedQuestions = questionRepository.getMyLikedQuestions(likeUserId); + assertThat(myLikedQuestions).hasSize(2); + } + + @Test + @DisplayName("자신이 답변한 질문만 가져오기") + @Transactional + void getQuestionsOfMyAnswer(){ + Question question1 = makePureQuestion(); + Question question2 = makePureQuestion(); + Question question3 = makePureQuestion(); + Long answerAuthorId = createUniqueAuthor(); + SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(answerAuthorId.toString(),"12345678")); + AnswerSaveDto answerSaveDto = AnswerSaveDto.builder() + .text("{\"type\": \"doc\", \"content\": [{\"type\": \"paragraph\", \"content\": [{\"text\": \"답변 내용\", \"type\": \"text\"}]}]}") + .description("답변 요약") + .build(); + answerService.createAnswerOfQuestion(answerSaveDto,question2.getId(),answerAuthorId); + answerService.createAnswerOfQuestion(answerSaveDto,question3.getId(),answerAuthorId); + + List questionsOfMyAnswer = questionRepository.getQuestionsOfMyAnswer(answerAuthorId); + assertThat(questionsOfMyAnswer).hasSize(2); + } + + @Test + @DisplayName("자신이 좋아요한 답변이 존재하는 질문들 가져오기") + @Transactional + void getQuestionsOfMyLikedAnswer(){ + Question question1 = makePureQuestion(); + Question question2 = makePureQuestion(); + Question question3 = makePureQuestion(); + + + Long answerAuthorId = createUniqueAuthor(); + makeAnswerOfQuestion(answerAuthorId,List.of(question1,question2)); + List answersOfQuestion1 = answerService.getAnswersOfQuestion(saveMemberId, question1.getId()); + List answersOfQuestion2 = answerService.getAnswersOfQuestion(saveMemberId, question2.getId()); + + Long likeUserId = createUniqueAuthor(); + SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(likeUserId.toString(),"12345678")); + likeService.likePost(PostType.ANSWER, answersOfQuestion1.get(0).getId()); + likeService.likePost(PostType.ANSWER, answersOfQuestion2.get(0).getId()); + + List questionsOfMyLikedAnswer = questionRepository.getQuestionsOfMyLikedAnswer(likeUserId); + assertThat(questionsOfMyLikedAnswer).hasSize(2); + } + private void makeQuestionOfOtherAuthor() { Long questionAuthorId = createUniqueAuthor(); QuestionSaveDto questionSaveDto = QuestionSaveDto.builder().title("테스트 제목 생성") @@ -460,14 +520,14 @@ void getAnswerOfQuestion(){ assertThat(answerOfQuestion.getText()).isEqualTo(answer.getText()); } - private void makeAnswerOfQuestion(Long authorId, List questions) { - answerService.createAnswerOfQuestion(createAnswerSaveDto(),questions.get(0).getId(),authorId); - answerService.createAnswerOfQuestion(createAnswerSaveDto(),questions.get(0).getId(),authorId); - answerService.createAnswerOfQuestion(createAnswerSaveDto(),questions.get(0).getId(),authorId); - answerService.createAnswerOfQuestion(createAnswerSaveDto(),questions.get(1).getId(),authorId); - } } + private void makeAnswerOfQuestion(Long authorId, List questions) { + answerService.createAnswerOfQuestion(createAnswerSaveDto(),questions.get(0).getId(),authorId); + answerService.createAnswerOfQuestion(createAnswerSaveDto(),questions.get(0).getId(),authorId); + answerService.createAnswerOfQuestion(createAnswerSaveDto(),questions.get(0).getId(),authorId); + answerService.createAnswerOfQuestion(createAnswerSaveDto(),questions.get(1).getId(),authorId); + } @Nested @DisplayName("질문 수정") From a07355a2cf8707eeb619ebe85a52f3a10a21b33c Mon Sep 17 00:00:00 2001 From: comolove Date: Tue, 10 Oct 2023 01:35:28 +0900 Subject: [PATCH 09/12] feat: add search question api --- .../qna/controller/QuestionController.java | 19 ++++++ .../qna/repository/QuestionRepository.java | 5 ++ .../repository/QuestionRepositoryImpl.java | 59 +++++++++++++++++++ .../post/qna/service/QuestionService.java | 3 + .../post/qna/service/QuestionServiceImpl.java | 29 +++++++++ 5 files changed, 115 insertions(+) diff --git a/src/main/java/Funssion/Inforum/domain/post/qna/controller/QuestionController.java b/src/main/java/Funssion/Inforum/domain/post/qna/controller/QuestionController.java index 7786fbdb..f1070ff9 100644 --- a/src/main/java/Funssion/Inforum/domain/post/qna/controller/QuestionController.java +++ b/src/main/java/Funssion/Inforum/domain/post/qna/controller/QuestionController.java @@ -4,6 +4,8 @@ import Funssion.Inforum.common.constant.OrderType; import Funssion.Inforum.common.dto.IsSuccessResponseDto; import Funssion.Inforum.common.exception.etc.UnAuthorizedException; +import Funssion.Inforum.common.utils.SecurityContextUtils; +import Funssion.Inforum.domain.post.memo.dto.response.MemoListDto; import Funssion.Inforum.domain.post.qna.Constant; import Funssion.Inforum.domain.post.qna.domain.Question; import Funssion.Inforum.domain.post.qna.dto.request.QuestionSaveDto; @@ -11,6 +13,7 @@ import Funssion.Inforum.domain.post.qna.service.QuestionService; import Funssion.Inforum.domain.post.utils.AuthUtils; import Funssion.Inforum.s3.dto.response.ImageDto; +import jakarta.validation.constraints.Min; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; @@ -18,7 +21,9 @@ import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; +import java.util.ArrayList; import java.util.List; +import java.util.Objects; @Slf4j @RequiredArgsConstructor @@ -66,6 +71,20 @@ public List getQuestionsOfMemo(@RequestParam Long memoId){ return questionService.getQuestionsOfMemo(loginId,memoId); } + @GetMapping("/search") + public List getSearchedQuestions( + @RequestParam(required = false) String searchString, + @RequestParam(required = false, defaultValue = SecurityContextUtils.ANONYMOUS_USER_ID_STRING) @Min(0) Long userId, + @RequestParam OrderType orderBy, + @RequestParam Boolean isTag + ) { + if (Objects.isNull(searchString) || searchString.isEmpty()) { + return new ArrayList<>(); + } + + return questionService.searchQuestionsBy(searchString, userId, orderBy, isTag); + } + @PostMapping("/image") public ImageDto saveImageAndGetImageURL( @RequestPart MultipartFile image diff --git a/src/main/java/Funssion/Inforum/domain/post/qna/repository/QuestionRepository.java b/src/main/java/Funssion/Inforum/domain/post/qna/repository/QuestionRepository.java index 54a604e2..16686041 100644 --- a/src/main/java/Funssion/Inforum/domain/post/qna/repository/QuestionRepository.java +++ b/src/main/java/Funssion/Inforum/domain/post/qna/repository/QuestionRepository.java @@ -1,6 +1,7 @@ package Funssion.Inforum.domain.post.qna.repository; import Funssion.Inforum.common.constant.OrderType; +import Funssion.Inforum.domain.post.memo.domain.Memo; import Funssion.Inforum.domain.post.qna.domain.Question; import Funssion.Inforum.domain.post.qna.dto.request.QuestionSaveDto; @@ -23,5 +24,9 @@ public interface QuestionRepository { List getQuestionsOfMemo(Long userId, Long memoId); + List findAllBySearchQuery(List searchStringList, OrderType orderType); + List findAllByTag(String tagText, OrderType orderType); + List findAllByTag(String tagText, Long userId, OrderType orderType); + void updateProfileImage(Long userId, String profileImageFilePath); } diff --git a/src/main/java/Funssion/Inforum/domain/post/qna/repository/QuestionRepositoryImpl.java b/src/main/java/Funssion/Inforum/domain/post/qna/repository/QuestionRepositoryImpl.java index 5089288a..919cc93a 100644 --- a/src/main/java/Funssion/Inforum/domain/post/qna/repository/QuestionRepositoryImpl.java +++ b/src/main/java/Funssion/Inforum/domain/post/qna/repository/QuestionRepositoryImpl.java @@ -5,6 +5,7 @@ import Funssion.Inforum.common.exception.badrequest.BadRequestException; import Funssion.Inforum.common.exception.etc.ArrayToListException; import Funssion.Inforum.common.exception.notfound.NotFoundException; +import Funssion.Inforum.domain.post.memo.domain.Memo; import Funssion.Inforum.domain.post.qna.domain.Question; import Funssion.Inforum.domain.post.qna.dto.request.QuestionSaveDto; import Funssion.Inforum.domain.post.qna.exception.DuplicateSelectedAnswerException; @@ -20,6 +21,7 @@ import javax.sql.DataSource; import java.sql.PreparedStatement; import java.sql.SQLException; +import java.util.ArrayList; import java.util.List; @Repository @@ -139,6 +141,63 @@ private void exceptionHandlingOfNonUniqueSolved(Long questionId){ throw new DuplicateSelectedAnswerException("두개의 답변을 채택할 순 없습니다."); } } + + @Override + public List findAllBySearchQuery(List searchStringList, OrderType orderType) { + String sql = getSql(searchStringList, orderType); + + return template.query(sql, questionRowMapper(), getParams(searchStringList)); + } + private static String getSql(List searchStringList, OrderType orderType) { + StringBuilder sql = new StringBuilder("select * from question.info where "); + + for (int i = 0; i < searchStringList.size() ; i++) { + sql.append("title ilike ? or "); + } + + for (int i = 0; i < searchStringList.size() ; i++) { + sql.append("text::text ilike ? "); + if (i != searchStringList.size() - 1) sql.append("or "); + } + + sql.append(getOrderBySql(orderType)); + + return sql.toString(); + } + + private static Object[] getParams(List searchStringList) { + ArrayList params = new ArrayList<>(); + params.addAll(searchStringList); + params.addAll(searchStringList); + return params.toArray(); + } + + @Override + public List findAllByTag(String tagText, OrderType orderType) { + String sql = "select * from question.info where ? ilike any(tags)" + getOrderBySql(orderType); + + return template.query(sql, questionRowMapper(), tagText); + } + + @Override + public List findAllByTag(String tagText, Long userId, OrderType orderType) { + String sql = "select * from question.info where author_id = ? and ? ilike any(tags)" + getOrderBySql(orderType); + + return template.query(sql, questionRowMapper(), userId, tagText); + } + + private static String getOrderBySql(OrderType orderType) { + switch (orderType) { + case HOT -> { + return " order by likes desc, id desc"; + } + case NEW -> { + return " order by id desc"; + } + } + throw new BadRequestException("Invalid orderType value"); + } + @Override public Question updateLikesInQuestion(Long likes, Long questionId) { String sql = "update question.info " + diff --git a/src/main/java/Funssion/Inforum/domain/post/qna/service/QuestionService.java b/src/main/java/Funssion/Inforum/domain/post/qna/service/QuestionService.java index dbca78b8..98f56440 100644 --- a/src/main/java/Funssion/Inforum/domain/post/qna/service/QuestionService.java +++ b/src/main/java/Funssion/Inforum/domain/post/qna/service/QuestionService.java @@ -2,6 +2,7 @@ import Funssion.Inforum.common.constant.OrderType; import Funssion.Inforum.common.dto.IsSuccessResponseDto; +import Funssion.Inforum.domain.post.memo.dto.response.MemoListDto; import Funssion.Inforum.domain.post.qna.domain.Question; import Funssion.Inforum.domain.post.qna.dto.request.QuestionSaveDto; import Funssion.Inforum.domain.post.qna.dto.response.QuestionDto; @@ -23,5 +24,7 @@ public interface QuestionService { List getQuestionsOfMemo(Long userId, Long memoId); + List searchQuestionsBy(String searchString, Long userId, OrderType orderBy, Boolean isTag); + ImageDto saveImageAndGetImageURL(MultipartFile image); } diff --git a/src/main/java/Funssion/Inforum/domain/post/qna/service/QuestionServiceImpl.java b/src/main/java/Funssion/Inforum/domain/post/qna/service/QuestionServiceImpl.java index 7c95dfb5..7731f66d 100644 --- a/src/main/java/Funssion/Inforum/domain/post/qna/service/QuestionServiceImpl.java +++ b/src/main/java/Funssion/Inforum/domain/post/qna/service/QuestionServiceImpl.java @@ -5,9 +5,12 @@ import Funssion.Inforum.common.constant.Sign; import Funssion.Inforum.common.dto.IsSuccessResponseDto; import Funssion.Inforum.common.exception.badrequest.BadRequestException; +import Funssion.Inforum.common.utils.SecurityContextUtils; import Funssion.Inforum.domain.member.entity.MemberProfileEntity; import Funssion.Inforum.domain.mypage.exception.HistoryNotFoundException; import Funssion.Inforum.domain.mypage.repository.MyRepository; +import Funssion.Inforum.domain.post.memo.domain.Memo; +import Funssion.Inforum.domain.post.memo.dto.response.MemoListDto; import Funssion.Inforum.domain.post.memo.repository.MemoRepository; import Funssion.Inforum.domain.post.qna.Constant; import Funssion.Inforum.domain.post.qna.domain.Question; @@ -26,6 +29,7 @@ import org.springframework.web.multipart.MultipartFile; import java.time.LocalDateTime; +import java.util.Arrays; import java.util.List; import static Funssion.Inforum.common.constant.PostType.QUESTION; @@ -93,6 +97,31 @@ public List getQuestionsOfMemo(Long userId, Long memoId) { return questionRepository.getQuestionsOfMemo(userId, memoId); } + @Override + public List searchQuestionsBy( + String searchString, + Long userId, + OrderType orderBy, + Boolean isTag) { + + if (isTag) + return getQuestionsSearchedByTag(searchString, userId, orderBy); + + return questionRepository.findAllBySearchQuery(getSearchStringList(searchString), orderBy); + } + + private List getQuestionsSearchedByTag(String searchString, Long userId, OrderType orderBy) { + if (userId.equals(SecurityContextUtils.ANONYMOUS_USER_ID)) + return questionRepository.findAllByTag(searchString, orderBy); + + return questionRepository.findAllByTag(searchString, userId, orderBy); + } + + private static List getSearchStringList(String searchString) { + return Arrays.stream(searchString.split(" ")) + .map(str -> "%" + str + "%") + .toList(); + } @Override public QuestionDto getOneQuestion(Long loginId, Long questionId) { From d5a1d5005b535c72a1053152697c95851255b30f Mon Sep 17 00:00:00 2001 From: Goathoon Date: Tue, 10 Oct 2023 14:25:24 +0900 Subject: [PATCH 10/12] =?UTF-8?q?fix:=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20get=20question=EC=A4=91=EB=B3=B5=20?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=20distinct=EB=A1=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/post/qna/repository/QuestionRepositoryImpl.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/Funssion/Inforum/domain/post/qna/repository/QuestionRepositoryImpl.java b/src/main/java/Funssion/Inforum/domain/post/qna/repository/QuestionRepositoryImpl.java index 9fc62a14..d8cf2ac5 100644 --- a/src/main/java/Funssion/Inforum/domain/post/qna/repository/QuestionRepositoryImpl.java +++ b/src/main/java/Funssion/Inforum/domain/post/qna/repository/QuestionRepositoryImpl.java @@ -5,7 +5,6 @@ import Funssion.Inforum.common.exception.badrequest.BadRequestException; import Funssion.Inforum.common.exception.etc.ArrayToListException; import Funssion.Inforum.common.exception.notfound.NotFoundException; -import Funssion.Inforum.domain.post.memo.domain.Memo; import Funssion.Inforum.domain.post.qna.domain.Question; import Funssion.Inforum.domain.post.qna.dto.request.QuestionSaveDto; import Funssion.Inforum.domain.post.qna.exception.DuplicateSelectedAnswerException; @@ -134,7 +133,7 @@ public List getQuestionsOfMyAnswer(Long userId) { String sql = "select Q.id, Q.author_id, Q.author_name, Q.author_image_path, Q.title, Q.text, Q.description, Q.likes, Q.is_solved, Q.created_date, Q.updated_date, Q.tags, Q.replies_count, Q.answers, Q.is_solved, Q.memo_id "+ "from question.info AS Q " + - "join (select question_id from question.answer where author_id = ?) AS A " + + "join (select distinct question_id from question.answer where author_id = ?) AS A " + "on Q.id = A.question_id " + "order by Q.id desc"; return template.query(sql,questionRowMapper(),userId); @@ -145,7 +144,7 @@ public List getQuestionsOfMyLikedAnswer(Long userId) { String sql = "select Q.id, Q.author_id, Q.author_name, Q.author_image_path, Q.title, Q.text, Q.description, Q.likes, Q.is_solved, Q.created_date, Q.updated_date, Q.tags, Q.replies_count, Q.answers, Q.is_solved, Q.memo_id "+ "from question.info AS Q " + - "join (select question_id from question.answer AS QA join member.like AS ML " + + "join (select distinct question_id from question.answer AS QA join member.like AS ML " + "on QA.id = ML.post_id and ML.post_type = 'ANSWER' and ML.user_id = ?) AS A " + "on Q.id = A.question_id " + "order by Q.id desc"; From 4c587a3161e27368c5e195bdce170a240ef58eb8 Mon Sep 17 00:00:00 2001 From: Goathoon Date: Tue, 10 Oct 2023 22:37:27 +0900 Subject: [PATCH 11/12] =?UTF-8?q?fix:=20=EB=85=BC=EC=86=8C=EC=85=9C=20?= =?UTF-8?q?=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85=ED=9B=84=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=ED=8E=98=EC=9D=B4=EC=A7=80=20security=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Inforum/config/SecurityConfig.java | 4 +- .../member/controller/MemberController.java | 5 +- .../domain/member/service/AuthService.java | 1 - .../domain/member/service/MemberService.java | 53 ++++++++++++++++++- 4 files changed, 57 insertions(+), 6 deletions(-) diff --git a/src/main/java/Funssion/Inforum/config/SecurityConfig.java b/src/main/java/Funssion/Inforum/config/SecurityConfig.java index 3e80eb33..ecb06c7f 100644 --- a/src/main/java/Funssion/Inforum/config/SecurityConfig.java +++ b/src/main/java/Funssion/Inforum/config/SecurityConfig.java @@ -65,12 +65,12 @@ public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Excepti authorizeRequests .requestMatchers(HttpMethod.OPTIONS, "/**/*").permitAll() //users 포함한 end point 보안 적용 X - .requestMatchers("/users/**").permitAll() + .requestMatchers(HttpMethod.GET,"/users/**").permitAll() + .requestMatchers(HttpMethod.GET, "/users/profile/**").permitAll() // 개인 정보 수정은 권한 필요 .requestMatchers(HttpMethod.POST, "/users/login").authenticated() //spring security filter에서 redirect .requestMatchers(HttpMethod.GET,"/tags/**").permitAll() .requestMatchers("/oauth2/authorization/**").permitAll() .requestMatchers("/login/oauth2/code/**").permitAll() - .requestMatchers(HttpMethod.GET, "/users/profile/**").permitAll() // 개인 정보 수정은 권한 필요 .requestMatchers("/error/**").permitAll() .requestMatchers(HttpMethod.GET, "/memos/**").permitAll() .requestMatchers(HttpMethod.GET,"/questions/**").permitAll() diff --git a/src/main/java/Funssion/Inforum/domain/member/controller/MemberController.java b/src/main/java/Funssion/Inforum/domain/member/controller/MemberController.java index fc45e749..610879f2 100644 --- a/src/main/java/Funssion/Inforum/domain/member/controller/MemberController.java +++ b/src/main/java/Funssion/Inforum/domain/member/controller/MemberController.java @@ -22,6 +22,7 @@ import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; +import java.io.IOException; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -43,8 +44,8 @@ public class MemberController { @PostMapping("") @ResponseStatus(HttpStatus.CREATED) - public SaveMemberResponseDto create(@RequestBody @Valid MemberSaveDto memberSaveDto){ //dto로 바꿔야함 - return memberService.requestMemberRegistration(memberSaveDto); + public SaveMemberResponseDto create(HttpServletRequest request, HttpServletResponse response, @RequestBody @Valid MemberSaveDto memberSaveDto) throws IOException { //dto로 바꿔야함 + return memberService.requestMemberRegistration(memberSaveDto,request,response); } @PostMapping("/authenticate-email") diff --git a/src/main/java/Funssion/Inforum/domain/member/service/AuthService.java b/src/main/java/Funssion/Inforum/domain/member/service/AuthService.java index 5845af6c..9191596c 100644 --- a/src/main/java/Funssion/Inforum/domain/member/service/AuthService.java +++ b/src/main/java/Funssion/Inforum/domain/member/service/AuthService.java @@ -18,7 +18,6 @@ public class AuthService implements UserDetailsService { @Override public UserDetails loadUserByUsername(String userEmail) throws UsernameNotFoundException { - log.info("useremail = {}",userEmail); NonSocialMember member = nonSocialMemberRepository.findNonSocialMemberByEmail(userEmail) .orElseThrow(() -> new UsernameNotFoundException("이 이메일과 매칭되는 유저가 존재하지 않습니다 : " + userEmail)); // non social, social 섞어있기 때문에, user_id를 CustomUserDetail 의 id로 생성합니다. -> 토큰의 getName의 user_id가 들어갑니다. diff --git a/src/main/java/Funssion/Inforum/domain/member/service/MemberService.java b/src/main/java/Funssion/Inforum/domain/member/service/MemberService.java index cf9443df..602e1590 100644 --- a/src/main/java/Funssion/Inforum/domain/member/service/MemberService.java +++ b/src/main/java/Funssion/Inforum/domain/member/service/MemberService.java @@ -18,15 +18,21 @@ import Funssion.Inforum.domain.post.memo.repository.MemoRepository; import Funssion.Inforum.domain.post.qna.repository.AnswerRepository; import Funssion.Inforum.domain.post.qna.repository.QuestionRepository; +import Funssion.Inforum.jwt.TokenProvider; import Funssion.Inforum.s3.S3Repository; import Funssion.Inforum.s3.S3Utils; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; +import java.io.IOException; import java.util.Optional; /* Spring Security 에서 유저의 정보를 가저오기 위한 로직이 포함. */ @@ -34,6 +40,9 @@ @Service @RequiredArgsConstructor public class MemberService { + private final TokenProvider tokenProvider; + @Value("${jwt.domain}") private String domain; + private final MemberRepository memberRepository; private final MyRepository myRepository; private final MemoRepository memoRepository; @@ -47,7 +56,7 @@ public class MemberService { private String profileDir; @Transactional - public SaveMemberResponseDto requestMemberRegistration (MemberSaveDto memberSaveDto){ + public SaveMemberResponseDto requestMemberRegistration (MemberSaveDto memberSaveDto, HttpServletRequest request, HttpServletResponse response) throws IOException { //중복 처리 한번더 검증 if(!isValidEmail(memberSaveDto.getUserEmail()).isValid()){ throw new DuplicateMemberException("이미 가입된 회원 이메일입니다."); @@ -58,9 +67,51 @@ public SaveMemberResponseDto requestMemberRegistration (MemberSaveDto memberSave NonSocialMember member = NonSocialMember.createNonSocialMember(memberSaveDto); SaveMemberResponseDto savedMember = memberRepository.save(member); + + UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(memberSaveDto.getUserEmail(), memberSaveDto.getUserPw()); + SecurityContextHolder.getContext().setAuthentication(authentication); + + makeLoginForSavedUser(request, response, authentication); return savedMember; } + private void makeLoginForSavedUser(HttpServletRequest request, HttpServletResponse response, UsernamePasswordAuthenticationToken authentication) { + String accessToken = tokenProvider.createAccessToken(authentication); + String refreshToken = tokenProvider.createRefreshToken(authentication); + + resolveResponseCookieByOrigin(request, response, accessToken, refreshToken); + } + + private void resolveResponseCookieByOrigin(HttpServletRequest request, HttpServletResponse response, String accessToken, String refreshToken){ + if(request.getServerName().equals("localhost") || request.getServerName().equals("dev.inforum.me")){ + addCookie(accessToken, refreshToken, response,false); + } + else{ + addCookie(accessToken, refreshToken, response,true); + } + } + private void addCookie(String accessToken, String refreshToken, HttpServletResponse response,boolean isHttpOnly) { + String accessCookieString = makeAccessCookieString(accessToken, isHttpOnly); + String refreshCookieString = makeRefreshCookieString(refreshToken, isHttpOnly); + response.setHeader("Set-Cookie", accessCookieString); + response.addHeader("Set-Cookie", refreshCookieString); + } + + private String makeAccessCookieString(String token,boolean isHttpOnly) { + if(isHttpOnly){ + return "accessToken=" + token + "; Path=/; Domain=" + domain + "; Max-Age=3600; SameSite=Lax; HttpOnly; Secure"; + }else{ + return "accessToken=" + token + "; Path=/; Domain=" + domain + "; Max-Age=3600;"; + } + } + + private String makeRefreshCookieString(String token,boolean isHttpOnly) { + if(isHttpOnly){ + return "refreshToken=" + token + "; Path=/; Domain=" + domain + "; Max-Age=864000; SameSite=Lax; HttpOnly; Secure"; + }else{ + return "refreshToken=" + token + "; Path=/; Domain=" + domain + "; Max-Age=864000;"; + } + } @Transactional public IsSuccessResponseDto requestNicknameRegistration(NicknameRequestDto nicknameRequestDto,Long userId){ ValidatedDto isValidName = isValidName(nicknameRequestDto.getNickname()); From d86049f8054182fdacf71bdb304f241c4346094f Mon Sep 17 00:00:00 2001 From: comolove Date: Tue, 10 Oct 2023 23:49:02 +0900 Subject: [PATCH 12/12] feat: add api withdrawing member --- ddl.sql | 3 +- .../common/utils/SecurityContextUtils.java | 5 +- .../member/controller/MemberController.java | 25 +- .../member/entity/MemberProfileEntity.java | 6 + .../member/repository/MemberRepository.java | 2 + .../repository/MemberRepositoryImpl.java | 17 +- .../domain/member/service/MemberService.java | 12 + .../domain/post/comment/domain/ReComment.java | 2 + .../Inforum/domain/post/qna/Constant.java | 2 +- .../Inforum/domain/post/utils/AuthUtils.java | 12 + .../domain/profile/ProfileRepository.java | 75 ++++++ .../domain/member/MemberIntegrationTest.java | 253 ++++++++++++++++++ 12 files changed, 391 insertions(+), 23 deletions(-) create mode 100644 src/main/java/Funssion/Inforum/domain/profile/ProfileRepository.java create mode 100644 src/test/java/Funssion/Inforum/domain/member/MemberIntegrationTest.java diff --git a/ddl.sql b/ddl.sql index f2ab78f7..4b6c820e 100644 --- a/ddl.sql +++ b/ddl.sql @@ -114,7 +114,8 @@ CREATE TABLE member.info ( image_path varchar(300), created_date timestamp, follow_cnt int8 not null default 0, - follower_cnt int8 not null default 0 + follower_cnt int8 not null default 0, + is_deleted bool not null default false ); create table comment.info( diff --git a/src/main/java/Funssion/Inforum/common/utils/SecurityContextUtils.java b/src/main/java/Funssion/Inforum/common/utils/SecurityContextUtils.java index e6bf764e..fd76ff12 100644 --- a/src/main/java/Funssion/Inforum/common/utils/SecurityContextUtils.java +++ b/src/main/java/Funssion/Inforum/common/utils/SecurityContextUtils.java @@ -6,16 +6,17 @@ public abstract class SecurityContextUtils { public static final Long ANONYMOUS_USER_ID = 0L; public static final String ANONYMOUS_USER_ID_STRING = "0"; + public static final String ANONYMOUS_USER_NAME = "anonymousUser"; public static Long getUserId() { String userId = SecurityContextHolder.getContext().getAuthentication().getName(); - if (userId.equals("anonymousUser")) return ANONYMOUS_USER_ID; + if (userId.equals(ANONYMOUS_USER_NAME)) return ANONYMOUS_USER_ID; return Long.valueOf(userId); } public static Long getAuthorizedUserId() { String userId = SecurityContextHolder.getContext().getAuthentication().getName(); - if (userId.equals("anonymousUser")) throw new UnAuthorizedException("인증되지 않은 사용자입니다."); + if (userId.equals(ANONYMOUS_USER_NAME)) throw new UnAuthorizedException("인증되지 않은 사용자입니다."); return Long.valueOf(userId); } } diff --git a/src/main/java/Funssion/Inforum/domain/member/controller/MemberController.java b/src/main/java/Funssion/Inforum/domain/member/controller/MemberController.java index fc45e749..714a049c 100644 --- a/src/main/java/Funssion/Inforum/domain/member/controller/MemberController.java +++ b/src/main/java/Funssion/Inforum/domain/member/controller/MemberController.java @@ -1,13 +1,16 @@ package Funssion.Inforum.domain.member.controller; +import Funssion.Inforum.common.constant.CRUDType; import Funssion.Inforum.common.dto.IsSuccessResponseDto; import Funssion.Inforum.common.exception.badrequest.BadRequestException; import Funssion.Inforum.common.exception.notfound.NotFoundException; +import Funssion.Inforum.common.utils.SecurityContextUtils; import Funssion.Inforum.domain.member.dto.request.*; import Funssion.Inforum.domain.member.dto.response.*; import Funssion.Inforum.domain.member.service.MailService; import Funssion.Inforum.domain.member.service.MemberService; +import Funssion.Inforum.domain.post.utils.AuthUtils; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -95,22 +98,14 @@ public ValidMemberDto method(@CurrentSecurityContext SecurityContext context) { @ResponseStatus(HttpStatus.NO_CONTENT) @GetMapping("/logout") public void logout(HttpServletRequest request, HttpServletResponse response) { + AuthUtils.logout(request, response); + } - Cookie[] cookies = request.getCookies(); - if (cookies != null) { - for (Cookie cookie : cookies) { - if ("accessToken".equals(cookie.getName())) { - log.info("[Logout] User Id ={},", cookie.getValue()); - } - else if ("refreshToken".equals(cookie.getName())){ - log.info("[Logout] refresh token invalidated"); - } - } - } - ResponseCookie invalidateAccessCookie = ResponseCookie.from("accessToken", "none").maxAge(0).path("/").domain(".inforum.me").sameSite("none").httpOnly(true).secure(true).build(); - ResponseCookie invalidateRefreshCookie = ResponseCookie.from("refreshToken", "none").maxAge(0).path("/").domain(".inforum.me").sameSite("none").httpOnly(true).secure(true).build(); - response.addHeader("Set-Cookie", invalidateAccessCookie.toString()); - response.addHeader("Set-Cookie",invalidateRefreshCookie.toString()); + @PostMapping("/withdraw") + public void withdraw(HttpServletRequest request, HttpServletResponse response) { + Long userId = SecurityContextUtils.getAuthorizedUserId(); + memberService.withdrawUser(userId); + AuthUtils.logout(request, response); } @PostMapping("/profile/{id}") diff --git a/src/main/java/Funssion/Inforum/domain/member/entity/MemberProfileEntity.java b/src/main/java/Funssion/Inforum/domain/member/entity/MemberProfileEntity.java index 5a868b19..01bb4d1b 100644 --- a/src/main/java/Funssion/Inforum/domain/member/entity/MemberProfileEntity.java +++ b/src/main/java/Funssion/Inforum/domain/member/entity/MemberProfileEntity.java @@ -31,6 +31,12 @@ public MemberProfileEntity(String profileImageFilePath, String nickname, String this.userTags = userTags; } + public MemberProfileEntity(String profileImageFilePath, String nickname, String introduce) { + this.profileImageFilePath = profileImageFilePath; + this.nickname = nickname; + this.introduce = introduce; + } + public static RowMapper MemberInfoRowMapper() { return ((rs, rowNum) -> MemberProfileEntity.builder() diff --git a/src/main/java/Funssion/Inforum/domain/member/repository/MemberRepository.java b/src/main/java/Funssion/Inforum/domain/member/repository/MemberRepository.java index 440c8d85..54448426 100644 --- a/src/main/java/Funssion/Inforum/domain/member/repository/MemberRepository.java +++ b/src/main/java/Funssion/Inforum/domain/member/repository/MemberRepository.java @@ -30,4 +30,6 @@ public interface MemberRepository { IsSuccessResponseDto findAndChangePassword(PasswordUpdateDto passwordUpdateDto); String findEmailByAuthCode(String code); + + void deleteUser(Long userId); } diff --git a/src/main/java/Funssion/Inforum/domain/member/repository/MemberRepositoryImpl.java b/src/main/java/Funssion/Inforum/domain/member/repository/MemberRepositoryImpl.java index 8f886a1a..63b29d7a 100644 --- a/src/main/java/Funssion/Inforum/domain/member/repository/MemberRepositoryImpl.java +++ b/src/main/java/Funssion/Inforum/domain/member/repository/MemberRepositoryImpl.java @@ -59,7 +59,7 @@ public SaveMemberResponseDto save(SocialMember member) { } public Optional findNonSocialMemberByEmail(String email) { - String sql ="SELECT A.ID AS A_ID ,U.ID AS U_ID,A.PASSWORD,U.EMAIL,U.FOLLOW_CNT,U.FOLLOWER_CNT FROM member.info AS U JOIN MEMBER.AUTH AS A ON U.ID = A.USER_ID WHERE U.EMAIL = ?"; + String sql ="SELECT A.ID AS A_ID ,U.ID AS U_ID,A.PASSWORD,U.EMAIL,U.FOLLOW_CNT,U.FOLLOWER_CNT FROM member.info AS U JOIN MEMBER.AUTH AS A ON U.ID = A.USER_ID WHERE U.IS_DELETED = false AND U.EMAIL = ?"; try{ NonSocialMember nonSocialMember = jdbcTemplate.queryForObject(sql,nonSocialmemberRowMapper(),email); return Optional.of(nonSocialMember); @@ -68,7 +68,7 @@ public Optional findNonSocialMemberByEmail(String email) { } } public Optional findSocialMemberByEmail(String email){ - String sql ="SELECT ID,NAME,EMAIL,LOGIN_TYPE,CREATED_DATE,IMAGE_PATH,INTRODUCE,TAGS,FOLLOW_CNT,FOLLOWER_CNT FROM member.info WHERE EMAIL = ?"; + String sql ="SELECT ID,NAME,EMAIL,LOGIN_TYPE,CREATED_DATE,IMAGE_PATH,INTRODUCE,TAGS,FOLLOW_CNT,FOLLOWER_CNT FROM member.info WHERE is_deleted = false and EMAIL = ?"; try{ SocialMember socialMember = jdbcTemplate.queryForObject(sql,socialMemberRowMapper(),email); return Optional.of(socialMember); @@ -80,7 +80,7 @@ public Optional findSocialMemberByEmail(String email){ @Override public Optional findByName(String name) { - String sql ="SELECT ID,EMAIL,NAME FROM member.info WHERE NAME = ?"; + String sql ="SELECT ID,EMAIL,NAME FROM member.info WHERE IS_DELETED = false AND NAME = ?"; try{ Member member = jdbcTemplate.queryForObject(sql,memberEmailAndNameRowMapper(),name); @@ -102,7 +102,7 @@ public IsSuccessResponseDto saveSocialMemberNickname(String nickname,Long userId @Override public String findEmailByNickname(String nickname) { - String sql ="select email from member.info where name = ?"; + String sql ="select email from member.info where is_deleted = false and name = ?"; try{ return jdbcTemplate.queryForObject(sql, String.class, nickname); }catch(EmptyResultDataAccessException e){ @@ -217,6 +217,15 @@ private SaveMemberResponseDto saveSocialMemberInUserTable(SocialMember member) { .build(); } + @Override + public void deleteUser(Long userId) { + String sql = "update member.info set is_deleted = true where id = ?"; + + if (jdbcTemplate.update(sql, userId) != 1) { + throw new BadRequestException("fail in deleting user : userId = " + userId); + } + } + private RowMapper nonSocialmemberRowMapper(){ return new RowMapper() { @Override diff --git a/src/main/java/Funssion/Inforum/domain/member/service/MemberService.java b/src/main/java/Funssion/Inforum/domain/member/service/MemberService.java index cf9443df..e1d0c73a 100644 --- a/src/main/java/Funssion/Inforum/domain/member/service/MemberService.java +++ b/src/main/java/Funssion/Inforum/domain/member/service/MemberService.java @@ -18,6 +18,7 @@ import Funssion.Inforum.domain.post.memo.repository.MemoRepository; import Funssion.Inforum.domain.post.qna.repository.AnswerRepository; import Funssion.Inforum.domain.post.qna.repository.QuestionRepository; +import Funssion.Inforum.domain.profile.ProfileRepository; import Funssion.Inforum.s3.S3Repository; import Funssion.Inforum.s3.S3Utils; import lombok.RequiredArgsConstructor; @@ -27,7 +28,9 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; +import java.util.List; import java.util.Optional; +import java.util.UUID; /* Spring Security 에서 유저의 정보를 가저오기 위한 로직이 포함. */ @Slf4j @@ -42,6 +45,7 @@ public class MemberService { private final CommentRepository commentRepository; private final S3Repository s3Repository; private final FollowRepository followRepository; + private final ProfileRepository profileRepository; @Value("${aws.s3.profile-dir}") private String profileDir; @@ -189,6 +193,14 @@ private IsProfileSavedDto updateProfile(Long userId, MemberProfileEntity memberP return myRepository.updateProfile(userId, memberProfileEntity); } + @Transactional + public void withdrawUser(Long userId) { + String anonymousUserName = UUID.randomUUID().toString().substring(0, 15); + profileRepository.updateProfile(userId, new MemberProfileEntity(null, anonymousUserName, "탈퇴한 유저입니다.")); + memberRepository.deleteUser(userId); + } + + @Transactional(readOnly = true) public MemberProfileDto getMemberProfile(Long userId){ Long requestUserId = SecurityContextUtils.getUserId(); diff --git a/src/main/java/Funssion/Inforum/domain/post/comment/domain/ReComment.java b/src/main/java/Funssion/Inforum/domain/post/comment/domain/ReComment.java index 291ff5b8..00db3272 100644 --- a/src/main/java/Funssion/Inforum/domain/post/comment/domain/ReComment.java +++ b/src/main/java/Funssion/Inforum/domain/post/comment/domain/ReComment.java @@ -3,11 +3,13 @@ import Funssion.Inforum.domain.member.entity.MemberProfileEntity; import Funssion.Inforum.domain.post.domain.Post; import lombok.Getter; +import lombok.experimental.SuperBuilder; import java.sql.Date; import java.time.LocalDateTime; @Getter +@SuperBuilder public class ReComment extends Post { private Long parentCommentId; private String commentText; diff --git a/src/main/java/Funssion/Inforum/domain/post/qna/Constant.java b/src/main/java/Funssion/Inforum/domain/post/qna/Constant.java index db16b892..2e55e554 100644 --- a/src/main/java/Funssion/Inforum/domain/post/qna/Constant.java +++ b/src/main/java/Funssion/Inforum/domain/post/qna/Constant.java @@ -1,5 +1,5 @@ package Funssion.Inforum.domain.post.qna; -public class Constant { +public abstract class Constant { public final static String NONE_MEMO_QUESTION = "0"; } diff --git a/src/main/java/Funssion/Inforum/domain/post/utils/AuthUtils.java b/src/main/java/Funssion/Inforum/domain/post/utils/AuthUtils.java index 38f6b7cf..bcbbb030 100644 --- a/src/main/java/Funssion/Inforum/domain/post/utils/AuthUtils.java +++ b/src/main/java/Funssion/Inforum/domain/post/utils/AuthUtils.java @@ -3,6 +3,10 @@ import Funssion.Inforum.common.constant.CRUDType; import Funssion.Inforum.common.utils.SecurityContextUtils; import Funssion.Inforum.domain.post.memo.exception.NeedAuthenticationException; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.http.ResponseCookie; import static Funssion.Inforum.common.constant.CRUDType.READ; @@ -15,4 +19,12 @@ public static Long getUserId(CRUDType type) { throw new NeedAuthenticationException(type.toString().toLowerCase() + " fail"); } + + public static void logout(HttpServletRequest request, HttpServletResponse response) { + Cookie[] cookies = request.getCookies(); + ResponseCookie invalidateAccessCookie = ResponseCookie.from("accessToken", "none").maxAge(0).path("/").domain(".inforum.me").sameSite("none").httpOnly(true).secure(true).build(); + ResponseCookie invalidateRefreshCookie = ResponseCookie.from("refreshToken", "none").maxAge(0).path("/").domain(".inforum.me").sameSite("none").httpOnly(true).secure(true).build(); + response.addHeader("Set-Cookie", invalidateAccessCookie.toString()); + response.addHeader("Set-Cookie",invalidateRefreshCookie.toString()); + } } diff --git a/src/main/java/Funssion/Inforum/domain/profile/ProfileRepository.java b/src/main/java/Funssion/Inforum/domain/profile/ProfileRepository.java new file mode 100644 index 00000000..91f44250 --- /dev/null +++ b/src/main/java/Funssion/Inforum/domain/profile/ProfileRepository.java @@ -0,0 +1,75 @@ +package Funssion.Inforum.domain.profile; + +import Funssion.Inforum.domain.member.entity.MemberProfileEntity; +import Funssion.Inforum.domain.tag.repository.TagRepository; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; + +import javax.sql.DataSource; + +@Repository +public class ProfileRepository { + + private final JdbcTemplate template; + + public ProfileRepository(DataSource dataSource) { + this.template = new JdbcTemplate(dataSource); + } + + public void updateProfile(Long userId, MemberProfileEntity memberProfile) { + updateAuthorProfileInMemo(userId, memberProfile); + updateAuthorProfileInComment(userId, memberProfile); + updateAuthorProfileInReComment(userId, memberProfile); + updateAuthorProfileInQuestion(userId, memberProfile); + updateAuthorProfileInAnswer(userId, memberProfile); + updateUserProfile(userId, memberProfile); + } + + private void updateAuthorProfileInMemo(Long userId, MemberProfileEntity memberProfile) { + String sql = "update memo.info " + + "set author_image_path = ?, author_name = ? " + + "where author_id = ?"; + + template.update(sql, memberProfile.getProfileImageFilePath(), memberProfile.getNickname(), userId); + } + + private void updateAuthorProfileInComment(Long userId, MemberProfileEntity memberProfile) { + String sql = "update comment.info " + + "set author_image_path = ?, author_name = ? " + + "where author_id = ?"; + + template.update(sql, memberProfile.getProfileImageFilePath(), memberProfile.getNickname(), userId); + } + + private void updateAuthorProfileInReComment(Long userId, MemberProfileEntity memberProfile) { + String sql = "update comment.re_comments " + + "set author_image_path = ?, author_name = ? " + + "where author_id = ?"; + + template.update(sql, memberProfile.getProfileImageFilePath(), memberProfile.getNickname(), userId); + } + + private void updateAuthorProfileInQuestion(Long userId, MemberProfileEntity memberProfile) { + String sql = "update question.info " + + "set author_image_path = ?, author_name = ? " + + "where author_id = ?"; + + template.update(sql, memberProfile.getProfileImageFilePath(), memberProfile.getNickname(), userId); + } + + private void updateAuthorProfileInAnswer(Long userId, MemberProfileEntity memberProfile) { + String sql = "update question.answer " + + "set author_image_path = ?, author_name = ? " + + "where author_id = ?"; + + template.update(sql, memberProfile.getProfileImageFilePath(), memberProfile.getNickname(), userId); + } + + private void updateUserProfile(Long userId, MemberProfileEntity memberProfile) { + String sql = "update member.info " + + "set image_path = ?, name = ?, introduce = ? " + + "where id = ?"; + + template.update(sql, memberProfile.getProfileImageFilePath(), memberProfile.getNickname(), memberProfile.getIntroduce(), userId); + } +} diff --git a/src/test/java/Funssion/Inforum/domain/member/MemberIntegrationTest.java b/src/test/java/Funssion/Inforum/domain/member/MemberIntegrationTest.java new file mode 100644 index 00000000..f4e07e83 --- /dev/null +++ b/src/test/java/Funssion/Inforum/domain/member/MemberIntegrationTest.java @@ -0,0 +1,253 @@ +package Funssion.Inforum.domain.member; + +import Funssion.Inforum.common.constant.PostType; +import Funssion.Inforum.common.exception.etc.UnAuthorizedException; +import Funssion.Inforum.common.exception.notfound.NotFoundException; +import Funssion.Inforum.common.utils.SecurityContextUtils; +import Funssion.Inforum.domain.member.constant.LoginType; +import Funssion.Inforum.domain.member.dto.response.SaveMemberResponseDto; +import Funssion.Inforum.domain.member.entity.Member; +import Funssion.Inforum.domain.member.entity.MemberProfileEntity; +import Funssion.Inforum.domain.member.entity.NonSocialMember; +import Funssion.Inforum.domain.member.repository.MemberRepository; +import Funssion.Inforum.domain.mypage.repository.MyRepository; +import Funssion.Inforum.domain.post.comment.domain.Comment; +import Funssion.Inforum.domain.post.comment.domain.ReComment; +import Funssion.Inforum.domain.post.comment.dto.request.CommentSaveDto; +import Funssion.Inforum.domain.post.comment.dto.response.CommentListDto; +import Funssion.Inforum.domain.post.comment.dto.response.ReCommentListDto; +import Funssion.Inforum.domain.post.comment.repository.CommentRepository; +import Funssion.Inforum.domain.post.memo.domain.Memo; +import Funssion.Inforum.domain.post.memo.repository.MemoRepository; +import Funssion.Inforum.domain.post.qna.Constant; +import Funssion.Inforum.domain.post.qna.domain.Answer; +import Funssion.Inforum.domain.post.qna.domain.Question; +import Funssion.Inforum.domain.post.qna.repository.AnswerRepository; +import Funssion.Inforum.domain.post.qna.repository.QuestionRepository; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.result.MockMvcResultMatchers; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.*; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@SpringBootTest +@Transactional +@AutoConfigureMockMvc +public class MemberIntegrationTest { + + @Autowired + MockMvc mvc; + + @Autowired + MemberRepository memberRepository; + + @Autowired + MyRepository myRepository; + + @Autowired + MemoRepository memoRepository; + + @Autowired + QuestionRepository questionRepository; + + @Autowired + AnswerRepository answerRepository; + + @Autowired + CommentRepository commentRepository; + + Long savedNonsocialMemberId; + Long savedSocialMemberId; + String savedNonsocialMemberEmail = "nonsocial@gmail.com"; + String savedSocialMemberEmail = "social@gmail.com"; + String savedMemberName = "jinu"; + String savedMemberImagePath = "https://image"; + SaveMemberResponseDto savedNonsocialMember; + SaveMemberResponseDto savedSocialMember; + Memo savedMemo; + Question savedQuestion; + Answer savedAnswer; + Comment savedComment; + ReCommentListDto savedReComment; + + @BeforeEach + void init() { + savedNonsocialMember = memberRepository.save(NonSocialMember.builder() + .userPw("1234") + .userEmail(savedNonsocialMemberEmail) + .loginType(LoginType.NON_SOCIAL) + .authId(1L) + .userName(savedMemberName) + .introduce("hi") + .createdDate(LocalDateTime.now()) + .tags("Java") + .imagePath(savedMemberImagePath) + .build()); + + savedNonsocialMemberId = savedNonsocialMember.getId(); + + savedSocialMember = memberRepository.save(NonSocialMember.builder() + .userPw("1234") + .userEmail(savedSocialMemberEmail) + .loginType(LoginType.SOCIAL) + .authId(1L) + .userName(savedMemberName) + .introduce("hi") + .createdDate(LocalDateTime.now()) + .tags("Java") + .imagePath(savedMemberImagePath) + .build()); + + savedSocialMemberId = savedSocialMember.getId(); + + savedMemo = memoRepository.create(Memo.builder() + .title("JPA") + .text("{\"content\" : \"JPA is JPA\"}") + .description("JPA is ...") + .color("yellow") + .authorId(savedNonsocialMemberId) + .authorName(savedMemberName) + .authorImagePath(savedMemberImagePath) + .createdDate(LocalDateTime.now().minusDays(1)) + .likes(0L) + .isTemporary(false) + .memoTags(List.of("Java", "JPA")) + .build()); + + savedQuestion = questionRepository.createQuestion(Question.builder() + .authorId(savedNonsocialMemberId) + .authorName(savedMemberName) + .authorImagePath(savedMemberImagePath) + .title("JPA") + .text("{\"content\" : \"JPA is what?\"}") + .tags(List.of("JAVA")) + .memoId(savedMemo.getId()) + .description("JPA ...") + .build()); + + savedAnswer = answerRepository.createAnswer(Answer.builder() + .questionId(savedQuestion.getId()) + .authorId(savedNonsocialMemberId) + .authorName(savedMemberName) + .authorImagePath(savedMemberImagePath) + .text("{\"content\" : \"JPA is good.\"}") + .build()); + + savedComment = commentRepository.createComment(Comment.builder() + .authorId(savedNonsocialMemberId) + .authorName(savedMemberName) + .authorImagePath(savedMemberImagePath) + .postTypeWithComment(PostType.MEMO) + .postId(savedMemo.getId()) + .commentText("wow") + .build()); + + commentRepository.createReComment(ReComment.builder() + .authorId(savedNonsocialMemberId) + .authorName(savedMemberName) + .authorImagePath(savedMemberImagePath) + .parentCommentId(savedComment.getId()) + .commentText("wow") + .createdDate(LocalDateTime.now()) + .build()); + + savedReComment = commentRepository.getReCommentsAtComment(savedComment.getId(), savedNonsocialMemberId).get(0); + } + + @Nested + @DisplayName("회원 탈퇴하기") + class withdrawMember { + + @Test + @DisplayName("로그인 없이 회원 탈퇴 시도") + void withdrawWithOutLogin() throws Exception { + + mvc.perform(post("/users/withdraw") + .with(user(SecurityContextUtils.ANONYMOUS_USER_NAME))) + .andExpect(status().isUnauthorized()); + } + + @Test + @DisplayName("자체 회원가입 유저 정상 회원 탍퇴") + void success() throws Exception { + + mvc.perform(post("/users/withdraw") + .with(user(savedNonsocialMemberId.toString()))) + .andExpect(status().isOk()); + + String deletedUserName = memberRepository.findNameById(savedNonsocialMemberId); + MemberProfileEntity deletedUserProfile = myRepository.findProfileByUserId(savedNonsocialMemberId); + Memo memoByDeletedUser = memoRepository.findById(savedMemo.getId()); + Question questionByDeletedUser = questionRepository.getOneQuestion(savedQuestion.getId()); + Answer answerByDeletedUser = answerRepository.getAnswerById(savedAnswer.getId()); + CommentListDto commentByDeletedUser = commentRepository.getCommentsAtPost(PostType.MEMO, savedMemo.getId(), deletedUserProfile.getUserId()).get(0); + ReCommentListDto reCommentByDeletedUser = commentRepository.getReCommentsAtComment(commentByDeletedUser.getId(), deletedUserProfile.getUserId()).get(0); + + assertThat(deletedUserName).hasSize(15); + + assertThat(deletedUserProfile.getNickname()).isEqualTo(deletedUserName); + assertThat(deletedUserProfile.getProfileImageFilePath()).isNull(); + assertThat(deletedUserProfile.getIntroduce()).isEqualTo("탈퇴한 유저입니다."); + + assertThat(memberRepository.findByName(deletedUserName)).isNotPresent(); + assertThat(memberRepository.findNonSocialMemberByEmail(savedNonsocialMemberEmail)).isNotPresent(); + assertThatThrownBy(() -> memberRepository.findEmailByNickname(deletedUserName)) + .isInstanceOf(NotFoundException.class); + + assertThat(memoByDeletedUser.getAuthorImagePath()).isNull(); + assertThat(memoByDeletedUser.getAuthorName()).isEqualTo(deletedUserName); + + assertThat(questionByDeletedUser.getAuthorImagePath()).isNull(); + assertThat(questionByDeletedUser.getAuthorName()).isEqualTo(deletedUserName); + + assertThat(answerByDeletedUser.getAuthorImagePath()).isNull(); + assertThat(answerByDeletedUser.getAuthorName()).isEqualTo(deletedUserName); + + assertThat(commentByDeletedUser.getAuthorImagePath()).isNull(); + assertThat(commentByDeletedUser.getAuthorName()).isEqualTo(deletedUserName); + + assertThat(reCommentByDeletedUser.getAuthorImagePath()).isNull(); + assertThat(reCommentByDeletedUser.getAuthorName()).isEqualTo(deletedUserName); + } + + @Test + @DisplayName("구글 로그인 회원가입 회원 정상 탈퇴") + void successWithGoogleAuth() throws Exception { + + mvc.perform(post("/users/withdraw") + .with(user(savedSocialMemberId.toString()))) + .andExpect(status().isOk()); + + String deletedUserName = memberRepository.findNameById(savedSocialMemberId); + MemberProfileEntity deletedUserProfile = myRepository.findProfileByUserId(savedSocialMemberId); + + assertThat(deletedUserName).hasSize(15); + + assertThat(deletedUserProfile.getNickname()).isEqualTo(deletedUserName); + assertThat(deletedUserProfile.getProfileImageFilePath()).isNull(); + assertThat(deletedUserProfile.getIntroduce()).isEqualTo("탈퇴한 유저입니다."); + + assertThat(memberRepository.findByName(deletedUserName)).isNotPresent(); + assertThat(memberRepository.findSocialMemberByEmail(savedSocialMemberEmail)).isNotPresent(); + assertThatThrownBy(() -> memberRepository.findEmailByNickname(deletedUserName)) + .isInstanceOf(NotFoundException.class); + } + } +}