Skip to content

Commit

Permalink
[SAMBAD-267] �릴레이 질문 hidden 저장 및 조회 필터링 기능 추가 (#117)
Browse files Browse the repository at this point in the history
* feat: 모임 질문 숨김 요청 기능 추가

* feat: 작성한 모임 질문 답변 히스토리 조회 시 hidden 처리 추가
  • Loading branch information
nahyeon99 committed Aug 24, 2024
1 parent 836a483 commit b5ccb71
Show file tree
Hide file tree
Showing 17 changed files with 1,320 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import org.depromeet.sambad.moring.meeting.answer.domain.MeetingAnswer;
import org.depromeet.sambad.moring.meeting.answer.presentation.response.MeetingAnswerListResponse;
import org.depromeet.sambad.moring.meeting.answer.presentation.response.MyMeetingAnswerListResponse;
import org.depromeet.sambad.moring.meeting.member.domain.MeetingMember;

public interface MeetingAnswerRepository {
Expand All @@ -21,4 +22,8 @@ public interface MeetingAnswerRepository {
List<MeetingMember> findMeetingMembersSelectWith(Long meetingQuestionId, List<Long> answerIds);

MeetingAnswerListResponse findAllByMeetingMemberId(Long meetingMemberId);

MyMeetingAnswerListResponse findAllByMyMeetingMemberId(Long loginMemberId);

List<MeetingAnswer> findAllByMeetingQuestionIdIn(List<Long> meetingQuestionIds);
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.depromeet.sambad.moring.meeting.answer.presentation.exception.DuplicateMeetingAnswerException;
import org.depromeet.sambad.moring.meeting.answer.presentation.request.MeetingAnswerRequest;
import org.depromeet.sambad.moring.meeting.answer.presentation.response.MeetingAnswerListResponse;
import org.depromeet.sambad.moring.meeting.answer.presentation.response.MyMeetingAnswerListResponse;
import org.depromeet.sambad.moring.meeting.member.application.MeetingMemberService;
import org.depromeet.sambad.moring.meeting.member.domain.MeetingMember;
import org.depromeet.sambad.moring.meeting.member.domain.MeetingMemberValidator;
Expand Down Expand Up @@ -55,9 +56,9 @@ public void save(Long userId, Long meetingId, Long meetingQuestionId, MeetingAns
advanceToNextQuestionIfAllAnswered(meetingId, meetingQuestion);
}

public MeetingAnswerListResponse getListByMe(Long userId, Long meetingId) {
public MyMeetingAnswerListResponse getListByMe(Long userId, Long meetingId) {
MeetingMember loginMember = meetingMemberService.getByUserIdAndMeetingId(userId, meetingId);
return meetingAnswerRepository.findAllByMeetingMemberId(loginMember.getId());
return meetingAnswerRepository.findAllByMyMeetingMemberId(loginMember.getId());
}

public MeetingAnswerListResponse getListByMember(Long userId, Long meetingId, Long targetMemberId) {
Expand Down Expand Up @@ -96,4 +97,12 @@ private void advanceToNextQuestionIfAllAnswered(Long meetingId, MeetingQuestion
});
}
}

@Transactional
public void updateHidden(Long userId, Long meetingId, List<Long> hiddenMeetingQuestionIds) {
MeetingMember member = meetingMemberService.getByUserIdAndMeetingId(userId, meetingId);
List<MeetingAnswer> hiddenAnswers = meetingAnswerRepository.findAllByMeetingQuestionIdIn(
hiddenMeetingQuestionIds);
hiddenAnswers.forEach(meetingAnswer -> meetingAnswer.updateHidden(member));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.depromeet.sambad.moring.common.domain.BaseTimeEntity;
import org.depromeet.sambad.moring.meeting.member.domain.MeetingMember;
import org.depromeet.sambad.moring.meeting.question.domain.MeetingQuestion;
import org.depromeet.sambad.moring.meeting.answer.presentation.exception.CannotUpdateMeetingAnswer;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
Expand Down Expand Up @@ -43,24 +44,34 @@ public class MeetingAnswer extends BaseTimeEntity {
@JoinColumn(name = "meeting_member_id")
private MeetingMember meetingMember;

private Boolean isHidden;

@Builder
public MeetingAnswer(MeetingQuestion meetingQuestion, Answer answer, MeetingMember meetingMember) {
this.meetingQuestion = meetingQuestion;
this.answer = answer;
this.meetingMember = meetingMember;
this.isHidden = false;

meetingQuestion.addMeetingAnswer(this);
}

public String getAnswerContent() {
return answer.getContent();
}

public static List<Long> getDistinctAnswerIds(List<MeetingAnswer> answers) {
return answers.stream()
.map(meetingAnswer -> meetingAnswer.answer.getId())
.filter(Objects::nonNull)
.distinct()
.toList();
}

public String getAnswerContent() {
return answer.getContent();
}

public void updateHidden(MeetingMember updateMember) {
if (this.meetingMember.isNotEqualMemberWith(updateMember)) {
throw new CannotUpdateMeetingAnswer();
}
this.isHidden = true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ public interface MeetingAnswerJpaRepository extends JpaRepository<MeetingAnswer,
boolean existsByMeetingQuestionIdAndMeetingMemberId(Long meetingQuestionId, Long meetingMemberId);

List<MeetingAnswer> findByMeetingQuestionIdAndMeetingMemberId(Long meetingQuestionId, Long meetingMemberId);

List<MeetingAnswer> findAllByMeetingQuestionIdIn(List<Long> meetingQuestionIds);
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@

import org.depromeet.sambad.moring.answer.domain.Answer;
import org.depromeet.sambad.moring.meeting.answer.domain.MeetingAnswer;
import org.depromeet.sambad.moring.meeting.answer.infrastructure.dto.MeetingAnswerResponseCustom;
import org.depromeet.sambad.moring.meeting.answer.infrastructure.dto.MyMeetingAnswerResponseCustom;
import org.depromeet.sambad.moring.meeting.answer.presentation.response.MeetingAnswerListResponse;
import org.depromeet.sambad.moring.meeting.answer.presentation.response.MyMeetingAnswerListResponse;
import org.depromeet.sambad.moring.meeting.member.domain.MeetingMember;
import org.depromeet.sambad.moring.meeting.question.domain.MeetingQuestion;
import org.springframework.stereotype.Repository;
Expand Down Expand Up @@ -91,7 +93,29 @@ public List<MeetingMember> findMeetingMembersSelectWith(Long meetingQuestionId,
.toList();
}

public MeetingAnswerListResponse findAllByMeetingMemberId(Long meetingMemberId) {
public MeetingAnswerListResponse findAllByOtherMeetingMemberId(Long meetingMemberId) {

List<MeetingQuestion> meetingQuestions = queryFactory.select(meetingQuestion)
.from(meetingQuestion)
.join(meetingAnswer).on(meetingQuestion.eq(meetingAnswer.meetingQuestion)).fetchJoin()
.join(meetingMember).on(meetingMember.eq(meetingAnswer.meetingMember)).fetchJoin()
.where(meetingMember.id.eq(meetingMemberId),
meetingAnswer.isHidden.isFalse())
.orderBy(meetingQuestion.createdAt.asc())
.fetch();

List<MeetingAnswerResponseCustom> responseCustoms = meetingQuestions.stream()
.map(meetingQuestion -> new MeetingAnswerResponseCustom(meetingQuestion.getId(),
meetingQuestion.getTitle(),
getMyAnswers(meetingMemberId, meetingQuestion),
getMyComment(meetingMemberId, meetingQuestion)))
.toList();

return MeetingAnswerListResponse.from(responseCustoms);
}

public MyMeetingAnswerListResponse findAllByMyMeetingMemberId(Long meetingMemberId) {

List<MeetingQuestion> meetingQuestions = queryFactory.select(meetingQuestion)
.from(meetingQuestion)
.join(meetingAnswer).on(meetingQuestion.eq(meetingAnswer.meetingQuestion)).fetchJoin()
Expand All @@ -101,12 +125,15 @@ public MeetingAnswerListResponse findAllByMeetingMemberId(Long meetingMemberId)
.fetch();

List<MyMeetingAnswerResponseCustom> responseCustoms = meetingQuestions.stream()
.map(question -> new MyMeetingAnswerResponseCustom(question.getTitle(),
getMyAnswers(meetingMemberId, question),
getMyComment(meetingMemberId, question)))
.map(meetingQuestion -> new MyMeetingAnswerResponseCustom(meetingQuestion.getId(),
meetingQuestion.getTitle(),
getMyAnswers(meetingMemberId, meetingQuestion),
getMyComment(meetingMemberId, meetingQuestion),
isHidden(meetingQuestion, meetingMemberId)
))
.toList();

return MeetingAnswerListResponse.from(responseCustoms);
return MyMeetingAnswerListResponse.from(responseCustoms);
}

private List<Answer> getMyAnswers(Long memberId, MeetingQuestion meetingQuestion) {
Expand All @@ -124,4 +151,12 @@ private String getMyComment(Long memberId, MeetingQuestion meetingQuestion) {
meetingQuestionComment.meetingQuestion.eq(meetingQuestion))
.fetchFirst();
}

private Boolean isHidden(MeetingQuestion meetingQuestion, Long loginMemberId) {
MeetingAnswer answerOfList = queryFactory.selectFrom(meetingAnswer)
.where(meetingAnswer.meetingQuestion.eq(meetingQuestion),
meetingAnswer.meetingMember.id.eq(loginMemberId))
.fetchFirst();
return answerOfList.getIsHidden();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.depromeet.sambad.moring.meeting.answer.application.MeetingAnswerRepository;
import org.depromeet.sambad.moring.meeting.answer.domain.MeetingAnswer;
import org.depromeet.sambad.moring.meeting.answer.presentation.response.MeetingAnswerListResponse;
import org.depromeet.sambad.moring.meeting.answer.presentation.response.MyMeetingAnswerListResponse;
import org.depromeet.sambad.moring.meeting.member.domain.MeetingMember;
import org.springframework.stereotype.Repository;

Expand Down Expand Up @@ -50,6 +51,16 @@ public List<MeetingMember> findMeetingMembersSelectWith(Long meetingQuestionId,

@Override
public MeetingAnswerListResponse findAllByMeetingMemberId(Long meetingMemberId) {
return meetingAnswerQueryRepository.findAllByMeetingMemberId(meetingMemberId);
return meetingAnswerQueryRepository.findAllByOtherMeetingMemberId(meetingMemberId);
}

@Override
public MyMeetingAnswerListResponse findAllByMyMeetingMemberId(Long loginMemberId) {
return meetingAnswerQueryRepository.findAllByMyMeetingMemberId(loginMemberId);
}

@Override
public List<MeetingAnswer> findAllByMeetingQuestionIdIn(List<Long> meetingQuestionIds) {
return meetingAnswerJpaRepository.findAllByMeetingQuestionIdIn(meetingQuestionIds);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.depromeet.sambad.moring.meeting.answer.infrastructure.dto;

import java.util.List;

import org.depromeet.sambad.moring.answer.domain.Answer;

import com.querydsl.core.annotations.QueryProjection;

public record MeetingAnswerResponseCustom(
Long meetingQuestionId,
String meetingQuestionTitle,
List<Answer> meetingAnswers,
String comment
) {

@QueryProjection
public MeetingAnswerResponseCustom {
}

public List<String> getMeetingAnswers() {
return meetingAnswers.stream()
.map(Answer::getContent)
.toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
import com.querydsl.core.annotations.QueryProjection;

public record MyMeetingAnswerResponseCustom(
Long meetingQuestionId,
String meetingQuestionTitle,
List<Answer> meetingAnswers,
String comment
String comment,
Boolean isHidden
) {

@QueryProjection
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
package org.depromeet.sambad.moring.meeting.answer.presentation;

import java.util.List;

import org.depromeet.sambad.moring.meeting.answer.application.MeetingAnswerResultService;
import org.depromeet.sambad.moring.meeting.answer.application.MeetingAnswerService;
import org.depromeet.sambad.moring.meeting.answer.presentation.request.MeetingAnswerRequest;
import org.depromeet.sambad.moring.meeting.answer.presentation.response.MeetingAnswerListResponse;
import org.depromeet.sambad.moring.meeting.answer.presentation.response.MyMeetingAnswerListResponse;
import org.depromeet.sambad.moring.meeting.answer.presentation.response.SelectedAnswerResponse;
import org.depromeet.sambad.moring.meeting.member.presentation.response.MeetingMemberListResponse;
import org.depromeet.sambad.moring.user.presentation.resolver.UserId;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import io.swagger.v3.oas.annotations.Operation;
Expand All @@ -27,7 +33,7 @@
@Tag(name = "모임원의 답변", description = "모임 내 릴레이 질문에 대한 답변 api / 담당자 : 김나현")
@RestController
@RequiredArgsConstructor
@RequestMapping("/v1")
@RequestMapping("/v1/meetings/{meetingId}")
public class MeetingAnswerController {

private final MeetingAnswerService meetingAnswerService;
Expand All @@ -41,7 +47,7 @@ public class MeetingAnswerController {
+ "NOT_FOUND_ANSWER"),
@ApiResponse(responseCode = "409", description = "DUPLICATE_MEETING_ANSWER / FINISHED_MEETING_QUESTION")
})
@PostMapping("/meetings/{meetingId}/questions/{meetingQuestionId}/answers")
@PostMapping("/questions/{meetingQuestionId}/answers")
public ResponseEntity<Object> save(
@UserId Long userId,
@Parameter(description = "모임 ID", example = "1", required = true) @PathVariable("meetingId") Long meetingId,
Expand All @@ -58,27 +64,29 @@ public ResponseEntity<Object> save(
+ "- 답변이 없다면, content는 빈 배열 [] 을 반환합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200"),
@ApiResponse(responseCode = "403", description = "USER_NOT_MEMBER_OF_MEETING")
@ApiResponse(responseCode = "403", description = "USER_NOT_MEMBER_OF_MEETING"),
@ApiResponse(responseCode = "404", description = "MEETING_MEMBER_NOT_FOUND")
})
@GetMapping("/meetings/{meetingId}/questions/answers/me")
public ResponseEntity<MeetingAnswerListResponse> findMyList(
@GetMapping("/questions/answers/me")
public ResponseEntity<MyMeetingAnswerListResponse> findMyList(
@UserId Long userId,
@Parameter(description = "모임 ID", example = "1", required = true) @PathVariable("meetingId") Long meetingId
) {
MeetingAnswerListResponse response = meetingAnswerService.getListByMe(userId, meetingId);
MyMeetingAnswerListResponse response = meetingAnswerService.getListByMe(userId, meetingId);
return ResponseEntity.ok(response);
}

@Operation(summary = "모임원이 작성한 모든 질문의 답변 리스트 조회", description =
@Operation(summary = "다른 모임원이 작성한 모든 질문의 답변 리스트 조회", description =
"- 프로필 페이지 내 릴레이 질문 영역 조회 시 사용합니다.\n"
+ "- 생성 순으로 오름차순 정렬하여 반환합니다.\n"
+ "- 답변이 없다면, content는 빈 배열 [] 을 반환합니다.")
+ "- 답변이 없다면, content는 빈 배열 [] 을 반환합니다.\n"
+ "- 숨김 처리 되어 있는 답변을 필터링 후 반환합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200"),
@ApiResponse(responseCode = "403", description = "USER_NOT_MEMBER_OF_MEETING"),
@ApiResponse(responseCode = "404", description = "MEETING_MEMBER_NOT_FOUND")
})
@GetMapping("/meetings/{meetingId}/members/{memberId}/questions/answers")
@GetMapping("/members/{memberId}/questions/answers")
public ResponseEntity<MeetingAnswerListResponse> findMemberList(
@UserId Long userId,
@Parameter(description = "모임 ID", example = "1", required = true) @PathVariable("meetingId") Long meetingId,
Expand All @@ -95,7 +103,7 @@ public ResponseEntity<MeetingAnswerListResponse> findMemberList(
@ApiResponse(responseCode = "403", description = "USER_NOT_MEMBER_OF_MEETING"),
@ApiResponse(responseCode = "404", description = "NOT_FOUND_MEETING_QUESTION")
})
@GetMapping("/meetings/{meetingId}/questions/{meetingQuestionId}/answers/most-selected")
@GetMapping("/questions/{meetingQuestionId}/answers/most-selected")
public ResponseEntity<SelectedAnswerResponse> getMostSelectedMeetingAnswer(
@UserId Long userId,
@Parameter(description = "모임 ID", example = "1", required = true) @PathVariable("meetingId") Long meetingId,
Expand All @@ -116,7 +124,7 @@ public ResponseEntity<SelectedAnswerResponse> getMostSelectedMeetingAnswer(
@ApiResponse(responseCode = "403", description = "USER_NOT_MEMBER_OF_MEETING"),
@ApiResponse(responseCode = "404", description = "MEETING_MEMBER_NOT_FOUND")
})
@GetMapping("/meetings/{meetingId}/questions/{meetingQuestionId}/answers/selected-same")
@GetMapping("/questions/{meetingQuestionId}/answers/selected-same")
public ResponseEntity<SelectedAnswerResponse> getSelectedSameMeetingAnswers(
@UserId Long userId,
@Parameter(description = "모임 ID", example = "1", required = true) @PathVariable("meetingId") Long meetingId,
Expand All @@ -129,4 +137,22 @@ public ResponseEntity<SelectedAnswerResponse> getSelectedSameMeetingAnswers(
? ResponseEntity.noContent().build()
: ResponseEntity.ok(response);
}

@Operation(summary = "릴레이 질문-답변 리스트 숨김 요청", description = "- 자기소개 페이지에서 숨기고 싶은 릴레이 질문 목록을 저장합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "204", description = "숨김 처리 성공"),
@ApiResponse(responseCode = "403", description = "USER_NOT_MEMBER_OF_MEETING / CANNOT_UPDATE_MEETING_ANSWER")
})
@PatchMapping("/hidden")
public ResponseEntity<MeetingMemberListResponse> updateMeetingQuestionHidden(
@UserId Long userId,
@Parameter(description = "모임 ID", example = "1", required = true)
@Positive @PathVariable Long meetingId,
@Parameter(description = "숨길 모임 질문 ID 리스트", example = "1,2,3", required = true)
@RequestParam(value = "hiddenMeetingQuestionIds")
List<Long> hiddenMeetingQuestionIds
) {
meetingAnswerService.updateHidden(userId, meetingId, hiddenMeetingQuestionIds);
return ResponseEntity.noContent().build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.depromeet.sambad.moring.meeting.answer.presentation.exception;

import static org.depromeet.sambad.moring.meeting.answer.presentation.exception.MeetingAnswerExceptionCode.*;

import org.depromeet.sambad.moring.common.exception.BusinessException;

public class CannotUpdateMeetingAnswer extends BusinessException {
public CannotUpdateMeetingAnswer() {
super(CANNOT_UPDATE_MEETING_ANSWER);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
@Getter
@RequiredArgsConstructor
public enum MeetingAnswerExceptionCode implements ExceptionCode {

CANNOT_UPDATE_MEETING_ANSWER(FORBIDDEN, "수정 권한이 없는 릴레이 질문 답변입니다."),

DUPLICATE_MEETING_ANSWER(CONFLICT, "등록된 답변이 존재합니다."),
;

Expand Down
Loading

0 comments on commit b5ccb71

Please sign in to comment.