From b40709d826d22afca6df749eb10d96fa921eca0e Mon Sep 17 00:00:00 2001 From: leeuihyun Date: Mon, 2 Jun 2025 15:48:16 +0900 Subject: [PATCH 1/4] =?UTF-8?q?feat=20:=20Post=20Entity=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=EC=9E=90=20=EC=88=98=EC=A0=95=20Profile=20=EA=B0=9D?= =?UTF-8?q?=EC=B2=B4=20=EC=83=9D=EC=84=B1=EC=9E=90=EC=97=90=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feeda/domain/post/entity/Post.java | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/example/feeda/domain/post/entity/Post.java b/src/main/java/com/example/feeda/domain/post/entity/Post.java index 87dbbfb..f1367d6 100644 --- a/src/main/java/com/example/feeda/domain/post/entity/Post.java +++ b/src/main/java/com/example/feeda/domain/post/entity/Post.java @@ -1,7 +1,14 @@ package com.example.feeda.domain.post.entity; import com.example.feeda.domain.profile.entity.Profile; -import jakarta.persistence.*; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; import lombok.Getter; @@ -10,7 +17,7 @@ @Getter @Entity @Table(name = "posts") -public class Post extends BaseEntity{ +public class Post extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -28,24 +35,25 @@ public class Post extends BaseEntity{ @Column(length = 50) private String category; - public void update(String title, String content, String category) { - this.title = title; - this.content = content; - this.category = category; - } + @ManyToOne + @JoinColumn(name = "profile_id") + private Profile profile; - public Post(String title, String content, String category) { + public Post(String title, String content, String category, Profile profile) { this.title = title; this.content = content; this.category = category; + this.profile = profile; } protected Post() { } - @ManyToOne - @JoinColumn(name = "profile_id") - private Profile profile; + public void update(String title, String content, String category) { + this.title = title; + this.content = content; + this.category = category; + } } From d7433e3b9a2639261472da9268b3f4c94f03bbb0 Mon Sep 17 00:00:00 2001 From: leeuihyun Date: Mon, 2 Jun 2025 15:51:31 +0900 Subject: [PATCH 2/4] =?UTF-8?q?feat=20:=20Post=20Controller=20=EC=9D=B8?= =?UTF-8?q?=EC=A6=9D=20=EA=B0=9D=EC=B2=B4=20=EB=88=84=EB=9D=BD=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EC=9D=B8=EA=B0=80=20=ED=99=95=EC=9D=B8=20=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20=EC=BD=94=EB=93=9C=EC=97=90=20@Authenticat?= =?UTF-8?q?ionPrincipal=20=EC=B6=94=EA=B0=80=20@Validated=20=EB=A1=9C=20Pa?= =?UTF-8?q?ram,=20Pathvariable=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/controller/PostController.java | 69 ++++++++++++------- 1 file changed, 45 insertions(+), 24 deletions(-) diff --git a/src/main/java/com/example/feeda/domain/post/controller/PostController.java b/src/main/java/com/example/feeda/domain/post/controller/PostController.java index 67986b9..565dec6 100644 --- a/src/main/java/com/example/feeda/domain/post/controller/PostController.java +++ b/src/main/java/com/example/feeda/domain/post/controller/PostController.java @@ -2,23 +2,31 @@ import com.example.feeda.domain.post.dto.PostRequestDto; import com.example.feeda.domain.post.dto.PostResponseDto; -import com.example.feeda.domain.post.entity.Post; import com.example.feeda.domain.post.service.PostService; +import com.example.feeda.security.jwt.JwtPayload; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; -import org.springframework.data.jpa.repository.config.EnableJpaAuditing; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.server.ResponseStatusException; - -import java.util.List; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.validation.annotation.Validated; +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.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; @RestController +@Validated @RequestMapping("/api/posts") @RequiredArgsConstructor public class PostController { @@ -26,44 +34,57 @@ public class PostController { private final PostService postService; @PostMapping - public ResponseEntity createPost(@RequestBody PostRequestDto requestDto) { + public ResponseEntity createPost(@RequestBody PostRequestDto requestDto, + @AuthenticationPrincipal JwtPayload jwtPayload) { - PostResponseDto post = postService.createPost(requestDto.getTitle(), requestDto.getContent(), requestDto.getCategory()); + PostResponseDto post = postService.createPost(requestDto, jwtPayload); - return new ResponseEntity<>(post, - HttpStatus.CREATED); + return new ResponseEntity<>(post, HttpStatus.CREATED); } @GetMapping("/{id}") - public ResponseEntity findPostById(@PathVariable Long id) { + public ResponseEntity findPostById(@PathVariable @NotNull Long id) { return new ResponseEntity<>(postService.findPostById(id), HttpStatus.OK); } @GetMapping public ResponseEntity> findAllPost( - @RequestParam(defaultValue = "1") int page, - @RequestParam(defaultValue = "10") int size, - @RequestParam(defaultValue = "") String keyword + @RequestParam(defaultValue = "1") @Min(1) int page, + @RequestParam(defaultValue = "10") @Min(1) int size, + @RequestParam(defaultValue = "") String keyword ) { - if (page < 1 || size < 1) { - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "page 와 size 는 1 이상의 값이어야 합니다."); - } + Pageable pageable = PageRequest.of(page - 1, size, Sort.Direction.DESC, "updatedAt"); return new ResponseEntity<>(postService.findAll(pageable, keyword), HttpStatus.OK); } + @GetMapping("/followings") + public ResponseEntity> findFollowingAllPost( + @RequestParam(defaultValue = "1") @Min(1) int page, + @RequestParam(defaultValue = "10") @Min(1) int size, + @AuthenticationPrincipal JwtPayload jwtPayload + ) { + Pageable pageable = PageRequest.of(page - 1, size); + + return new ResponseEntity<>(postService.findFollowingAllPost(pageable, jwtPayload), + HttpStatus.OK); + } + @PutMapping("/{id}") - public ResponseEntity updatePost( - @PathVariable Long id, - @RequestBody PostRequestDto requestDto) { - Post post = postService.updatePost(id, requestDto); + public ResponseEntity updatePost( + @PathVariable @NotNull Long id, + @RequestBody PostRequestDto requestDto, @AuthenticationPrincipal JwtPayload jwtPayload) { + + PostResponseDto post = postService.updatePost(id, requestDto, jwtPayload); return new ResponseEntity<>(post, HttpStatus.OK); } @DeleteMapping("/{id}") - public ResponseEntity deletePost(@PathVariable Long id) { - postService.deletePost(id); + public ResponseEntity deletePost(@PathVariable @NotNull Long id, + @AuthenticationPrincipal JwtPayload jwtPayload) { + + postService.deletePost(id, jwtPayload); return new ResponseEntity<>(HttpStatus.OK); } } From c76fe6c288d1bc0a7f7c4c16ee28af2b21bdd416 Mon Sep 17 00:00:00 2001 From: leeuihyun Date: Mon, 2 Jun 2025 15:53:44 +0900 Subject: [PATCH 3/4] =?UTF-8?q?feat=20:=20Post=20=EB=B9=84=EC=A6=88?= =?UTF-8?q?=EB=8B=88=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20=EB=88=84=EB=9D=BD=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80=20JwtPayload=20?= =?UTF-8?q?=ED=86=B5=ED=95=9C=20=EC=9D=B8=EA=B0=80=20=ED=99=95=EC=9D=B8=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80=20(=EC=88=98=EC=A0=95?= =?UTF-8?q?=20/=20=EC=82=AD=EC=A0=9C=EB=8A=94=20=EB=B3=B8=EC=9D=B8?= =?UTF-8?q?=EB=A7=8C=20=EA=B0=80=EB=8A=A5=ED=95=98=EA=B2=8C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD)=20=ED=8C=94=EB=A1=9C=EC=9A=B0=ED=95=9C=20=EC=82=AC?= =?UTF-8?q?=EB=9E=8C=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20=EB=B3=B4=EA=B8=B0=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EB=88=84=EB=9D=BD=EB=90=98=EC=96=B4=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/post/service/PostService.java | 13 +-- .../domain/post/service/PostServiceImpl.java | 87 +++++++++++++++---- 2 files changed, 78 insertions(+), 22 deletions(-) diff --git a/src/main/java/com/example/feeda/domain/post/service/PostService.java b/src/main/java/com/example/feeda/domain/post/service/PostService.java index 3e340c7..40c1f9e 100644 --- a/src/main/java/com/example/feeda/domain/post/service/PostService.java +++ b/src/main/java/com/example/feeda/domain/post/service/PostService.java @@ -2,22 +2,23 @@ import com.example.feeda.domain.post.dto.PostRequestDto; import com.example.feeda.domain.post.dto.PostResponseDto; -import com.example.feeda.domain.post.entity.Post; +import com.example.feeda.security.jwt.JwtPayload; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; -import java.util.List; - public interface PostService { - PostResponseDto createPost(String title, String content, String category); + PostResponseDto createPost(PostRequestDto postRequestDto, + JwtPayload jwtPayload); PostResponseDto findPostById(Long id); Page findAll(Pageable pageable, String keyword); - Post updatePost(Long id, PostRequestDto requestDto); + Page findFollowingAllPost(Pageable pageable, JwtPayload jwtPayload); + + PostResponseDto updatePost(Long id, PostRequestDto requestDto, JwtPayload jwtPayload); - void deletePost(Long id); + void deletePost(Long id, JwtPayload jwtPayload); } diff --git a/src/main/java/com/example/feeda/domain/post/service/PostServiceImpl.java b/src/main/java/com/example/feeda/domain/post/service/PostServiceImpl.java index ff3a9b3..7fa17a7 100644 --- a/src/main/java/com/example/feeda/domain/post/service/PostServiceImpl.java +++ b/src/main/java/com/example/feeda/domain/post/service/PostServiceImpl.java @@ -1,36 +1,54 @@ package com.example.feeda.domain.post.service; +import com.example.feeda.domain.follow.entity.Follows; +import com.example.feeda.domain.follow.repository.FollowsRepository; import com.example.feeda.domain.post.dto.PostRequestDto; import com.example.feeda.domain.post.dto.PostResponseDto; import com.example.feeda.domain.post.entity.Post; import com.example.feeda.domain.post.repository.PostRepository; +import com.example.feeda.domain.profile.entity.Profile; +import com.example.feeda.domain.profile.repository.ProfileRepository; +import com.example.feeda.security.jwt.JwtPayload; +import java.util.List; +import java.util.Optional; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.web.server.ResponseStatusException; -import java.util.List; -import java.util.Optional; - @Service @RequiredArgsConstructor public class PostServiceImpl implements PostService { private final PostRepository postRepository; + private final ProfileRepository profileRepository; + private final FollowsRepository followsRepository; @Override - public PostResponseDto createPost(String title, String content, String category) { - Post post = new Post(title, content, category); + public PostResponseDto createPost(PostRequestDto postRequestDto, JwtPayload jwtPayload) { + + Profile profile = profileRepository.findById(jwtPayload.getProfileId()) + .orElseThrow( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "존재하지 않는 프로필입니다.")); + + Post post = new Post(postRequestDto.getTitle(), postRequestDto.getContent(), + postRequestDto.getCategory(), profile); + Post savedPost = postRepository.save(post); + return new PostResponseDto(savedPost.getId(), - savedPost.getTitle(), - savedPost.getContent(), - savedPost.getCategory()); + savedPost.getTitle(), + savedPost.getContent(), + savedPost.getCategory()); } @Override + @Transactional(readOnly = true) public PostResponseDto findPostById(Long id) { Optional optionalPost = postRepository.findById(id); @@ -40,29 +58,66 @@ public PostResponseDto findPostById(Long id) { Post findPost = optionalPost.get(); - return new PostResponseDto(id, findPost.getTitle(), findPost.getContent(), findPost.getCategory()); + return new PostResponseDto(id, findPost.getTitle(), findPost.getContent(), + findPost.getCategory()); } @Override + @Transactional(readOnly = true) public Page findAll(Pageable pageable, String keyword) { - return postRepository.findAllByTitleContaining(keyword, pageable).map(PostResponseDto::toDto); + + return postRepository.findAllByTitleContaining(keyword, pageable) + .map(PostResponseDto::toDto); } @Override - public Post updatePost(Long id, PostRequestDto requestDto) { + @Transactional(readOnly = true) + public Page findFollowingAllPost(Pageable pageable, JwtPayload jwtPayload) { + + Page followings = followsRepository.findAllByFollowers_Id( + jwtPayload.getProfileId(), pageable); + + List followingProfileIds = followings.stream() + .map(following -> following.getFollowings().getId()) + .toList(); + + Pageable newPageable = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), + Sort.Direction.DESC, "updatedAt"); + + return postRepository.findAllByProfile_IdIn(followingProfileIds, newPageable) + .map(PostResponseDto::toDto); + } + + @Override + @Transactional + public PostResponseDto updatePost(Long id, PostRequestDto requestDto, JwtPayload jwtPayload) { + Post findPost = postRepository.findById(id) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "존재하지 않는 게시글")); + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "존재하지 않는 게시글")); + if (!findPost.getProfile().getId().equals(jwtPayload.getProfileId())) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "권한이 없습니다."); + } findPost.update(requestDto.getTitle(), requestDto.getCategory(), requestDto.getCategory()); - postRepository.save(findPost); + Post savedPost = postRepository.save(findPost); - return findPost; + return new PostResponseDto(savedPost.getId(), + savedPost.getTitle(), + savedPost.getContent(), + savedPost.getCategory()); } @Override - public void deletePost(Long id) { - Post findPost = postRepository.findById(id).orElseThrow( () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "존재하지 않는 게시글")); + @Transactional + public void deletePost(Long id, JwtPayload jwtPayload) { + + Post findPost = postRepository.findById(id) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "존재하지 않는 게시글")); + + if (!findPost.getProfile().getId().equals(jwtPayload.getProfileId())) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "권한이 없습니다."); + } postRepository.delete(findPost); } From b17c25a0d60157edfaf35e4e65b83f81047174c7 Mon Sep 17 00:00:00 2001 From: leeuihyun Date: Mon, 2 Jun 2025 15:54:34 +0900 Subject: [PATCH 4/4] =?UTF-8?q?feat=20:=20=ED=8C=94=EB=A1=9C=EC=9E=89?= =?UTF-8?q?=ED=95=9C=20=EC=82=AC=EB=9E=8C=EB=93=A4=EC=9D=98=20=EA=B2=8C?= =?UTF-8?q?=EC=8B=9C=EA=B8=80=20=EC=A1=B0=ED=9A=8C=20=EC=9E=91=EC=84=B1=20?= =?UTF-8?q?In=EC=9D=84=20=EC=82=AC=EC=9A=A9=ED=95=98=EC=97=AC=20=EA=B2=8C?= =?UTF-8?q?=EC=8B=9C=EA=B8=80=20=EC=A1=B0=ED=9A=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../example/feeda/domain/post/repository/PostRepository.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/example/feeda/domain/post/repository/PostRepository.java b/src/main/java/com/example/feeda/domain/post/repository/PostRepository.java index bd62d79..4d0ec21 100644 --- a/src/main/java/com/example/feeda/domain/post/repository/PostRepository.java +++ b/src/main/java/com/example/feeda/domain/post/repository/PostRepository.java @@ -1,14 +1,14 @@ package com.example.feeda.domain.post.repository; import com.example.feeda.domain.post.entity.Post; +import java.util.List; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.http.HttpStatus; -import org.springframework.web.server.ResponseStatusException; public interface PostRepository extends JpaRepository { Page findAllByTitleContaining(String title, Pageable pageable); + Page findAllByProfile_IdIn(List followingProfileIds, Pageable pageable); }