Skip to content

Commit

Permalink
Merge pull request #33 from Parkjyun/feat/31
Browse files Browse the repository at this point in the history
[feat] 토론 투표, 토론 댓글 관련 api 생성
  • Loading branch information
Parkjyun authored Mar 21, 2024
2 parents f0cbec6 + db49e61 commit 14e10d7
Show file tree
Hide file tree
Showing 16 changed files with 261 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ public enum DebateFailureCode implements FailureCode {
/**
* 404 Not Found
*/
DEBATE_NOT_FOUND(HttpStatus.NOT_FOUND, "존재하지 않는 토론입니다.");
DEBATE_NOT_FOUND(HttpStatus.NOT_FOUND, "존재하지 않는 토론입니다."),

/**
* 409 Conflict
*/
ALREADY_VOTED_DEBATE(HttpStatus.NOT_FOUND, "이미 투표햤습니다");

private final HttpStatus httpStatus;
private final String message;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.newsnack.www.newsnackserver.common.code.failure;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;

@Getter
@RequiredArgsConstructor
public enum DebateParticipationFailureCode implements FailureCode {
/**
* 404 Not Found
*/
NOT_PARTICIPATED_DEBATE(HttpStatus.NOT_FOUND,"투표를 먼저 하셔야 합니다"),
NOT_FOUND_DEBATE(HttpStatus.NOT_FOUND, "토론이 없습니다"),
/**
* 409 Conflict
*/
ALREADY_COMMENTED_DEBATE(HttpStatus.CONFLICT,"이미 작성한 댓글입니다");


private final HttpStatus httpStatus;
private final String message;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.newsnack.www.newsnackserver.common.code.success;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;

@Getter
@RequiredArgsConstructor
public enum DebateParticipationSuccessCode implements SuccessCode{
/**
* 200 OK
**/
DEBATE_PARTICIPATION_SUCCESS(HttpStatus.OK, "토론 댓글 작성 성공");

private final HttpStatus httpStatus;
private final String message;
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ public enum DebateSuccessCode implements SuccessCode{
/**
* 200 OK
**/
GET_DEBATES_SUCCESS(HttpStatus.OK, "토론 조회 성공");
GET_DEBATES_SUCCESS(HttpStatus.OK, "토론 조회 성공"),
/**
* 201 CREATED
**/
DEBATE_VOTE_SUCCESS(HttpStatus.CREATED, "투표 성공");

private final HttpStatus httpStatus;
private final String message;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.newsnack.www.newsnackserver.common.exception;

import com.newsnack.www.newsnackserver.common.code.failure.FailureCode;
import lombok.Getter;

@Getter
public class DebateParticipationException extends RuntimeException{
private final FailureCode failureCode;
public DebateParticipationException(FailureCode failureCode) {
super("[DebateParticipationException] : " + failureCode.getMessage());
this.failureCode = failureCode;
}

public int getHttpStatusCode() {
return failureCode.getHttpStatus().value();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@ public ResponseEntity<NewSnackResponse<?>> handleDebateException(DebateException
.body(NewSnackResponse.error(e.getFailureCode()));
}

@ExceptionHandler(DebateParticipationException.class)
public ResponseEntity<NewSnackResponse<?>> handleDebateParticipationException(DebateParticipationException e) {
log.info("handleDebateParticipationException() in NewSnackControllerAdvice throw DebateParticipationException : {}", e.getMessage());
return ResponseEntity.status(e.getHttpStatusCode())
.body(NewSnackResponse.error(e.getFailureCode()));
}

@ExceptionHandler(MemberException.class)
public ResponseEntity<NewSnackResponse<?>> handleMemberException(MemberException e) {
log.info("handleMemberException() in NewSnackControllerAdvice throw MemberException : {}", e.getMessage());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
package com.newsnack.www.newsnackserver.controller;

import com.newsnack.www.newsnackserver.annotation.MemberId;
import com.newsnack.www.newsnackserver.common.code.success.DebateParticipationSuccessCode;
import com.newsnack.www.newsnackserver.common.code.success.DebateSuccessCode;
import com.newsnack.www.newsnackserver.common.response.NewSnackResponse;
import com.newsnack.www.newsnackserver.controller.parameter.SearchOrder;
import com.newsnack.www.newsnackserver.dto.request.DebateParticipationRequest;
import com.newsnack.www.newsnackserver.dto.request.DebateVoteRequest;
import com.newsnack.www.newsnackserver.dto.response.DebateCommentResponse;
import com.newsnack.www.newsnackserver.dto.response.DebateIndividualResponse;
import com.newsnack.www.newsnackserver.dto.response.DebateMainPageResponse;
import com.newsnack.www.newsnackserver.service.DebateParticipationService;
import com.newsnack.www.newsnackserver.service.DebateService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;

import java.util.List;

Expand All @@ -20,6 +24,7 @@
public class DebateController {

private final DebateService debateService;
private final DebateParticipationService debateParticipationService;

@GetMapping
public NewSnackResponse<List<DebateMainPageResponse>> getDebates() {
Expand All @@ -35,4 +40,22 @@ public NewSnackResponse<DebateMainPageResponse> getMainDebate() {
public NewSnackResponse<DebateIndividualResponse> getDebate(@PathVariable Long debateId, @MemberId(isForSecuredApi = false) Long memberId) {
return NewSnackResponse.success(DebateSuccessCode.GET_DEBATES_SUCCESS, debateService.getDebate(debateId, memberId));
}

@PostMapping("/{debateId}/votes")
public NewSnackResponse<?> voteDebate(@PathVariable Long debateId, @MemberId Long memberId, @RequestBody DebateVoteRequest debateVoteRequest) {
debateService.voteDebate(debateId, memberId, debateVoteRequest.vote());
return NewSnackResponse.success(DebateSuccessCode.DEBATE_VOTE_SUCCESS);
}

@PostMapping("/{debateId}/comments")
public NewSnackResponse<?> createDebateComment(@PathVariable Long debateId, @MemberId Long memberId, @RequestBody @Valid DebateParticipationRequest request) {
debateParticipationService.participateDebate(debateId, memberId, request.content());
return NewSnackResponse.success(DebateParticipationSuccessCode.DEBATE_PARTICIPATION_SUCCESS);
}

@GetMapping("/{debateId}/comments")
public NewSnackResponse<List<DebateCommentResponse>> getDebateComments(@PathVariable Long debateId, @MemberId(isForSecuredApi = false) Long memberId,
@RequestParam SearchOrder order) {
return NewSnackResponse.success(DebateSuccessCode.GET_DEBATES_SUCCESS, debateParticipationService.getDebateComments(debateId, memberId, order));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,12 @@ public class Debate extends BaseTimeEntity {
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "article_id")
private Article article;

public void upVote() {
this.upVoteCount++;
}

public void downVote() {
this.downVoteCount++;
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
package com.newsnack.www.newsnackserver.domain.debateparticipation.model;

import com.newsnack.www.newsnackserver.domain.commom.BaseTimeEntity;
import com.newsnack.www.newsnackserver.domain.debate.model.Debate;
import com.newsnack.www.newsnackserver.domain.debateparticipationheart.model.DebateParticipationHeart;
import com.newsnack.www.newsnackserver.domain.member.model.Member;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.ArrayList;
import java.util.List;

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class DebateParticipation {
public class DebateParticipation extends BaseTimeEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Expand All @@ -24,8 +29,23 @@ public class DebateParticipation {
@JoinColumn(name = "member_id")
private Member member;

@OneToMany(mappedBy = "debateParticipation", cascade = CascadeType.ALL, orphanRemoval = true)//둘다 부모 삭제시 자식 모두 삭제, orphanRemoval: debateParticipationHearts.remove만 해도 DB에서 삭제됨
private List<DebateParticipationHeart> debateParticipationHearts = new ArrayList<>();

private Boolean vote;

private String comment;

private int heartCount;

public void updateComment(String comment) {
this.comment = comment;
}

public DebateParticipation(Debate debate, Member member, Boolean vote) {
this.debate = debate;
this.member = member;
this.vote = vote;
}

}
Original file line number Diff line number Diff line change
@@ -1,10 +1,25 @@
package com.newsnack.www.newsnackserver.domain.debateparticipation.repository;

import com.newsnack.www.newsnackserver.domain.debate.model.Debate;
import com.newsnack.www.newsnackserver.domain.debateparticipation.model.DebateParticipation;
import com.newsnack.www.newsnackserver.domain.member.model.Member;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

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

public interface DebateParticipationJpaRepository extends JpaRepository<DebateParticipation, Long> {
Optional<DebateParticipation> findByDebateIdAndMemberId(Long debateId, Long memberId);
Optional<DebateParticipation> findByDebateAndMember(Debate debate, Member member);

@Query("select dp from DebateParticipation dp join fetch dp.member where dp.debate.id = :debateId order by dp.id desc")
List<DebateParticipation> findAllWithMemberByDebateIdOrderByIdDescJPQL(Long debateId);

@Query("select dp from DebateParticipation dp join fetch dp.member where dp.debate.id = :debateId order by dp.heartCount desc, dp.id desc")
List<DebateParticipation> findAllWithMemberByDebateIdOrderByHeartCountDescJPQL(Long debateId);

@Query("select distinct dp from DebateParticipation dp left join fetch dp.debateParticipationHearts join fetch dp.member where dp.debate.id = :debateId order by dp.id desc")
List<DebateParticipation> findAllWithMemberAndDebateParticipationHeartByDebateIdOrderByIdDescJPQL(Long debateId);
@Query("select distinct dp from DebateParticipation dp left join fetch dp.debateParticipationHearts join fetch dp.member where dp.debate.id = :debateId order by dp.heartCount desc, dp.id desc")
List<DebateParticipation> findAllWithMemberAndDebateParticipationHeartByDebateIdOrderByHeartCountDescJPQL(Long debateId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.newsnack.www.newsnackserver.dto.request;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;

public record DebateParticipationRequest(@Size(min = 1, max = 200) @NotBlank String content) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.newsnack.www.newsnackserver.dto.request;

public record DebateVoteRequest(boolean vote) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.newsnack.www.newsnackserver.dto.response;

import com.newsnack.www.newsnackserver.domain.debateparticipation.model.DebateParticipation;

public record DebateCommentResponse(Long id, String writerName, long memberId, boolean vote, String content, String createdAt, int likeCount, boolean isLikedByMe, boolean isMyComment) {
public static DebateCommentResponse of(DebateParticipation debateParticipation, boolean isLikedByMe, boolean isMyComment) {
return new DebateCommentResponse(debateParticipation.getId(), debateParticipation.getMember().getName(), debateParticipation.getMember().getId(),
debateParticipation.getVote(), debateParticipation.getComment(),
debateParticipation.getCreatedAt().toString(), debateParticipation.getHeartCount(), isLikedByMe, isMyComment);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.List;

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
Expand All @@ -44,6 +42,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
.requestMatchers("/v1/articles/{articleId}", "/v1/articles", "/v1/articles/main").permitAll()
.requestMatchers("v1/debates/{debateId}", "/v1/debates", "/v1/debates/main").permitAll()
.requestMatchers(HttpMethod.GET, "/v1/articles/{articleId}/comments").permitAll()
.requestMatchers(HttpMethod.GET, "/v1/debates/{debateId}/comments").permitAll()
.anyRequest().authenticated())//인증 필요
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.build();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package com.newsnack.www.newsnackserver.service;

import com.newsnack.www.newsnackserver.common.code.failure.DebateParticipationFailureCode;
import com.newsnack.www.newsnackserver.common.exception.DebateParticipationException;
import com.newsnack.www.newsnackserver.controller.parameter.SearchOrder;
import com.newsnack.www.newsnackserver.domain.debate.model.Debate;
import com.newsnack.www.newsnackserver.domain.debate.repository.DebateJpaRepository;
import com.newsnack.www.newsnackserver.domain.debateparticipation.model.DebateParticipation;
import com.newsnack.www.newsnackserver.domain.debateparticipation.repository.DebateParticipationJpaRepository;
import com.newsnack.www.newsnackserver.domain.member.model.Member;
import com.newsnack.www.newsnackserver.domain.member.repository.MemberJpaRepository;
import com.newsnack.www.newsnackserver.dto.response.DebateCommentResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Transactional(readOnly = true)
@Service
@RequiredArgsConstructor
public class DebateParticipationService {
private final DebateParticipationJpaRepository debateParticipationJpaRepository;
private final DebateJpaRepository debateJpaRepository;
private final MemberJpaRepository memberJpaRepository;

@Transactional
public void participateDebate(Long debateId, Long memberId, String content) {
Debate debate = debateJpaRepository.getReferenceById(debateId);
Member member = memberJpaRepository.getReferenceById(memberId);

DebateParticipation debateParticipation = debateParticipationJpaRepository.findByDebateAndMember(debate, member)
.orElseThrow(() -> new DebateParticipationException(DebateParticipationFailureCode.NOT_PARTICIPATED_DEBATE));
if (debateParticipation.getComment() != null) {
throw new DebateParticipationException(DebateParticipationFailureCode.ALREADY_COMMENTED_DEBATE);
}
debateParticipation.updateComment(content);
}

public List<DebateCommentResponse> getDebateComments(Long debateId, Long memberId, SearchOrder searchOrder) {
Debate debate = debateJpaRepository.findById(debateId).orElseThrow(() -> new DebateParticipationException(DebateParticipationFailureCode.NOT_FOUND_DEBATE));
if(memberId == null) { // 비회원일 경우
if (searchOrder.getValue().equals(SearchOrder.RECENT.getValue()))// 최신순 정렬
return debateParticipationJpaRepository.findAllWithMemberByDebateIdOrderByIdDescJPQL(debateId)
.stream().map(debateParticipation -> DebateCommentResponse.of(debateParticipation, false, false)).toList();
if (searchOrder.getValue().equals(SearchOrder.POPULAR.getValue())) // 인기순 정렬
return debateParticipationJpaRepository.findAllWithMemberByDebateIdOrderByHeartCountDescJPQL(debateId)
.stream().map(debateParticipation -> DebateCommentResponse.of(debateParticipation, false, false)).toList();
}
//회원일 경우 1000 -> boolean 값 4개 변경 해야함
if (searchOrder.getValue().equals(SearchOrder.RECENT.getValue())) {// 최신순 정렬
return debateParticipationJpaRepository.findAllWithMemberAndDebateParticipationHeartByDebateIdOrderByIdDescJPQL(debateId)
.stream().map(debateParticipation -> DebateCommentResponse.of(debateParticipation, isLikedByMe(debateParticipation, memberId), isMyDebateParticipation(debateParticipation, memberId))).toList();
}
//인기순
return debateParticipationJpaRepository.findAllWithMemberAndDebateParticipationHeartByDebateIdOrderByHeartCountDescJPQL(debateId)
.stream().map(debateParticipation -> DebateCommentResponse.of(debateParticipation, isLikedByMe(debateParticipation, memberId), isMyDebateParticipation(debateParticipation, memberId))).toList();
}
private boolean isLikedByMe(DebateParticipation debateParticipation, Long memberId) {
return debateParticipation.getDebateParticipationHearts().stream().anyMatch(commentHeart -> commentHeart.getMember().getId().equals(memberId));
}

private boolean isMyDebateParticipation(DebateParticipation debateParticipation, Long memberId) {
return debateParticipation.getMember().getId().equals(memberId);
}

}
Loading

0 comments on commit 14e10d7

Please sign in to comment.