Skip to content

Commit

Permalink
Merge branch 'dev' into feat/26/JWT-Logout
Browse files Browse the repository at this point in the history
  • Loading branch information
dudxo authored Aug 10, 2024
2 parents 21043a1 + 0b2758e commit 128daa1
Show file tree
Hide file tree
Showing 23 changed files with 392 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,21 @@
import com.dnd.gongmuin.common.dto.PageResponse;
import com.dnd.gongmuin.member.domain.Member;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;

@Tag(name = "답변 API")
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/question-posts")
public class AnswerController {
private final AnswerService answerService;

@Operation(summary = "답변 등록 API", description = "질문글에 대한 답변을 작성한다.")
@ApiResponse(useReturnTypeSchema = true)
@PostMapping("/{questionPostId}/answers")
public ResponseEntity<AnswerDetailResponse> registerAnswer(
@PathVariable Long questionPostId,
Expand All @@ -34,11 +40,24 @@ public ResponseEntity<AnswerDetailResponse> registerAnswer(
return ResponseEntity.ok(response);
}

@Operation(summary = "답변 조회 API", description = "질문글에 속하는 답변을 모두 조회한다.")
@ApiResponse(useReturnTypeSchema = true)
@GetMapping("/{questionPostId}/answers")
public ResponseEntity<PageResponse<AnswerDetailResponse>> getAnswersByQuestionPostId(
@PathVariable Long questionPostId
) {
PageResponse<AnswerDetailResponse> response = answerService.getAnswersByQuestionPostId(questionPostId);
return ResponseEntity.ok(response);
}

@Operation(summary = "답변 채택 API", description = "질문자가 답변을 채택한다.")
@ApiResponse(useReturnTypeSchema = true)
@PostMapping("/answers/{answerId}")
public ResponseEntity<AnswerDetailResponse> getAnswersByQuestionPostId(
@PathVariable Long answerId,
@AuthenticationPrincipal Member member
) {
AnswerDetailResponse response = answerService.chooseAnswer(answerId, member);
return ResponseEntity.ok(response);
}
}
4 changes: 4 additions & 0 deletions src/main/java/com/dnd/gongmuin/answer/domain/Answer.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,8 @@ public static Answer of(String content, boolean isQuestioner, Long questionPostI
return new Answer(content, isQuestioner, questionPostId, member);
}

public void updateIsChosen() {
this.isChosen = true;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,4 @@ public record RegisterAnswerRequest(
@NotBlank(message = "답변을 입력해주세요.")
String content
) {
public static RegisterAnswerRequest from(
String content
) {
return new RegisterAnswerRequest(content);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.dnd.gongmuin.answer.exception;

import com.dnd.gongmuin.common.exception.ErrorCode;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public enum AnswerErrorCode implements ErrorCode {

NOT_FOUND_ANSWER("해당 아이디의 답변이 존재하지 않습니다.", "ANS_001"),
ALREADY_CHOSEN_ANSWER_EXISTS("채택한 답변이 존재합니다.", "ANS_02");

private final String message;
private final String code;
}
48 changes: 42 additions & 6 deletions src/main/java/com/dnd/gongmuin/answer/service/AnswerService.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@
import com.dnd.gongmuin.answer.dto.AnswerDetailResponse;
import com.dnd.gongmuin.answer.dto.AnswerMapper;
import com.dnd.gongmuin.answer.dto.RegisterAnswerRequest;
import com.dnd.gongmuin.answer.exception.AnswerErrorCode;
import com.dnd.gongmuin.answer.repository.AnswerRepository;
import com.dnd.gongmuin.common.dto.PageMapper;
import com.dnd.gongmuin.common.dto.PageResponse;
import com.dnd.gongmuin.common.exception.runtime.NotFoundException;
import com.dnd.gongmuin.common.exception.runtime.ValidationException;
import com.dnd.gongmuin.credit_history.service.CreditHistoryService;
import com.dnd.gongmuin.member.domain.Member;
import com.dnd.gongmuin.question_post.domain.QuestionPost;
import com.dnd.gongmuin.question_post.exception.QuestionPostErrorCode;
Expand All @@ -25,18 +28,22 @@ public class AnswerService {

private final QuestionPostRepository questionPostRepository;
private final AnswerRepository answerRepository;
private final CreditHistoryService creditHistoryService;

private static void validateIfQuestioner(Member member, QuestionPost questionPost) {
if (!questionPost.isQuestioner(member)) {
throw new ValidationException(QuestionPostErrorCode.NOT_AUTHORIZED);
}
}

@Transactional
public AnswerDetailResponse registerAnswer(
Long questionPostId,
RegisterAnswerRequest request,
Member member
) {
QuestionPost questionPost = questionPostRepository.findById(questionPostId)
.orElseThrow(() -> new NotFoundException(QuestionPostErrorCode.NOT_FOUND_QUESTION_POST));
boolean isQuestioner
= questionPost.getMember().getId().equals(member.getId());
Answer answer = AnswerMapper.toAnswer(questionPostId, isQuestioner, request, member);
QuestionPost questionPost = findQuestionPostById(questionPostId);
Answer answer = AnswerMapper.toAnswer(questionPostId, questionPost.isQuestioner(member), request, member);
return AnswerMapper.toAnswerDetailResponse(answerRepository.save(answer));
}

Expand All @@ -49,11 +56,40 @@ public PageResponse<AnswerDetailResponse> getAnswersByQuestionPostId(Long questi
return PageMapper.toPageResponse(answerResponsePage);
}

@Transactional
public AnswerDetailResponse chooseAnswer(
Long answerId,
Member member
) {
Answer answer = getAnswerById(answerId);
QuestionPost questionPost = findQuestionPostById(answer.getQuestionPostId());
validateIfQuestioner(member, questionPost);
chooseAnswer(questionPost, answer);

return AnswerMapper.toAnswerDetailResponse(answer);
}

private void chooseAnswer(QuestionPost questionPost, Answer answer) {
questionPost.updateIsChosen(answer);
answer.getMember().increaseCredit(questionPost.getReward());
questionPost.getMember().decreaseCredit(questionPost.getReward());
creditHistoryService.saveChosenCreditHistory(questionPost, answer);
}

private void validateIfQuestionPostExists(Long questionPostId) {
boolean isExists = questionPostRepository.existsById(questionPostId);
if (!isExists) {
throw new NotFoundException(QuestionPostErrorCode.NOT_FOUND_QUESTION_POST);
}
}

}
private Answer getAnswerById(Long answerId) {
return answerRepository.findById(answerId)
.orElseThrow(() -> new NotFoundException(AnswerErrorCode.NOT_FOUND_ANSWER));
}

private QuestionPost findQuestionPostById(Long questionPostId) {
return questionPostRepository.findById(questionPostId)
.orElseThrow(() -> new NotFoundException(QuestionPostErrorCode.NOT_FOUND_QUESTION_POST));
}
}
27 changes: 0 additions & 27 deletions src/main/java/com/dnd/gongmuin/credit/CreditDetail.java

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.dnd.gongmuin.credit;
package com.dnd.gongmuin.credit_history;

import static jakarta.persistence.FetchType.*;

Expand All @@ -15,27 +15,25 @@
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Credit extends TimeBaseEntity {
public class CreditHistory extends TimeBaseEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "credit_id", nullable = false)
@Column(name = "credit_history_id", nullable = false)
private Long id;

@Enumerated(EnumType.STRING)
@Column(name = "type", nullable = false)
private CreditType type;

@Enumerated(EnumType.STRING)
@Column(name = "detail", nullable = false)
private CreditDetail detail;
private String detail;

@Column(name = "amount", nullable = false)
private int amount;
Expand All @@ -44,11 +42,14 @@ public class Credit extends TimeBaseEntity {
@JoinColumn(name = "member_id", nullable = false) // 정합성 중요
private Member member;

@Builder
public Credit(CreditType type, CreditDetail detail, int amount, Member member) {
private CreditHistory(CreditType type, String detail, int amount, Member member) {
this.type = type;
this.detail = detail;
this.amount = amount;
this.member = member;
}

public static CreditHistory of(CreditType type, String detail, int amount, Member member) {
return new CreditHistory(type, detail, amount, member);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.dnd.gongmuin.credit;
package com.dnd.gongmuin.credit_history;

import java.util.Arrays;

Expand All @@ -9,12 +9,13 @@
@RequiredArgsConstructor
public enum CreditType {

CHOOSE("채택하기"),
CHOSEN("채택받기"),
CHAT_REQUEST("채팅신청"),
CHAT_ACCEPT("채팅받기");
CHOOSE("채택하기", "출금"),
CHOSEN("채택받기", "입금"),
CHAT_REQUEST("채팅신청", "출금"),
CHAT_ACCEPT("채팅받기", "입금");

private final String label;
private final String detail;

public static CreditType of(String input) {
return Arrays.stream(values())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.dnd.gongmuin.credit_history.dto;

import com.dnd.gongmuin.credit_history.CreditHistory;
import com.dnd.gongmuin.credit_history.CreditType;
import com.dnd.gongmuin.member.domain.Member;

import lombok.AccessLevel;
import lombok.NoArgsConstructor;

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class CreditHistoryMapper {
public static CreditHistory toCreditHistory(CreditType creditType, int reward, Member member) {
return CreditHistory.of(creditType, creditType.getDetail(), reward, member);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.dnd.gongmuin.credit_history.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import com.dnd.gongmuin.credit_history.CreditHistory;

public interface CreditHistoryRepository extends JpaRepository<CreditHistory, Long> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.dnd.gongmuin.credit_history.service;

import java.util.List;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.dnd.gongmuin.answer.domain.Answer;
import com.dnd.gongmuin.credit_history.CreditType;
import com.dnd.gongmuin.credit_history.dto.CreditHistoryMapper;
import com.dnd.gongmuin.credit_history.repository.CreditHistoryRepository;
import com.dnd.gongmuin.question_post.domain.QuestionPost;

import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class CreditHistoryService {

private final CreditHistoryRepository creditHistoryRepository;

@Transactional
public void saveChosenCreditHistory(QuestionPost questionPost, Answer answer) {
creditHistoryRepository.saveAll(List.of(
CreditHistoryMapper.toCreditHistory(CreditType.CHOSEN, questionPost.getReward(), answer.getMember()),
CreditHistoryMapper.toCreditHistory(CreditType.CHOOSE, questionPost.getReward(), questionPost.getMember())
));
}
}
13 changes: 13 additions & 0 deletions src/main/java/com/dnd/gongmuin/member/domain/Member.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import static lombok.AccessLevel.*;

import com.dnd.gongmuin.common.entity.TimeBaseEntity;
import com.dnd.gongmuin.common.exception.runtime.ValidationException;
import com.dnd.gongmuin.member.exception.MemberErrorCode;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
Expand Down Expand Up @@ -93,4 +95,15 @@ public void updateAdditionalInfo(String nickname, String officialEmail,
this.jobCategory = jobCategory;
}

public void decreaseCredit(int credit) {
if (this.credit < credit) {
throw new ValidationException(MemberErrorCode.NOT_ENOUGH_CREDIT);
}
this.credit -= credit;
}

public void increaseCredit(int credit) {
this.credit += credit;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ public enum MemberErrorCode implements ErrorCode {

NOT_FOUND_MEMBER("특정 회원을 찾을 수 없습니다.", "MEMBER_001"),
NOT_FOUND_NEW_MEMBER("신규 회원이 아닙니다.", "MEMBER_002"),
FAIL_LOGOUT("로그아웃을 실패했습니다.", "MEMBER_003");
FAIL_LOGOUT("로그아웃을 실패했습니다.", "MEMBER_003"),
NOT_ENOUGH_CREDIT("보유한 크레딧이 부족합니다.", "MEMBER_004");

private final String message;
private final String code;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;

@Tag(name = "질문글 API")
Expand All @@ -27,20 +28,20 @@ public class QuestionPostController {

private final QuestionPostService questionPostService;

@PostMapping
@Operation(summary = "질문글 등록 API", description = "질문글을 등록한다")
@ApiResponse(useReturnTypeSchema = true)
@PostMapping
public ResponseEntity<QuestionPostDetailResponse> registerQuestionPost(
@RequestBody RegisterQuestionPostRequest request,
@Valid @RequestBody RegisterQuestionPostRequest request,
@AuthenticationPrincipal Member member
) {
QuestionPostDetailResponse response = questionPostService.registerQuestionPost(request, member);
return ResponseEntity.ok(response);
}

@GetMapping("/{questionPostId}")
@Operation(summary = "질문글 상세 조회 API", description = "질문글을 아이디로 상세조회한다.")
@ApiResponse(useReturnTypeSchema = true)
@GetMapping("/{questionPostId}")
public ResponseEntity<QuestionPostDetailResponse> getQuestionPostById(
@PathVariable("questionPostId") Long questionPostId
) {
Expand Down
Loading

0 comments on commit 128daa1

Please sign in to comment.