Skip to content

Commit

Permalink
[BE] issue195: 리뷰 수정 및 삭제 (#198)
Browse files Browse the repository at this point in the history
* test: 내가 작성한 리뷰 삭제하는 인수 테스트 작성

* feat: 내가 작성하지 않은 리뷰 삭제 시 예외 처리 구현

* refactor: 리뷰 삭제 시 리뷰 Id로만 리뷰 불러오도록 수정

* test: 내가 작성한 리뷰 수정하는 인수 테스트 작성

* feat: 내가 작성하지 않은 리뷰 수정 시 예외 처리 구현

* test: 리뷰 관련 테스트 추가

* test: 리뷰 관련 테스트 수정

* test: 리뷰 수정 및 삭제 REST Docs 테스트 추가

* feat: 인증 관련 설정

* test: 리뷰 수정 및 삭제 인수테스트 추가

* feat: 리뷰 삭제 시 Soft Delete로 변경 및 테스트 추가
  • Loading branch information
sc0116 authored Aug 4, 2022
1 parent 25f611c commit 245d832
Show file tree
Hide file tree
Showing 18 changed files with 402 additions and 39 deletions.
6 changes: 6 additions & 0 deletions backend/src/docs/asciidoc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,9 @@ operation::reviews/list[snippets='http-request,http-response']

=== 후기 특정 개수 조회
operation::reviews/list-certain-number[snippets='http-request,http-response']

=== 리뷰 수정
operation::reviews/update[snippets='http-request,http-response']

=== 리뷰 삭제
operation::reviews/delete[snippets='http-request,http-response']
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ public class AuthRequestMatchConfig {
@Bean
public AuthenticationRequestMatcher authenticationRequestMatcher() {
return new AuthenticationRequestMatcherBuilder()
.addUpAuthenticationPath(HttpMethod.POST, "/api/studies", "/api/studies/\\d+/reviews")
.addUpAuthenticationPath(HttpMethod.POST, "/api/studies", "/api/studies/\\d+/reviews", "/api/studies/\\d+/reviews/\\d+")
.addUpAuthenticationPath(HttpMethod.GET, "/api/my/studies")
.addUpAuthenticationPath(HttpMethod.PUT, "/api/studies/\\d+/reviews/\\d+")
.addUpAuthenticationPath(HttpMethod.DELETE, "/api/studies/\\d+/reviews/\\d+")
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@

import com.woowacourse.moamoa.auth.config.AuthenticationPrincipal;
import com.woowacourse.moamoa.review.service.ReviewService;
import com.woowacourse.moamoa.review.service.request.EditingReviewRequest;
import com.woowacourse.moamoa.review.service.request.WriteReviewRequest;
import java.net.URI;
import javax.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
Expand All @@ -29,4 +32,23 @@ public ResponseEntity<Void> writeReview(
final Long id = reviewService.writeReview(githubId, studyId, writeReviewRequest);
return ResponseEntity.created(URI.create("/api/studies/" + studyId + "/reviews/" + id)).build();
}

@PutMapping("/{review-id}")
public ResponseEntity<Void> updateReview(
@AuthenticationPrincipal final Long githubId,
@PathVariable(name = "review-id") final Long reviewId,
@Valid @RequestBody final EditingReviewRequest editingReviewRequest
) {
reviewService.updateReview(githubId, reviewId, editingReviewRequest);
return ResponseEntity.noContent().build();
}

@DeleteMapping("/{review-id}")
public ResponseEntity<Void> deleteReview(
@AuthenticationPrincipal final Long githubId,
@PathVariable(name = "review-id") final Long reviewId
) {
reviewService.deleteReview(githubId, reviewId);
return ResponseEntity.noContent().build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,22 @@
import static lombok.AccessLevel.PROTECTED;

import com.woowacourse.moamoa.common.entity.BaseEntity;
import com.woowacourse.moamoa.review.service.exception.UnwrittenReviewException;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.Where;

@Entity
@NoArgsConstructor(access = PROTECTED)
@AllArgsConstructor
@SQLDelete(sql = "UPDATE Review SET deleted = true WHERE id = ?")
@Where(clause = "deleted = false")
public class Review extends BaseEntity {

@Id
Expand All @@ -30,17 +35,44 @@ public class Review extends BaseEntity {
@Column(nullable = false)
private String content;

@Column(nullable = false)
private boolean deleted;

public Review(
final AssociatedStudy associatedStudy, final Reviewer reviewer, final String content
) {
this(null, associatedStudy, reviewer, content);
this(null, associatedStudy, reviewer, content, false);
}

public static Review writeNewReview(Long studyId, Long memberId, String content) {
return new Review(new AssociatedStudy(studyId), new Reviewer(memberId), content);
}

public void updateContent(final Reviewer reviewer, final String content) {
validateReviewer(reviewer);
this.content = content;
}

public void delete(final Reviewer reviewer) {
validateReviewer(reviewer);
deleted = true;
}

public void validateReviewer(final Reviewer reviewer) {
if (!this.reviewer.equals(reviewer)) {
throw new UnwrittenReviewException();
}
}

public Long getId() {
return id;
}

public String getContent() {
return content;
}

public boolean isDeleted() {
return deleted;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
import javax.persistence.Column;
import javax.persistence.Embeddable;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;

@Embeddable
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@EqualsAndHashCode
public class Reviewer {

@Column(name = "member_id", nullable = false)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package com.woowacourse.moamoa.review.domain.repository;

import com.woowacourse.moamoa.review.domain.AssociatedStudy;
import com.woowacourse.moamoa.review.domain.Review;
import java.util.List;
import java.util.Optional;

public interface ReviewRepository {

Review save(Review review);

Optional<Review> findById(Long id);

void deleteById(Long id);
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ public List<ReviewData> findAllByStudyId(final Long studyId) {
String sql = "SELECT review.id, review.content, review.created_date, review.last_modified_date, "
+ "member.github_id, member.username, member.image_url, member.profile_url "
+ "FROM review JOIN member ON review.member_id = member.id "
+ "WHERE review.study_id = :studyId";
+ "WHERE review.deleted = false "
+ "AND review.study_id = :studyId "
+ "ORDER BY review.created_date DESC ";

return namedParameterJdbcTemplate.query(sql, Map.of("studyId", studyId), rowMapper());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@
import com.woowacourse.moamoa.member.domain.repository.MemberRepository;
import com.woowacourse.moamoa.member.service.exception.MemberNotFoundException;
import com.woowacourse.moamoa.review.domain.Review;
import com.woowacourse.moamoa.review.domain.Reviewer;
import com.woowacourse.moamoa.review.domain.exception.WritingReviewBadRequestException;
import com.woowacourse.moamoa.review.domain.repository.ReviewRepository;
import com.woowacourse.moamoa.review.service.exception.ReviewNotFoundException;
import com.woowacourse.moamoa.review.service.request.EditingReviewRequest;
import com.woowacourse.moamoa.review.service.request.WriteReviewRequest;
import com.woowacourse.moamoa.study.domain.Study;
import com.woowacourse.moamoa.study.domain.repository.StudyRepository;
import com.woowacourse.moamoa.study.service.exception.StudyNotFoundException;
import java.time.LocalDateTime;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -37,4 +39,22 @@ public Long writeReview(final Long githubId, final Long studyId, final WriteRevi
final Review review = Review.writeNewReview(study.getId(), member.getId(), writeReviewRequest.getContent());
return reviewRepository.save(review).getId();
}

public void updateReview(final Long githubId, final Long reviewId, final EditingReviewRequest editingReviewRequest) {
final Member member = memberRepository.findByGithubId(githubId)
.orElseThrow(MemberNotFoundException::new);
final Review review = reviewRepository.findById(reviewId)
.orElseThrow(ReviewNotFoundException::new);

review.updateContent(new Reviewer(member.getId()), editingReviewRequest.getContent());
}

public void deleteReview(final Long githubId, final Long reviewId) {
final Member member = memberRepository.findByGithubId(githubId)
.orElseThrow(MemberNotFoundException::new);
final Review review = reviewRepository.findById(reviewId)
.orElseThrow(ReviewNotFoundException::new);

review.delete(new Reviewer(member.getId()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.woowacourse.moamoa.review.service.exception;

import com.woowacourse.moamoa.common.exception.NotFoundException;

public class ReviewNotFoundException extends NotFoundException {

public ReviewNotFoundException() {
super("후기를 찾을 수 없습니다.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.woowacourse.moamoa.review.service.exception;

import com.woowacourse.moamoa.common.exception.BadRequestException;

public class UnwrittenReviewException extends BadRequestException {
public UnwrittenReviewException() {

super("내가 작성한 후기가 아닙니다.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.woowacourse.moamoa.review.service.request;

import javax.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@NoArgsConstructor
@AllArgsConstructor
@Getter
public class EditingReviewRequest {

@NotBlank(message = "내용을 입력해 주세요.")
private String content;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.woowacourse.acceptance.AcceptanceTest;
import com.woowacourse.moamoa.auth.service.oauthclient.response.GithubProfileResponse;
import com.woowacourse.moamoa.member.query.data.MemberData;
import com.woowacourse.moamoa.review.service.request.EditingReviewRequest;
import com.woowacourse.moamoa.review.service.request.WriteReviewRequest;
import com.woowacourse.moamoa.review.service.response.ReviewResponse;
import com.woowacourse.moamoa.review.service.response.ReviewsResponse;
Expand All @@ -29,6 +30,10 @@ public class ReviewsAcceptanceTest extends AcceptanceTest {
private static final MemberData DWOO = new MemberData(3L, "dwoo", "https://image", "github.com");
private static final MemberData VERUS = new MemberData(4L, "verus", "https://image", "github.com");

private Long javaStudyId;
private Long javaReviewId1;
private Long javaReviewId2;

private List<ReviewResponse> javaReviews;

@BeforeEach
Expand All @@ -48,7 +53,7 @@ void initDataBase() {
.startDate(startDate)
.build();

long javaStudyId = createStudy(jjangguToken, javaStudyRequest);
javaStudyId = createStudy(jjangguToken, javaStudyRequest);
long reactStudyId = createStudy(jjangguToken, reactStudyRequest);

participateStudy(greenlawnToken, javaStudyId);
Expand All @@ -59,17 +64,25 @@ void initDataBase() {
final LocalDate lastModifiedDate = LocalDate.now();

// 리뷰 추가
long javaReviewId1 = createReview(jjangguToken, javaStudyId, new WriteReviewRequest("리뷰 내용1"));
long javaReviewId2 = createReview(greenlawnToken, javaStudyId, new WriteReviewRequest("리뷰 내용2"));
javaReviewId1 = createReview(jjangguToken, javaStudyId, new WriteReviewRequest("리뷰 내용1"));
javaReviewId2 = createReview(greenlawnToken, javaStudyId, new WriteReviewRequest("리뷰 내용2"));
long javaReviewId3 = createReview(dwooToken, javaStudyId, new WriteReviewRequest("리뷰 내용3"));
long javaReviewId4 = createReview(verusToken, javaStudyId, new WriteReviewRequest("리뷰 내용4"));
createReview(jjangguToken, reactStudyId, new WriteReviewRequest("리뷰 내용5"));

final ReviewResponse 리뷰_내용1 = new ReviewResponse(javaReviewId1, new WriterResponse(JJANGGU), createdAt,
lastModifiedDate, "리뷰 내용1");
final ReviewResponse 리뷰_내용2 = new ReviewResponse(javaReviewId2, new WriterResponse(GREENLAWN), createdAt,
lastModifiedDate, "리뷰 내용2");
final ReviewResponse 리뷰_내용3 = new ReviewResponse(javaReviewId3, new WriterResponse(DWOO), createdAt,
lastModifiedDate, "리뷰 내용3");
final ReviewResponse 리뷰_내용4 = new ReviewResponse(javaReviewId4, new WriterResponse(VERUS), createdAt,
lastModifiedDate, "리뷰 내용4");
javaReviews = List.of(
new ReviewResponse(javaReviewId1, new WriterResponse(JJANGGU), createdAt, lastModifiedDate, "리뷰 내용1"),
new ReviewResponse(javaReviewId2, new WriterResponse(GREENLAWN), createdAt, lastModifiedDate, "리뷰 내용2"),
new ReviewResponse(javaReviewId3, new WriterResponse(DWOO), createdAt, lastModifiedDate, "리뷰 내용3"),
new ReviewResponse(javaReviewId4, new WriterResponse(VERUS), createdAt, lastModifiedDate, "리뷰 내용4")
리뷰_내용4,
리뷰_내용3,
리뷰_내용2,
리뷰_내용1
);
}

Expand Down Expand Up @@ -126,4 +139,39 @@ public void getReviewsBySize() {
assertThat(reviewsResponse.getTotalCount()).isEqualTo(4);
assertThat(reviewsResponse.getReviews()).containsExactlyInAnyOrderElementsOf(javaReviews.subList(0, 2));
}

@DisplayName("자신이 참여한 스터디에 작성한 리뷰를 삭제할 수 있다.")
@Test
void deleteReview() {
final String token = getBearerTokenBySignInOrUp(toGithubProfileResponse(JJANGGU));

RestAssured.given(spec).log().all()
.filter(document("reviews/delete"))
.header(HttpHeaders.AUTHORIZATION, token)
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.pathParam("study-id", javaStudyId)
.pathParam("review-id", javaReviewId1)
.when().log().all()
.delete("/api/studies/{study-id}/reviews/{review-id}")
.then().statusCode(HttpStatus.NO_CONTENT.value());
}

@DisplayName("자신이 참여한 스터디에 작성한 리뷰를 수정할 수 있다.")
@Test
void updateReview() {
final String token = getBearerTokenBySignInOrUp(toGithubProfileResponse(JJANGGU));
final EditingReviewRequest request = new EditingReviewRequest("edit review");

RestAssured.given(spec).log().all()
.filter(document("reviews/update"))
.header(HttpHeaders.AUTHORIZATION, token)
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.pathParam("study-id", javaStudyId)
.pathParam("review-id", javaReviewId1)
.body(request)
.when().log().all()
.put("/api/studies/{study-id}/reviews/{review-id}")
.then().statusCode(HttpStatus.NO_CONTENT.value());
}
}
20 changes: 16 additions & 4 deletions backend/src/test/java/com/woowacourse/fixtures/ReviewFixtures.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
package com.woowacourse.fixtures;

import static com.woowacourse.fixtures.MemberFixtures.그린론_아이디;
import static com.woowacourse.fixtures.MemberFixtures.그린론_응답;
import static com.woowacourse.fixtures.MemberFixtures.디우_아이디;
import static com.woowacourse.fixtures.MemberFixtures.디우_응답;
import static com.woowacourse.fixtures.MemberFixtures.베루스_아이디;
import static com.woowacourse.fixtures.MemberFixtures.베루스_응답;
import static com.woowacourse.fixtures.MemberFixtures.짱구_아이디;
import static com.woowacourse.fixtures.MemberFixtures.짱구_응답;
import static com.woowacourse.fixtures.StudyFixtures.자바_스터디_아이디;

import com.woowacourse.moamoa.review.domain.AssociatedStudy;
import com.woowacourse.moamoa.review.domain.Review;
import com.woowacourse.moamoa.review.domain.Reviewer;
import com.woowacourse.moamoa.review.query.data.ReviewData;
import java.time.LocalDate;

Expand All @@ -13,22 +21,26 @@ public class ReviewFixtures {
/* 자바 스터디 리뷰 */
public static final Long 자바_리뷰1_아이디 = 1L;
public static final String 자바_리뷰1_내용 = "자바 스터디 첫 번째 리뷰입니다.";
public static final ReviewData 자바_리뷰1 = new ReviewData(자바_리뷰1_아이디, 짱구_응답,
public static final Review 자바_리뷰1 = new Review(new AssociatedStudy(자바_스터디_아이디), new Reviewer(짱구_아이디), 자바_리뷰1_내용);
public static final ReviewData 자바_리뷰1_데이터 = new ReviewData(자바_리뷰1_아이디, 짱구_응답,
LocalDate.of(2022, 10, 9), LocalDate.of(2022, 10, 9), 자바_리뷰1_내용);

public static final Long 자바_리뷰2_아이디 = 2L;
public static final String 자바_리뷰2_내용 = "자바 스터디 두 번째 리뷰입니다.";
public static final ReviewData 자바_리뷰2 = new ReviewData(자바_리뷰2_아이디, 베루스_응답,
public static final Review 자바_리뷰2 = new Review(new AssociatedStudy(자바_스터디_아이디), new Reviewer(베루스_아이디), 자바_리뷰2_내용);
public static final ReviewData 자바_리뷰2_데이터 = new ReviewData(자바_리뷰2_아이디, 베루스_응답,
LocalDate.of(2022, 10, 9), LocalDate.of(2022, 10, 10), 자바_리뷰2_내용);

public static final Long 자바_리뷰3_아이디 = 3L;
public static final String 자바_리뷰3_내용 = "자바 스터디 세 번째 리뷰입니다.";
public static final ReviewData 자바_리뷰3 = new ReviewData(자바_리뷰3_아이디, 그린론_응답,
public static final Review 자바_리뷰3 = new Review(new AssociatedStudy(자바_스터디_아이디), new Reviewer(그린론_아이디), 자바_리뷰3_내용);
public static final ReviewData 자바_리뷰3_데이터 = new ReviewData(자바_리뷰3_아이디, 그린론_응답,
LocalDate.of(2022, 10, 10), LocalDate.of(2022, 10, 10), 자바_리뷰3_내용);

public static final Long 자바_리뷰4_아이디 = 4L;
public static final String 자바_리뷰4_내용 = "자바 스터디 네 번째 리뷰입니다.";
public static final ReviewData 자바_리뷰4 = new ReviewData(자바_리뷰4_아이디, 디우_응답,
public static final Review 자바_리뷰4 = new Review(new AssociatedStudy(자바_스터디_아이디), new Reviewer(디우_아이디), 자바_리뷰4_내용);
public static final ReviewData 자바_리뷰4_데이터 = new ReviewData(자바_리뷰4_아이디, 디우_응답,
LocalDate.of(2022, 10, 14), LocalDate.of(2022, 10, 15), 자바_리뷰4_내용);

public static final int 자바_리뷰__개수 = 4;
Expand Down
Loading

0 comments on commit 245d832

Please sign in to comment.