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

Feat#21 게시물 생성, 삭제, 수정 API 구현 #26

Merged
merged 13 commits into from
Nov 7, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ public interface MemberRepository extends JpaRepository<Member, Long> {
boolean existsByNicknameAndKakaoId(String nickname, Long kakaoId);
Optional<Member> findByNicknameAndKakaoId(String nickname, Long kakaoId);
boolean existsByTag(String tag);

Optional<Member>findByTag(String tag);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.NoSuchElementException;

@Service
@RequiredArgsConstructor
public class MemberService {
Expand All @@ -28,4 +30,9 @@ public void saveMember(Member member) {
public boolean checkMemberExistsBy(String tag) {
return this.memberRepository.existsByTag(tag);
}

public Member findMemberByTag(String tag){
return this.memberRepository.findByTag(tag).orElseThrow(
NoSuchMemberException::new);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,48 @@
package com.leets.team2.xclone.domain.post.controller;

import com.leets.team2.xclone.common.ApiData;
import com.leets.team2.xclone.domain.member.entities.Member;
import com.leets.team2.xclone.domain.member.service.MemberService;
import com.leets.team2.xclone.domain.post.dto.PostDTO;
import com.leets.team2.xclone.domain.post.entity.Post;
import com.leets.team2.xclone.domain.post.service.PostService;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/posts")
public class PostController {
private final PostService postService;
private final MemberService memberService;

@PostMapping("/create")//게시물 생성
public ResponseEntity<ApiData<PostDTO>> createPost(@RequestParam String authorTag, @RequestBody @Validated PostDTO postDTO){
Member author=memberService.findMemberByTag(authorTag);
Post post=postService.createPost(postDTO,author);
Copy link
Collaborator

Choose a reason for hiding this comment

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

이런 비즈니스 로직은 Service layer로 분리하는게 좋아보입니다👍

return ApiData.created(new PostDTO(
post.getId(),
post.getTitle(),
post.getContent()
));
}

@PatchMapping("/{postId}/edit")
Copy link
Contributor

Choose a reason for hiding this comment

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

HTTP 메서드 자체가 어떤 동작을 수행하는지 보이기때문에 edit 등을 경로에 동작을 명시 안하는게 좋아보입니다

Copy link
Contributor Author

Choose a reason for hiding this comment

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

삭제했습니다!!

public ResponseEntity<ApiData<PostDTO>> updatePost(@PathVariable Long postId,@RequestBody @Validated PostDTO postDTO){
Post updatedPost=postService.updatePost(postId,postDTO);
Copy link
Contributor

Choose a reason for hiding this comment

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

본인이 쓴 게시물인지 확인하는 로직이 필요해 보여요

Copy link
Contributor Author

Choose a reason for hiding this comment

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

구현했습니다. 확인 한번 부탁드립니다.

return ApiData.ok(new PostDTO(
updatedPost.getId(),
updatedPost.getTitle(),
updatedPost.getContent()
));
}

@DeleteMapping("/{postId}/delete")
Copy link
Contributor

Choose a reason for hiding this comment

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

여기서도 delete를 경로에 동작을 명시 안하는게 좋아보입니다

Copy link
Contributor Author

Choose a reason for hiding this comment

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

아 그렇네요!! 어차피 DeleteMapping해서 없어도 될 것 같아요!! 삭제했습니다.

public ResponseEntity<ApiData<Void>> deletePost(@PathVariable Long postId){
postService.deletePost(postId);
Copy link
Contributor

Choose a reason for hiding this comment

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

여기서도 본인이 쓴 게시물인지 확인하는 로직도 필요해보여요

Copy link
Contributor Author

Choose a reason for hiding this comment

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

구현했습니다. 확인한번 부탁드립니다.

return ApiData.ok(null);
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

DTO를 사용하기로 한 이상, 각각의 Request와 Response에 대해서 각각 다른 DTO를 사용하는게 좋습니다.
만약, 여러 컨트롤러가 같은 DTO를 사용하고있는 상황에서 하나의 컨트롤러의 변경으로 인해 DTO가 변경된다면 다른 컨트롤러까지 영향을 받으니까요🤔 같은 필드를 가진 DTO라고 하더라도 분리해서 사용하는건 어떠신가요?
(저는 개인적으로 DTO는 무한으로 복사되어도 상관 없다는 생각이긴 합니다...ㅎㅎ😅)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

수정하였습니다. 확인 한번 부탁드립니다.

}
34 changes: 15 additions & 19 deletions src/main/java/com/leets/team2/xclone/domain/post/dto/PostDTO.java
Original file line number Diff line number Diff line change
@@ -1,27 +1,23 @@
package com.leets.team2.xclone.domain.post.dto;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class PostDTO {
//DTO 같은 경우 각각의 기능에 따른 DTO를 전부 만들 예정입니다. 다음은 예시 상황입니다. 이런 방식으로 구현하는건 어떻게 생각하시는지 의견 부탁드리겠습니다.
public record CreatePostRequest(
@NotNull(message = "아이디는 필수입니다.")
Long postId,

@NotBlank(message = "제목은 필수입니다.")
String title,
private Long id;//게시물 id

@NotBlank
private String title;//제목

@NotBlank
private String content;//내용

@NotBlank(message = "내용은 필수입니다.")
String content
){}
public record CreatePostResponse(
Long postId,
String title,
String content
){
public static CreatePostResponse of(Long postId,String title,String content) {
return new CreatePostResponse(postId,title,content);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.leets.team2.xclone.domain.member.entities.Member;
import jakarta.persistence.*;
import lombok.*;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;

Expand All @@ -13,15 +14,16 @@
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Getter
@Setter
public class Post extends AbstractEntity {//정말 기본적인 부분들만 일단 만들어냈습니다.
@Column(name="title",nullable = false)
private String title;

@Column(name="content",nullable = false)
private String content;

@ManyToOne
@JoinColumn(name="member_id",nullable = false)
private Member member;
@ManyToOne(fetch=FetchType.LAZY)//Member랑 다대일
@JoinColumn(name="author_id",nullable = false)//author_id로 Member와 연결
private Member author;//작성자를 멤버 객체로.

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.leets.team2.xclone.domain.post.entity.Post;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface PostRepository extends JpaRepository<Post,Long> {
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,43 @@
package com.leets.team2.xclone.domain.post.service;

import com.leets.team2.xclone.domain.member.entities.Member;
import com.leets.team2.xclone.domain.member.repository.MemberRepository;
import com.leets.team2.xclone.domain.member.service.MemberService;
import com.leets.team2.xclone.domain.post.dto.PostDTO;
import com.leets.team2.xclone.domain.post.entity.Post;
import com.leets.team2.xclone.domain.post.repository.PostRepository;
import com.leets.team2.xclone.exception.PostNotFoundException;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.NoSuchElementException;

@Service
@RequiredArgsConstructor
public class PostService {
private final PostRepository postRepository;

public Post createPost(PostDTO postDTO, Member author){
Post post=Post.builder()
.title(postDTO.getTitle())
.content(postDTO.getContent())
.author(author)
.build();
return postRepository.save(post);
}

public Post updatePost(Long postId,PostDTO postDTO){
Post post=postRepository.findById(postId)
.orElseThrow(PostNotFoundException::new);
post.setTitle(postDTO.getTitle());
post.setContent(postDTO.getContent());
return postRepository.save(post);
}

public void deletePost(Long postId){
if(!postRepository.existsById(postId)){//게시물이 없을 경우 예외 발생
throw new PostNotFoundException();
}
postRepository.deleteById(postId);//해당 게시물 아이디의 게시물 삭제
}
}
2 changes: 2 additions & 0 deletions src/main/java/com/leets/team2/xclone/exception/ErrorInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ public enum ErrorInfo {
NO_SUCH_MEMBER(HttpStatus.NOT_FOUND, "해당 멤버를 찾을 수 없습니다.", 10001),
ALREADY_EXIST_MEMBER(HttpStatus.CONFLICT, "이미 존재하는 멤버입니다.", 10002),

NO_SUCH_POST(HttpStatus.NOT_FOUND,"해당 게시물을 찾을 수 없습니다.",10003),//게시물을 못 찾았을 때 에러 정보

// jwt 영역
INVALID_TOKEN(HttpStatus.BAD_REQUEST, "잘못된 토큰입니다.", 10100);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.leets.team2.xclone.exception;

public class PostNotFoundException extends ApplicationException{//게시물을 찾지 못했을 경우 에러
public PostNotFoundException(){
super(ErrorInfo.NO_SUCH_POST);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.leets.team2.xclone.common.ApiData;
import com.leets.team2.xclone.exception.ApplicationException;
import com.leets.team2.xclone.exception.PostNotFoundException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
Expand Down Expand Up @@ -41,4 +42,9 @@ public String handleAnyCheckedException(Exception e) {
public ResponseEntity<ApiData<String>> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
return ApiData.validationFailure(e.getFieldErrors());
}

@ExceptionHandler(PostNotFoundException.class)
public ResponseEntity<ApiData<String>> handlePostNotFoundException(PostNotFoundException e){
return ApiData.errorFrom(e.getErrorInfo());
}
}