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] 피드 좋아요 기능 구현 #210

Merged
merged 1 commit into from
Nov 24, 2023
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
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
package com.foodymoody.be.feed.dto.response;

import com.fasterxml.jackson.annotation.JsonProperty;

public class FeedRegisterResponse {

@JsonProperty
private final String id;

public FeedRegisterResponse(String id) {
this.id = id;
}

public String getId() {
return id;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,10 @@ public Slice<FeedReadAllResponse> readAll(Pageable pageable) {
.images(images)
.createdAt(feed.getCreatedAt())
.updatedAt(feed.getUpdatedAt())
.commentCount(feed.getCommentCount())
// TODO: 아래 로직 구현 후 추가
.likeCount(feed.getLikeCount())
.isLiked(feed.isLiked())
.commentCount(feed.getCommentCount())
.likeCount(feed.getLikeCount())
.build();

responses.add(response);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.foodymoody.be.heart.controller;

import com.foodymoody.be.common.annotation.MemberId;
import com.foodymoody.be.heart.dto.request.HeartRequest;
import com.foodymoody.be.heart.dto.response.HeartResponse;
import com.foodymoody.be.heart.service.HeartService;
import com.foodymoody.be.heart.util.HeartMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RequiredArgsConstructor
@RestController
public class HeartController {

private final HeartService heartService;

@PostMapping("/api/likes")
public ResponseEntity<HeartResponse> like(@RequestBody HeartRequest heartRequest,
@MemberId String memberId) {
HeartResponse heartResponse = heartService.like(HeartMapper.toHeartServiceRequest(heartRequest, memberId));
return ResponseEntity.ok().body(heartResponse);
}

}
44 changes: 44 additions & 0 deletions be/src/main/java/com/foodymoody/be/heart/domain/Heart.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.foodymoody.be.heart.domain;

import javax.persistence.Entity;
import javax.persistence.Id;

@Entity
public class Heart {

@Id
private String id;
private String feedId;
private String memberId;
private int count;

public Heart() {
}

public Heart(String id, String feedId, String memberId) {
this.id = id;
this.feedId = feedId;
this.memberId = memberId;
}

public String getId() {
return id;
}

public String getFeedId() {
return feedId;
}

public String getMemberId() {
return memberId;
}

public int getCount() {
return count;
}

public void updateCount() {
this.count += 1;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.foodymoody.be.heart.dto.request;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;

@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class HeartRequest {

private String feedId;

public String getFeedId() {
return feedId;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.foodymoody.be.heart.dto.request;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;

@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class HeartServiceRequest {

private String feedId;
private String memberId;

public String getMemberId() {
return memberId;
}

public String getFeedId() {
return feedId;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.foodymoody.be.heart.dto.response;

import com.fasterxml.jackson.annotation.JsonProperty;

public class HeartResponse {

@JsonProperty
private String id;
@JsonProperty
private String feedId;
@JsonProperty
private String memberId;

public HeartResponse(String id, String feedId, String memberId) {
this.id = id;
this.feedId = feedId;
this.memberId = memberId;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.foodymoody.be.heart.repository;

import com.foodymoody.be.heart.domain.Heart;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;

public interface HeartRepository extends JpaRepository<Heart, String> {

Optional<Heart> findHeartByMemberIdAndFeedId(String memberId, String feedId);

}
36 changes: 36 additions & 0 deletions be/src/main/java/com/foodymoody/be/heart/service/HeartService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.foodymoody.be.heart.service;

import com.foodymoody.be.heart.domain.Heart;
import com.foodymoody.be.heart.dto.request.HeartServiceRequest;
import com.foodymoody.be.heart.dto.response.HeartResponse;
import com.foodymoody.be.heart.repository.HeartRepository;
import com.foodymoody.be.heart.util.HeartMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@RequiredArgsConstructor
@Service
public class HeartService {

private final HeartRepository heartRepository;

public HeartResponse like(HeartServiceRequest heartServiceRequest) {
if (isHeartExist(heartServiceRequest)) {
throw new IllegalArgumentException("이미 좋아요 누른 피드입니다.");
}

Heart heart = HeartMapper.toHeartWithFeedIdAndMemberId(heartServiceRequest.getFeedId(),
heartServiceRequest.getMemberId());
heart.updateCount();

Heart savedHeart = heartRepository.save(heart);
return HeartMapper.toHeartResponse(savedHeart);
}

private boolean isHeartExist(HeartServiceRequest heartServiceRequest) {
return heartRepository.findHeartByMemberIdAndFeedId(heartServiceRequest.getMemberId(),
heartServiceRequest.getFeedId())
.isPresent();
}

}
27 changes: 27 additions & 0 deletions be/src/main/java/com/foodymoody/be/heart/util/HeartMapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.foodymoody.be.heart.util;

import com.foodymoody.be.common.util.IdGenerator;
import com.foodymoody.be.heart.domain.Heart;
import com.foodymoody.be.heart.dto.request.HeartRequest;
import com.foodymoody.be.heart.dto.request.HeartServiceRequest;
import com.foodymoody.be.heart.dto.response.HeartResponse;

public class HeartMapper {

private HeartMapper() {
throw new IllegalArgumentException("Utility Class");
}

public static HeartServiceRequest toHeartServiceRequest(HeartRequest heartRequest, String memberId) {
return new HeartServiceRequest(heartRequest.getFeedId(), memberId);
}

public static Heart toHeartWithFeedIdAndMemberId(String feedId, String memberId) {
return new Heart(IdGenerator.generate(), feedId, memberId);
}

public static HeartResponse toHeartResponse(Heart savedHeart) {
return new HeartResponse(savedHeart.getId(), savedHeart.getFeedId(), savedHeart.getMemberId());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.foodymoody.be.acceptance.heart;

import static com.foodymoody.be.acceptance.feed.FeedSteps.피드를_등록한다;
import static com.foodymoody.be.acceptance.heart.HeartSteps.응답코드가_200이고_id가_존재하면_정상적으로_좋아요_가능;
import static com.foodymoody.be.acceptance.heart.HeartSteps.좋아요를_한다;

import com.foodymoody.be.acceptance.AcceptanceTest;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

class HeartAcceptanceTest extends AcceptanceTest {

@AfterEach
void cleanupDatabase() {
데이터베이스를_초기화한다();
}

@DisplayName("좋아요에 성공하면 응답코드 200을 반환한다.")
@Test
void when_like_then_response200() {
// docs
api_문서_타이틀("like", spec);

// given
String feedId = 피드를_등록한다(회원아티_액세스토큰, spec).jsonPath().getString("id");

// when
var response = 좋아요를_한다(feedId, 회원아티_액세스토큰, spec);

// then
응답코드가_200이고_id가_존재하면_정상적으로_좋아요_가능(response);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.foodymoody.be.acceptance.heart;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertAll;

import io.restassured.RestAssured;
import io.restassured.response.ExtractableResponse;
import io.restassured.response.Response;
import io.restassured.specification.RequestSpecification;
import java.util.Map;
import org.springframework.http.MediaType;

public class HeartSteps {

public static ExtractableResponse<Response> 좋아요를_한다(String feedId, String accessToken, RequestSpecification spec) {
Map<String, Object> body = Map.of(
"feedId", feedId
);

return RestAssured
.given()
.contentType(MediaType.APPLICATION_JSON_VALUE)
.spec(spec)
.log().all()
.auth()
.oauth2(accessToken)
.body(body)
.when()
.post("/api/likes")
.then()
.log().all()
.extract();
}

public static void 응답코드가_200이고_id가_존재하면_정상적으로_좋아요_가능(ExtractableResponse<Response> response) {
String id = response.jsonPath().getString("id");

assertAll(
() -> assertThat(response.statusCode()).isEqualTo(200),
assertThat(id)::isNotNull
);
}

}