Skip to content

Commit

Permalink
게시글 작성 기능 API 명세의 변경에 따른 코드 수정 (#173)
Browse files Browse the repository at this point in the history
* refactor: (#95) 게시글 작성 기능 API 명세의 변경에 따라 게시글 내용의 이미지 추가

* refactor: (#95) PostController import문 정리

* refactor: (#95) h2-console 동작을 위한 설정 추가

* refactor: (#95) 게시글의 마감 기한이 현재 시간보다 3일 이상을 초과한 경우 예외 처리 기능 구현

* refactor: (#95) 마감 기한 제한으로 인한 양성 테스트 코드 수정

* refactor: (#95) 게시글 작성 API 데이터 전달 시, 잘못된 입력 값에 대한 예외 처리 기능 구현

* refactor: (#172) Dto 이름을 더 명확하게 개선

* refactor: (#172) swagger schema 설정 및 valid 설정 개선

* refactor: (#172) 예외 메시지 변경에 의한 테스트 메시지 변경

* refactor: (#172) 이미지 파일 이름 중복을 방지하기 위해 밀리초를 이름에 포함하는 것으로 개선

* refactor: (#172) 날짜 데이터 변환 형식을 DateTimeFormat이 아닌 JsonFormat으로 변환하는 것으로 변경

* refactor: (#172) 이미지 Url 저장 및 변환을 고수준인 도메인에서가 아닌 저수준인 서비스 계층에서 실행하도록 개선

* refactor: (#172) 테스트 코드 개선

* refactor: (#172) 이미지 저장에 실패했을 때 예외를 던지는 것으로 수정

* refactor: (#172) 예외 핸들러의 범위 원복

* refactor: (#172) 최대 마감 기한 유효성 검증 로직을 더 확장성 있게 코드 개선

* refactor: (#172) 새로 만든 엔티티에 BaseEntity 상속 받도록 수정

* refactor: (#172) 객체 비교 시, id로 비교하는 것으로 개선

* refactor: (#172) images 경로 push를 위한 이미지 저장

* refactor: (#172) 서버에서 필요없는 데이터 필드 삭제

* refactor: (#172) 설정 파일에도 h2 console의 path를 명시해주기

* refactor: (#172) 메서드를 사용하는 클래스 변경
  • Loading branch information
tjdtls690 authored Aug 1, 2023
1 parent 2865a12 commit b6094be
Show file tree
Hide file tree
Showing 22 changed files with 433 additions and 85 deletions.
Binary file added backend/images/testImage1.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@EqualsAndHashCode(of = {"name"})
@EqualsAndHashCode(of = {"id"})
@Getter
public class Category extends BaseEntity {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import java.net.URI;
import java.util.List;
import lombok.RequiredArgsConstructor;
Expand Down Expand Up @@ -40,15 +41,16 @@ public class PostController {
})
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<Void> save(
@RequestPart final PostCreateRequest request,
@RequestPart final List<MultipartFile> images,
@RequestPart @Valid final PostCreateRequest request,
@RequestPart final List<MultipartFile> contentImages,
@RequestPart final List<MultipartFile> optionImages,
@Auth final Member loginMember
) {
final long postId = postService.save(request, loginMember, images);
final long postId = postService.save(request, loginMember, contentImages, optionImages);
return ResponseEntity.created(URI.create("/posts/" + postId)).build();
}

@Operation(summary = "게시글 조회", description = "게시글을 조회한다.")
@Operation(summary = "전체 게시글 조회", description = "게시글을 조회한다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "게시글을 조회했습니다."),
@ApiResponse(responseCode = "400", description = "잘못된 입력입니다.")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,41 @@
package com.votogether.domain.post.dto.request;

import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import java.time.LocalDateTime;
import java.util.List;
import lombok.Builder;
import org.springframework.format.annotation.DateTimeFormat;
import org.hibernate.validator.constraints.Length;

@Schema(name = "게시글 관련 데이터", description = "게시글에 관련한 데이터들입니다.")
@Schema(description = "게시글에 관련한 데이터들입니다.")
@Builder
public record PostCreateRequest(
@Schema(description = "카테고리의 여러 아이디", example = "[0, 2]")
@Size(min = 1, message = "게시글에 해당하는 카테고리는 최소 1개 이상이어야 합니다.")
List<Long> categoryIds,

@Schema(description = "게시글 제목", example = "title")
@NotBlank(message = "제목을 입력해주세요.")
@Length(max = 100, message = "제목은 최대 100자까지 입력 가능합니다.")
String title,

@Schema(description = "게시글 내용", example = "content")
@NotBlank(message = "내용을 입력해주세요.")
@Length(max = 1000, message = "내용은 최대 1000자까지 입력 가능합니다.")
String content,
List<String> postOptionContents,

@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm")
@Schema(description = "게시글의 여러 선택지")
@Valid
@NotNull(message = "선택지는 최소 2개 이상 등록해야 합니다.")
@Size(min = 2, max = 5, message = "선택지는 최소 2개, 최대 5개까지 등록 가능합니다.")
List<PostOptionCreateRequest> postOptions,

@Schema(description = "마감 기한", example = "2023-08-01 15:30")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm")
LocalDateTime deadline
) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.votogether.domain.post.dto.request;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Builder;
import org.hibernate.validator.constraints.Length;

@Schema(description = "게시글 선택지에 관련한 데이터들입니다.")
@Builder
public record PostOptionCreateRequest(
@Schema(description = "선택지 내용", example = "content")
@NotBlank(message = "해당 선택지의 내용을 입력해주세요.")
@Length(max = 50, message = "선택지의 내용은 최대 50자까지 입력 가능합니다.")
String content
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
import com.votogether.domain.post.entity.Post;
import com.votogether.domain.post.entity.PostBody;
import com.votogether.domain.post.entity.PostCategory;
import io.swagger.v3.oas.annotations.media.Schema;
import java.time.LocalDateTime;
import java.util.List;

@Schema(description = "게시글에 관련한 데이터들입니다.")
public record PostResponse(
Long postId,
WriterResponse writer,
Expand Down
27 changes: 16 additions & 11 deletions backend/src/main/java/com/votogether/domain/post/entity/Post.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.Formula;
import org.springframework.web.multipart.MultipartFile;

@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
Expand Down Expand Up @@ -86,26 +85,22 @@ public void mapCategories(final List<Category> categories) {

public void mapPostOptionsByElements(
final List<String> postOptionContents,
final List<MultipartFile> images
final List<String> optionImageUrls
) {
this.postOptions.addAllPostOptions(toPostOptionEntities(postOptionContents, images));
this.postOptions.addAllPostOptions(toPostOptions(postOptionContents, optionImageUrls));
}

private List<PostOption> toPostOptionEntities(
private List<PostOption> toPostOptions(
final List<String> postOptionContents,
final List<MultipartFile> images
final List<String> optionImageUrls
) {
return toPostOptions(postOptionContents, images);
}

private List<PostOption> toPostOptions(final List<String> postOptionContents, final List<MultipartFile> images) {
return IntStream.rangeClosed(FIRST_OPTION_SEQUENCE, postOptionContents.size())
.mapToObj(postOptionSequence ->
PostOption.of(
postOptionContents.get(postOptionSequence - 1),
this,
postOptionSequence,
images.get(postOptionSequence - 1)
optionImageUrls.get(postOptionSequence - 1)
)
)
.toList();
Expand All @@ -115,6 +110,13 @@ public boolean hasPostOption(final PostOption postOption) {
return postOptions.contains(postOption);
}

public void validateDeadlineNotExceedByMaximumDeadline(final int maximumDeadline) {
LocalDateTime maximumDeadlineFromNow = LocalDateTime.now().plusDays(maximumDeadline);
if (this.deadline.isAfter(maximumDeadlineFromNow)) {
throw new BadRequestException(PostExceptionType.DEADLINE_EXCEED_THREE_DAYS);
}
}

public void validateWriter(final Member member) {
if (!Objects.equals(this.writer.getId(), member.getId())) {
throw new BadRequestException(PostExceptionType.NOT_WRITER);
Expand Down Expand Up @@ -160,6 +162,10 @@ public boolean isWriter(final Member member) {
return Objects.equals(this.writer, member);
}

public void addContentImage(final String contentImageUrl) {
this.postBody.addContentImage(this, contentImageUrl);
}

public long getFinalTotalVoteCount(final Member loginMember) {
if (isVisibleVoteResult(loginMember)) {
return this.totalVoteCount;
Expand All @@ -176,5 +182,4 @@ public void addComment(final Comment comment) {
comments.add(comment);
comment.setPost(this);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import jakarta.persistence.Embedded;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.web.multipart.MultipartFile;

@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
Expand All @@ -18,10 +20,18 @@ public class PostBody {
@Column(length = 1000, nullable = false)
private String content;

@Embedded
private PostContentImages postContentImages;

@Builder
private PostBody(final String title, final String content) {
this.title = title;
this.content = content;
this.postContentImages = new PostContentImages();
}

public void addContentImage(final Post post, final String contentImageUrl) {
this.postContentImages.addContentImage(post, contentImageUrl);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.votogether.domain.post.entity;

import com.votogether.domain.common.BaseEntity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@Entity
public class PostContentImage extends BaseEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "post_id", nullable = false)
private Post post;

@Column
private String imageUrl;

@Builder
public PostContentImage(final Post post, final String imageUrl) {
this.post = post;
this.imageUrl = imageUrl;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.votogether.domain.post.entity;

import com.votogether.domain.post.util.ImageUploader;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Embeddable;
import jakarta.persistence.OneToMany;
import java.util.ArrayList;
import java.util.List;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.web.multipart.MultipartFile;

@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@Embeddable
public class PostContentImages {

@OneToMany(mappedBy = "post", cascade = CascadeType.PERSIST, orphanRemoval = true)
private List<PostContentImage> contentImages = new ArrayList<>();

public void addContentImage(final Post post, final String contentImageUrl) {
this.contentImages.add(getPostContentImage(post, contentImageUrl));
}

private PostContentImage getPostContentImage(final Post post, final String contentImageUrl) {
return PostContentImage.builder()
.post(post)
.imageUrl(contentImageUrl)
.build();
}

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

import com.votogether.domain.common.BaseEntity;
import com.votogether.domain.member.entity.Member;
import com.votogether.domain.post.util.ImageUploader;
import com.votogether.domain.vote.entity.Vote;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
Expand All @@ -13,10 +14,6 @@
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
Expand Down Expand Up @@ -68,38 +65,26 @@ public static PostOption of(
final String postOptionContent,
final Post post,
final int postOptionSequence,
final MultipartFile image
final String optionImageUrl
) {
if (!image.isEmpty()) {
final String imageUrl = saveImageToPath(image);
return toPostOptionEntity(post, postOptionSequence, postOptionContent, imageUrl);
if (!optionImageUrl.isEmpty()) {
return toPostOptionEntity(post, postOptionSequence, postOptionContent, optionImageUrl);
}

return toPostOptionEntity(post, postOptionSequence, postOptionContent, "");
}

private static String saveImageToPath(final MultipartFile image) {
final String absolutePath = new File("").getAbsolutePath();
final String imageUrl = absolutePath + "/images/" + image.getOriginalFilename();

try {
Files.write(Paths.get(imageUrl), image.getBytes());
} catch (IOException ignore) {
}
return imageUrl;
}

private static PostOption toPostOptionEntity(
final Post post,
final Integer postOptionSequence,
final String postOptionContent,
final String imageUrl
final String optionImageUrl
) {
return PostOption.builder()
.post(post)
.sequence(postOptionSequence)
.content(postOptionContent)
.imageUrl(imageUrl)
.imageUrl(optionImageUrl)
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ public enum PostExceptionType implements ExceptionType {
UNRELATED_POST_OPTION(1002, "게시글 투표 옵션이 게시글과 연관되어 있지 않습니다."),
NOT_WRITER(1003, "해당 게시글 작성자가 아닙니다."),
NOT_VOTER(1004, "해당 게시글 작성자는 투표할 수 없습니다."),
;
DEADLINE_EXCEED_THREE_DAYS(1005, "마감 기한은 현재 시간으로부터 3일을 초과할 수 없습니다."),
WRONG_IMAGE(1006, "이미지 저장에 실패했습니다. 다시 시도해주세요.");

private final int code;
private final String message;
Expand Down
Loading

0 comments on commit b6094be

Please sign in to comment.