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

[백지현] 3차 과제 제출 #13

Open
wants to merge 8 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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 backend/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
*.properties
*.yml

HELP.md
.gradle
build/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ public enum ErrorCode {
BAD_REQUEST(HttpStatus.BAD_REQUEST, "잘못된 요청입니다.", "COMMON-001"),
INVALID_PARAMETER(HttpStatus.BAD_REQUEST, "요청 파라미터가 잘 못 되었습니다.", "COMMON-002"),

// 404
POST_NOT_FOUND(HttpStatus.NOT_FOUND, "게시글을 찾을 수 없습니다.", "POST-001"),

//500
INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "서버 내부에서 에러가 발생하였습니다.", "COMMON-002"),
;
Expand Down
13 changes: 0 additions & 13 deletions backend/src/main/java/cotato/backend/domains/post/Post.java

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,26 +1,74 @@
package cotato.backend.domains.post;

import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.http.ResponseEntity;
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.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import cotato.backend.common.dto.DataResponse;
import cotato.backend.domains.post.dto.request.CreatePostRequest;
import cotato.backend.domains.post.dto.request.SavePostsByExcelRequest;
import cotato.backend.domains.post.dto.response.PostPageResponse;
import cotato.backend.domains.post.dto.response.PostResponse;
import io.swagger.v3.oas.annotations.Operation;
import lombok.RequiredArgsConstructor;

@RestController
@RequestMapping("/api/posts")
@RequiredArgsConstructor
public class PostController {

private static final int PAGE_SIZE = 10;
Copy link
Member

Choose a reason for hiding this comment

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

페이지 사이즈를 상수화시킨 것 좋네요!


private final PostService postService;

@Operation(summary = "엑셀 파일로 게시글 저장", description = "엑셀 파일 경로를 받아 게시글 목록을 저장합니다.")
@PostMapping("/excel")
public ResponseEntity<DataResponse<Void>> savePostsByExcel(@RequestBody SavePostsByExcelRequest request) {
postService.saveEstatesByExcel(request.getPath());

return ResponseEntity.ok(DataResponse.ok());
}

@Operation(summary = "게시글 생성", description = "새로운 게시글을 생성합니다.")
@PostMapping("/")
public ResponseEntity<DataResponse<Void>> createPost(@RequestBody CreatePostRequest request) {
postService.createPost(request);

return ResponseEntity.ok(DataResponse.ok());
}

@Operation(summary = "게시글 상세 조회", description = "ID를 통해 특정 게시글을 조회합니다.")
@GetMapping("/{postId}")
public ResponseEntity<DataResponse<PostResponse>> getPostById(@PathVariable Long postId) {
PostResponse response = postService.getPostById(postId);
return ResponseEntity.ok(DataResponse.from(response));
}

@Operation(summary = "게시글 목록 조회", description = "게시글을 좋아요 수(likes) 순으로 정렬하여 페이징 처리 후 반환합니다.")
@GetMapping("/")
public ResponseEntity<DataResponse<PostPageResponse>> getPostList(
@RequestParam(defaultValue = "0") int page // 기본 페이지는 0번
) {
Pageable pageable = PageRequest.of(page, PAGE_SIZE, Sort.by("likes").descending());
PostPageResponse response = postService.getPostList(pageable);
return ResponseEntity.ok(DataResponse.from(response));
}

@Operation(summary = "게시글 삭제", description = "ID를 통해 특정 게시글을 삭제합니다.")
@DeleteMapping("/{postId}")
public ResponseEntity<DataResponse<Void>> deletePost(@PathVariable Long postId) {
postService.deletePost(postId);
return ResponseEntity.ok(DataResponse.ok());
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,20 @@
import java.util.List;
import java.util.stream.Collectors;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import cotato.backend.common.excel.ExcelUtils;
import cotato.backend.common.exception.ApiException;
import cotato.backend.common.exception.ErrorCode;
import cotato.backend.domains.post.dto.request.CreatePostRequest;
import cotato.backend.domains.post.dto.response.PostPageResponse;
import cotato.backend.domains.post.dto.response.PostResponse;
import cotato.backend.domains.post.entity.Post;
import cotato.backend.domains.post.repository.PostRepository;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -19,6 +28,7 @@
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
@Transactional
public class PostService {
private final PostRepository postRepository;

// 로컬 파일 경로로부터 엑셀 파일을 읽어 Post 엔터티로 변환하고 저장
public void saveEstatesByExcel(String filePath) {
Expand All @@ -30,13 +40,48 @@ public void saveEstatesByExcel(String filePath) {
String content = row.get("content");
String name = row.get("name");

return new Post(title, content, name);
return Post.builder()
.title(title)
.content(content)
.name(name)
.build();
})
.collect(Collectors.toList());

postRepository.saveAll(posts);

} catch (Exception e) {
log.error("Failed to save estates by excel", e);
throw ApiException.from(INTERNAL_SERVER_ERROR);
}
}

public void createPost(CreatePostRequest request) {
Post post = Post.builder()
.title(request.getTitle())
.content(request.getContent())
.name(request.getName())
.build();
postRepository.save(post);
}

public PostResponse getPostById(Long id) {
Post post = postRepository.findById(id)
.orElseThrow(() -> ApiException.from(ErrorCode.POST_NOT_FOUND));

post.incrementViews();

return PostResponse.from(post);
}

public PostPageResponse getPostList(Pageable pageable) {
Page<Post> postPage = postRepository.findAllByOrderByLikesDesc(pageable);
return PostPageResponse.from(postPage);
}

public void deletePost(Long postId) {
Post post = postRepository.findById(postId)
.orElseThrow(() -> ApiException.from(ErrorCode.POST_NOT_FOUND));
postRepository.delete(post);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package cotato.backend.domains.post.dto.request;

import lombok.Getter;

@Getter
public class CreatePostRequest {
private String title;
Copy link
Member

Choose a reason for hiding this comment

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

request에 Validation을 넣어주면 객체가 생성되는 단계에서 유효성 검사를 할 수 있어요!

private String content;
private String name;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package cotato.backend.domains.post.dto.response;

import java.util.List;
import java.util.stream.Collectors;

import org.springframework.data.domain.Page;

import cotato.backend.domains.post.entity.Post;
import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public class PostPageResponse {
Copy link
Member

Choose a reason for hiding this comment

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

java17부터 불변객체임을 증명해주고 효율적으로 코드를 짤 수 있는 record 클래스라는게 있어요!
한번 공부해보시면 좋을 것 같아요.

레퍼런스: https://s7won.tistory.com/2

private List<PostSummary> posts;
private int currentPage;
private int totalPages;

public static PostPageResponse from(Page<Post> postPage) {
List<PostSummary> posts = postPage.getContent().stream()
.map(PostSummary::from)
.collect(Collectors.toList());

return new PostPageResponse(posts, postPage.getNumber(), postPage.getTotalPages());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package cotato.backend.domains.post.dto.response;

import cotato.backend.domains.post.entity.Post;
import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public class PostResponse {
private Long id;
private String title;
private String content;
private String name;
private int views;

public static PostResponse from(Post post) {
return new PostResponse(
post.getId(),
post.getTitle(),
post.getContent(),
post.getName(),
post.getViews()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package cotato.backend.domains.post.dto.response;

import cotato.backend.domains.post.entity.Post;
import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public class PostSummary {
private Long id;
private String title;
private String name;

public static PostSummary from(Post post) {
Copy link
Member

Choose a reason for hiding this comment

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

정적 팩토리 메소드👍

return new PostSummary(post.getId(), post.getTitle(), post.getName());
}
}
42 changes: 42 additions & 0 deletions backend/src/main/java/cotato/backend/domains/post/entity/Post.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package cotato.backend.domains.post.entity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Builder
public class Post {
@Id
@Column(name = "post_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false)
private String title;

@Column(nullable = false)
private String content;

@Column(nullable = false)
private String name;

@Column(nullable = false)
private int views = 0;

private int likes = 0;

public void incrementViews() {
this.views++;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package cotato.backend.domains.post.repository;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;

import cotato.backend.domains.post.entity.Post;

public interface PostRepository extends JpaRepository<Post, Long> {
Page<Post> findAllByOrderByLikesDesc(Pageable pageable);
}
1 change: 0 additions & 1 deletion backend/src/main/resources/application.properties

This file was deleted.