Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BE] issue195: 리뷰 수정 및 삭제 #198

Merged
merged 13 commits into from
Aug 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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+")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Comment on lines -17 to +20
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

중복된 URL이 너무 많은데 어떻게 하면 중복을 최대한 없앨 수 있을지 addUpAuthenticationPath의 시그니처를 변경해서 생각해보면 좋을 것 같아요.

.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() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

한 칸 띄어도 될 것 같아요


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