Skip to content

Commit

Permalink
Feat: 게시글 저장 관련 기능 구현
Browse files Browse the repository at this point in the history
  • Loading branch information
yunhacandy authored Jul 29, 2024
2 parents a9c32ac + 6ae69fa commit 5101d34
Show file tree
Hide file tree
Showing 7 changed files with 152 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ public enum ErrorCode {
CANNOT_LIKE_OWN_POST(HttpStatus.BAD_REQUEST, "본인이 작성한 게시글은 좋아요를 누를 수 없습니다."),
CANNOT_LIKE_OWN_COMMENT(HttpStatus.BAD_REQUEST, "본인이 작성한 댓글은 좋아요를 누를 수 없습니다."),
CANNOT_LIKE_OWN_REPLY_COMMENT(HttpStatus.BAD_REQUEST, "본인이 작성한 답글은 좋아요를 누를 수 없습니다."),
ALREADY_DELETED(HttpStatus.CONFLICT, "이미 삭제되었습니다.");
ALREADY_DELETED(HttpStatus.CONFLICT, "이미 삭제되었습니다."),
ALREADY_SAVED(HttpStatus.CONFLICT, "이미 저장되었습니다.");

private final HttpStatus httpStatus;
private final String message;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,4 @@ public Response<?> deletePost(@PathVariable Long postId,
postService.deletePost(postId, memberId);
return Response.createSuccessWithNoData("포스트 삭제 완료");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package cotato.growingpain.post.controller;

import cotato.growingpain.common.Response;
import cotato.growingpain.post.domain.entity.Post;
import cotato.growingpain.post.service.PostSaveService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

@Tag(name = "게시글 저장", description = "게시글 저장 관련된 api")
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/post/saves")
@Slf4j
public class PostSaveController {

private final PostSaveService postSaveService;

@Operation(summary = "게시글 저장", description = "게시글을 저장하기 위한 메소드")
@ApiResponse(content = @Content(schema = @Schema(implementation = Response.class)))
@PostMapping("/{post-id}")
@ResponseStatus(HttpStatus.OK)
public Response<?> savePost(@PathVariable("post-id") Long postId,
@AuthenticationPrincipal Long memberId) {
log.info("게시글 {} 저장한 memberId: {}", postId, memberId);
postSaveService.savePost(postId, memberId);
return Response.createSuccessWithNoData("게시글 저장 완료");
}

@Operation(summary = "게시글 저장 취소", description = "게시글 저장을 취소하기 위한 메소드")
@ApiResponse(content = @Content(schema = @Schema(implementation = Response.class)))
@DeleteMapping("/{post-id}")
@ResponseStatus(HttpStatus.OK)
public Response<?> deleteSavePost(@PathVariable("post-id") Long postId,
@AuthenticationPrincipal Long memberId) {
log.info("게시글 {} 저장 취소한 memberId: {}", postId, memberId);
postSaveService.deleteSavePost(postId, memberId);
return Response.createSuccessWithNoData("게시글 저장 취소 완료");
}

@Operation(summary = "저장한 게시글 목록 조회", description = "사용자가 저장한 게시글의 목록을 조회하기 위한 메소드")
@ApiResponse(content = @Content(schema = @Schema(implementation = Response.class)))
@GetMapping("/list/{member-id}")
@ResponseStatus(HttpStatus.OK)
public Response<List<Post>> getSavedPosts(@AuthenticationPrincipal Long memberId) {
log.info("사용자가 저장한 게시글 목록 요청: memberId {}", memberId);
List<Post> savedPosts = postSaveService.getSavedPosts(memberId);
return Response.createSuccess("저장한 게시글 목록 조회 완료", savedPosts);
}
}
2 changes: 2 additions & 0 deletions src/main/java/cotato/growingpain/post/domain/entity/Post.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cotato.growingpain.post.domain.entity;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import cotato.growingpain.common.domain.BaseTimeEntity;
import cotato.growingpain.common.exception.AppException;
import cotato.growingpain.common.exception.ErrorCode;
Expand Down Expand Up @@ -28,6 +29,7 @@
@Getter
@DynamicInsert
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Post extends BaseTimeEntity {

@Id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,13 @@ public class PostSave extends BaseTimeEntity {
@JoinColumn(name = "post_id")
@JsonIgnore
private Post post;

public PostSave(Member member, Post post) {
this.member = member;
this.post = post;
}

public static PostSave createPostSave(Member member, Post post) {
return new PostSave(member, post);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package cotato.growingpain.post.repository;

import cotato.growingpain.post.domain.entity.PostSave;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;

public interface PostSaveRepository extends JpaRepository<PostSave, Long> {

boolean existsById(Long postSaveId);
boolean existsByMemberIdAndPostId(Long memberId, Long postId);
void deleteByMemberIdAndPostId(Long memberId, Long postId);

List<PostSave> findByMemberId(Long memberId);
}
60 changes: 60 additions & 0 deletions src/main/java/cotato/growingpain/post/service/PostSaveService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package cotato.growingpain.post.service;

import cotato.growingpain.common.exception.AppException;
import cotato.growingpain.common.exception.ErrorCode;
import cotato.growingpain.member.domain.entity.Member;
import cotato.growingpain.member.repository.MemberRepository;
import cotato.growingpain.post.domain.entity.Post;
import cotato.growingpain.post.domain.entity.PostSave;
import cotato.growingpain.post.repository.PostRepository;
import cotato.growingpain.post.repository.PostSaveRepository;
import java.util.List;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Slf4j
@Service
@RequiredArgsConstructor
public class PostSaveService {

private final MemberRepository memberRepository;
private final PostRepository postRepository;
private final PostSaveRepository postSaveRepository;

@Transactional
public void savePost(Long postId, Long memberId) {
Member member = memberRepository.findById(memberId)
.orElseThrow(() -> new AppException(ErrorCode.MEMBER_NOT_FOUND));
Post post = postRepository.findById(postId)
.orElseThrow(() -> new AppException(ErrorCode.POST_NOT_FOUND));

if (postSaveRepository.existsByMemberIdAndPostId(memberId, postId)) {
throw new AppException(ErrorCode.ALREADY_SAVED);
}

PostSave postSave = PostSave.createPostSave(member, post);
postSaveRepository.save(postSave);
}

@Transactional
public void deleteSavePost(Long postId, Long memberId) {

if (!postSaveRepository.existsByMemberIdAndPostId(postId, memberId)) {
throw new AppException(ErrorCode.POST_NOT_FOUND);
}

postSaveRepository.deleteByMemberIdAndPostId(memberId, postId);
}

@Transactional
public List<Post> getSavedPosts(Long memberId) {
List<PostSave> postSaves = postSaveRepository.findByMemberId(memberId);

return postSaves.stream()
.map(PostSave::getPost)
.collect(Collectors.toList());
}
}

0 comments on commit 5101d34

Please sign in to comment.