Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ dependencies {
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'

// Valid
implementation 'org.springframework.boot:spring-boot-starter-validation'

// Swagger
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.9'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,3 @@ public enum Role {

private final String key;
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.moongeul.backend.api.post.controller;

import com.moongeul.backend.api.post.dto.PostCreateRequestDTO;
import com.moongeul.backend.api.post.dto.PostCreateResponseDTO;
import com.moongeul.backend.api.post.service.PostService;
import com.moongeul.backend.common.response.ApiResponse;
import com.moongeul.backend.common.response.SuccessStatus;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Tag(name = "Post", description = "Post(게시글) 관련 API 입니다.")
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v2/post")
public class PostController {

private final PostService postService;

@Operation(
summary = "글쓰기 API",
description = "기록(게시글)을 작성하는 글쓰기 API 입니다." +
"<br>필수: isbn, readDate / 선택: rating(default = 5.0), page(default = 300), content, quotes" +
"<br>선택 요소의 경우 입력되지 않았을 때 'null'로 전달 바랍니다."
)
@ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "글쓰기 성공"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "ISBN은 필수입니다. (isbn)"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "해당 도서를 찾을 수 없습니다.")
})
@PostMapping("/create")
public ResponseEntity<ApiResponse<PostCreateResponseDTO>> createPost(@AuthenticationPrincipal UserDetails userDetails,
@Valid @RequestBody PostCreateRequestDTO postCreateRequestDTO) {

PostCreateResponseDTO response = postService.createPost(postCreateRequestDTO, userDetails.getUsername());
return ApiResponse.success(SuccessStatus.CREATE_POST_SUCCESS, response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package com.moongeul.backend.api.post.dto;

import com.moongeul.backend.api.book.entity.Book;
import com.moongeul.backend.api.member.entity.Member;
import com.moongeul.backend.api.post.entity.Post;
import com.moongeul.backend.api.post.entity.Quote;
import jakarta.validation.constraints.*;
import lombok.Builder;
import lombok.Getter;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;

@Getter
@Builder
public class PostCreateRequestDTO {

/* 드롭다운 - 필수 입력*/

/* 필수 입력 */
@NotBlank(message = "ISBN은 필수입니다")
private String isbn; // 책

@NotNull(message = "독서 완료일은 필수입니다")
@PastOrPresent(message = "독서 완료일은 미래일 수 없습니다")
private LocalDate readDate; // 읽은 날짜

/* 기본값 있는 필드 */
@Min(value = 0, message = "평점은 0.0 이상이어야 합니다.")
@Max(value = 5, message = "평점은 5.0 이하이어야 합니다.")
private Double rating; // 평점

@Positive(message = "페이지 수는 양수여야 합니다")
private Integer page; // 페이지 수

/* 선택 입력 */
@Size(max = 3000, message = "내용은 5000자 이하로 작성해야 합니다.")
private String content; // 감상평
private List<QuoteRequestDTO> quotes; // 인상깊은구절

@Getter
public static class QuoteRequestDTO {
private String quote; // 인용문 내용
private Integer pageNumber; // 페이지 번호
}

public Post toEntity(Member member, Book book) {

// rating과 page가 null로 들어오면 기본값으로 대체
Double finalRating = (this.rating != null) ? this.rating : 5.0;
Integer finalPage = (this.page != null) ? this.page : 300;

Post post = Post.builder()
.readDate(this.readDate)
.rating(finalRating)
.page(finalPage)
.content(this.content)
.member(member)
.book(book)
.build();

// Quote 처리
if (this.quotes != null && !this.quotes.isEmpty()) {
List<Quote> quoteList = new ArrayList<>();
for (QuoteRequestDTO quoteDTO : this.quotes) {
Quote quote = Quote.builder()
.quoteContent(quoteDTO.getQuote())
.pageNumber(quoteDTO.getPageNumber())
.post(post)
.build();
quoteList.add(quote);

post.addQuotes(quoteList);
}
}
return post;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.moongeul.backend.api.post.dto;

import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class PostCreateResponseDTO {

private Long post_id; // 생성된 Post id
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.moongeul.backend.api.post.dto;

import com.moongeul.backend.api.book.dto.BookDTO;
import lombok.Builder;
import lombok.Getter;

import java.util.List;

@Getter
@Builder
public class PostResponseDTO {

// 필수
private BookDTO bookDTO; // 필요 - 책 제목/작가/출판사
private double rating;

// 선택
private String content; // 감상평
private List<String> quotes; // 인상깊은구절 리스트

}
27 changes: 27 additions & 0 deletions src/main/java/com/moongeul/backend/api/post/entity/Category.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.moongeul.backend.api.post.entity;

import com.moongeul.backend.api.member.entity.Member;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@Builder // 빌더 패턴 사용을 위한 롬복 애너테이션
@NoArgsConstructor // 기본 생성자
@AllArgsConstructor // 모든 필드를 포함한 생성자
@Table(name = "CATEGORY") // 데이터베이스 테이블 이름 지정
public class Category {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; // 카테고리 id

private String title; // 카테고리 제목

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id", nullable = false)
private Member member;
}
56 changes: 56 additions & 0 deletions src/main/java/com/moongeul/backend/api/post/entity/Post.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.moongeul.backend.api.post.entity;

import com.moongeul.backend.api.book.entity.Book;
import com.moongeul.backend.api.member.entity.Member;
import com.moongeul.backend.common.entity.BaseTimeEntity;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;

@Entity
@Getter
@Builder // 빌더 패턴 사용을 위한 롬복 애너테이션
@NoArgsConstructor // 기본 생성자
@AllArgsConstructor // 모든 필드를 포함한 생성자
@Table(name = "POST") // 데이터베이스 테이블 이름 지정
public class Post extends BaseTimeEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; // 게시글 id

private LocalDate readDate; // 읽은날짜
private Double rating; // 평점
private Integer page; // 페이지 수
private String content; // 감상평

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id", nullable = false)
private Member member;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "book_isbn", nullable = false)
private Book book;

// @ManyToOne(fetch = FetchType.LAZY)
// @JoinColumn(name = "category_id", nullable = false)
// private Category category;

@OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true)
@Builder.Default
private List<Quote> quotes = new ArrayList<>(); // 인상깊은구절

public void addQuote(Quote quote) {
quotes.add(quote);
}

public void addQuotes(List<Quote> quoteList) {
quoteList.forEach(this::addQuote);
}
}
27 changes: 27 additions & 0 deletions src/main/java/com/moongeul/backend/api/post/entity/Quote.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.moongeul.backend.api.post.entity;

import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@Builder // 빌더 패턴 사용을 위한 롬복 애너테이션
@NoArgsConstructor // 기본 생성자
@AllArgsConstructor // 모든 필드를 포함한 생성자
@Table(name = "QUOTE") // 데이터베이스 테이블 이름 지정
public class Quote {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; // 인상깊은구절 id

private String quoteContent; // 인상깊은구절 내용
private int pageNumber; // 페이지 번호

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "post_id", nullable = false)
private Post post;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.moongeul.backend.api.post.repository;

import com.moongeul.backend.api.post.entity.Post;
import org.springframework.data.jpa.repository.JpaRepository;

public interface PostRepository extends JpaRepository<Post, Long> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.moongeul.backend.api.post.service;

import com.moongeul.backend.api.book.entity.Book;
import com.moongeul.backend.api.book.repository.BookRepository;
import com.moongeul.backend.api.member.entity.Member;
import com.moongeul.backend.api.member.repository.MemberRepository;
import com.moongeul.backend.api.post.dto.PostCreateRequestDTO;
import com.moongeul.backend.api.post.dto.PostCreateResponseDTO;
import com.moongeul.backend.api.post.entity.Post;
import com.moongeul.backend.api.post.repository.PostRepository;
import com.moongeul.backend.common.exception.NotFoundException;
import com.moongeul.backend.common.response.ErrorStatus;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class PostService {

private final MemberRepository memberRepository;
private final BookRepository bookRepository;
private final PostRepository postRepository;

/* 글쓰기 */
@Transactional
public PostCreateResponseDTO createPost(PostCreateRequestDTO postCreateRequestDTO, String email){

Member member = memberRepository.findByEmail(email)
.orElseThrow(() -> new NotFoundException(ErrorStatus.USER_NOTFOUND_EXCEPTION.getMessage()));

Book book = bookRepository.findByIsbn(postCreateRequestDTO.getIsbn())
.orElseThrow(() -> new NotFoundException(ErrorStatus.BOOK_NOTFOUND_EXCEPTION.getMessage()));

Post newPost = postCreateRequestDTO.toEntity(member, book);
Post savedPost = postRepository.save(newPost);

return PostCreateResponseDTO.builder()
.post_id(savedPost.getId())
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public enum SuccessStatus {
ADD_WISH_READ_BOOK_SUCCESS(HttpStatus.OK, "읽고 싶은 책 등록 성공"),
REMOVE_WISH_READ_BOOK_SUCCESS(HttpStatus.OK, "읽고 싶은 책 삭제 성공"),
REISSUE_TOKEN_SUCCESS(HttpStatus.OK, "토큰 재발급 성공"),
CREATE_POST_SUCCESS(HttpStatus.OK, "글쓰기 성공"),

/**
* 201
Expand Down