Skip to content

Commit

Permalink
feat/LS-26: 특정 회고 작성 API (#34)
Browse files Browse the repository at this point in the history
* add: dto 추가

* feat: 회고 작성 로직 구현

* feat: 변환 로직 구현

* feat: 질문 관련 일급컬렉션 구현

* add: 예외 추가

* del: 컨버터 삭제

* del: 컨버터 삭제

* chore: Answers 로직 수정

* del: 컨버터 삭제

* chore: 예외 추가

* chore: 검증로직 추가
  • Loading branch information
mikekks authored Jul 15, 2024
1 parent dee5506 commit e40c038
Show file tree
Hide file tree
Showing 23 changed files with 316 additions and 37 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.layer.domain.answer.controller;

import org.layer.common.annotation.MemberId;
import org.layer.domain.answer.controller.dto.request.AnswerListCreateRequest;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;

@Tag(name = "회고 작성", description = "회고 작성 관련 API")
public interface AnswerApi {

@Operation(summary = "회고 작성", description = "")
ResponseEntity<Void> createAnswer(@PathVariable("spaceId") Long spaceId, @PathVariable("retrospectId") Long retrospectId,
@RequestBody @Valid AnswerListCreateRequest request, @MemberId Long memberId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package org.layer.domain.answer.controller;

import java.util.List;

import org.layer.common.annotation.MemberId;
import org.layer.domain.answer.controller.dto.request.AnswerListCreateRequest;
import org.layer.domain.answer.service.AnswerService;
import org.layer.domain.answer.service.dto.request.AnswerCreateServiceRequest;
import org.layer.domain.answer.service.dto.request.AnswerListCreateServiceRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
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.RestController;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;

@RestController
@RequiredArgsConstructor
@RequestMapping("/space/{spaceId}/retrospect/{retrospectId}/answer")
public class AnswerController implements AnswerApi {
private final AnswerService answerService;

@Override
@PostMapping
@PreAuthorize("isAuthenticated()")
public ResponseEntity<Void> createAnswer(@PathVariable("spaceId") Long spaceId,
@PathVariable("retrospectId") Long retrospectId,
@RequestBody @Valid AnswerListCreateRequest request, @MemberId Long memberId) {

List<AnswerCreateServiceRequest> requests = request.requests().stream()
.map(r -> AnswerCreateServiceRequest.of(r.questionId(), r.questionType(), r.answer()))
.toList();

answerService.create(AnswerListCreateServiceRequest.of(requests), spaceId, retrospectId, memberId);

return ResponseEntity.status(HttpStatus.CREATED).build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.layer.domain.answer.controller.dto.request;

public record AnswerCreateRequest(
Long questionId,
String questionType,
String answer

) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.layer.domain.answer.controller.dto.request;

import java.util.List;

public record AnswerListCreateRequest(
List<AnswerCreateRequest> requests
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package org.layer.domain.answer.service;

import static org.layer.common.exception.MemberSpaceRelationExceptionType.*;

import java.util.List;
import java.util.Optional;

import org.layer.domain.answer.entity.Answer;
import org.layer.domain.answer.entity.Answers;
import org.layer.domain.answer.repository.AnswerRepository;
import org.layer.domain.answer.service.dto.request.AnswerCreateServiceRequest;
import org.layer.domain.answer.service.dto.request.AnswerListCreateServiceRequest;
import org.layer.domain.question.entity.Questions;
import org.layer.domain.question.enums.QuestionType;
import org.layer.domain.question.repository.QuestionRepository;
import org.layer.domain.retrospect.repository.RetrospectRepository;
import org.layer.domain.space.entity.MemberSpaceRelation;
import org.layer.domain.space.exception.MemberSpaceRelationException;
import org.layer.domain.space.repository.MemberSpaceRelationRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class AnswerService {
private final AnswerRepository answerRepository;
private final MemberSpaceRelationRepository memberSpaceRelationRepository;
private final RetrospectRepository retrospectRepository;
private final QuestionRepository questionRepository;

public void create(AnswerListCreateServiceRequest request, Long spaceId, Long retrospectId, Long memberId) {
// 스페이스 팀원인지 검증
Optional<MemberSpaceRelation> team = memberSpaceRelationRepository.findBySpaceIdAndMemberId(
spaceId, memberId);
if (team.isEmpty()) {
throw new MemberSpaceRelationException(NOT_FOUND_MEMBER_SPACE_RELATION);
}

// 회고 존재 검증
retrospectRepository.findByIdOrThrow(retrospectId);

// 회고 질문 유효성 검사 - 해당 회고에 속해있는 질문인지
List<Long> questionIds = request.requests().stream()
.map(AnswerCreateServiceRequest::questionId)
.toList();
Questions questions = new Questions(questionRepository.findAllByIdIn(questionIds));

questions.validateQuestionSize(questionIds.size());

// 회고 질문 유효성 검사 - 이미 응답을 하지 않았는지
Answers answers = new Answers(
answerRepository.findByRetrospectIdAndMemberIdAndQuestionIdIn(retrospectId, memberId, questionIds));
answers.validateNoAnswer();

request.requests().forEach(
r -> {
// 회고 질문 유효성 검사 - 각각의 질문들이 유효한지
questions.validateIdAndQuestionType(r.questionId(), QuestionType.stringToEnum(r.questionType()));
Answer answer = new Answer(retrospectId, r.questionId(), memberId, r.answer());
answerRepository.save(answer);
});



}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.layer.domain.answer.service.dto.request;

public record AnswerCreateServiceRequest(
Long questionId,
String questionType,
String answer
) {
public static AnswerCreateServiceRequest of(Long questionId, String questionType, String answer){
return new AnswerCreateServiceRequest(questionId, questionType, answer);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.layer.domain.answer.service.dto.request;

import java.util.List;

public record AnswerListCreateServiceRequest(
List<AnswerCreateServiceRequest> requests
) {
public static AnswerListCreateServiceRequest of(List<AnswerCreateServiceRequest> requests){
return new AnswerListCreateServiceRequest(requests);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ public record RetrospectGetResponse(
@Schema(description = "회고 상태 : PROCEEDING 나 DONE 중에 하나입니다.", example = "PROCEEDING")
RetrospectStatus retrospectStatus,
@Schema(description = "해당 회고 응답 수", example = "4")
int writeCount
long writeCount
) {
public static RetrospectGetResponse of(String title, String introduction, boolean isWrite, RetrospectStatus retrospectStatus,
int writeCount){
long writeCount){

return new RetrospectGetResponse(title, introduction, isWrite, retrospectStatus, writeCount);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public RetrospectListGetServiceResponse getRetrospects(Long spaceId, Long member

List<RetrospectGetServiceResponse> retrospectDtos = retrospects.stream()
.map(r -> RetrospectGetServiceResponse.of(r.getTitle(), r.getIntroduction(),
answers.hasRetrospectAnswer(memberId), r.getRetrospectStatus(), answers.getWriteCount()))
answers.hasRetrospectAnswer(memberId, r.getId()), r.getRetrospectStatus(), answers.getWriteCount(r.getId())))
.toList();

return RetrospectListGetServiceResponse.of(retrospects.size(), retrospectDtos);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ public record RetrospectGetServiceResponse(
String introduction,
boolean isWrite,
RetrospectStatus retrospectStatus,
int writeCount
long writeCount
) {
public static RetrospectGetServiceResponse of(String title, String introduction, boolean isWrite,
RetrospectStatus retrospectStatus, int writeCount){
RetrospectStatus retrospectStatus, long writeCount){
return new RetrospectGetServiceResponse(title, introduction, isWrite, retrospectStatus, writeCount);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.layer.common.exception;

import org.springframework.http.HttpStatus;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public enum AnswerExceptionType implements ExceptionType{
ALREADY_ANSWERED(HttpStatus.BAD_REQUEST, "이미 응답한 질문이 있습니다.");

private final HttpStatus status;
private final String message;

@Override
public HttpStatus httpStatus() {
return status;
}

@Override
public String message() {
return message;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.layer.common.exception;

import org.springframework.http.HttpStatus;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public enum QuestionExceptionType implements ExceptionType{
INVALID_QUESTION(HttpStatus.BAD_REQUEST, "유효하지 않은 질문 입니다."),
INVALID_QUESTION_TYPE(HttpStatus.BAD_REQUEST, "유효하지 않은 질문 타입입니다."),
INVALID_QUESTION_SIZE(HttpStatus.BAD_REQUEST, "요청한 질문 응답 수가 유효하지 않습니다.");

private final HttpStatus status;
private final String message;

@Override
public HttpStatus httpStatus() {
return status;
}

@Override
public String message() {
return message;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import jakarta.validation.constraints.NotNull;
Expand All @@ -30,5 +31,11 @@ public class Answer {
@NotNull
private String content;


@Builder
public Answer(Long retrospectId, Long questionId, Long memberId, String content) {
this.retrospectId = retrospectId;
this.questionId = questionId;
this.memberId = memberId;
this.content = content;
}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,34 @@
package org.layer.domain.answer.entity;

import static org.layer.common.exception.AnswerExceptionType.*;

import java.util.List;

import org.layer.domain.answer.exception.AnswerException;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public class Answers {
private static final int ZERO = 0;

private final List<Answer> answers;

public boolean hasRetrospectAnswer(Long memberId) {
public boolean hasRetrospectAnswer(Long memberId, Long retrospectId) {
return answers.stream()
.filter(answer -> answer.getRetrospectId().equals(retrospectId))
.anyMatch(answer -> answer.getMemberId().equals(memberId));
}

public int getWriteCount() {
return answers.size();
public long getWriteCount(Long retrospectId) {
return answers.stream()
.filter(answer -> answer.getRetrospectId().equals(retrospectId))
.count();
}

public void validateNoAnswer(){
if(answers.size() != ZERO){
throw new AnswerException(ALREADY_ANSWERED);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.layer.domain.answer.exception;

import org.layer.common.exception.BaseCustomException;
import org.layer.common.exception.ExceptionType;

public class AnswerException extends BaseCustomException {
public AnswerException(ExceptionType exceptionType) {
super(exceptionType);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@

public interface AnswerRepository extends JpaRepository<Answer, Long> {
List<Answer> findAllByRetrospectIdIn(List<Long> retrospectIds);

List<Answer> findByRetrospectIdAndMemberIdAndQuestionIdIn(Long retrospectId, Long memberId, List<Long> questionId);
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.layer.domain.BaseEntity;
import org.layer.domain.question.converter.QuestionTypeConverter;
import org.layer.domain.question.enums.QuestionOwner;
import org.layer.domain.question.enums.QuestionType;
import org.layer.domain.questionOption.entity.QuestionOption;
Expand Down
Loading

0 comments on commit e40c038

Please sign in to comment.