Skip to content

Commit

Permalink
Merge pull request #38 from goalSetter09/10th-Kampus-BE-33
Browse files Browse the repository at this point in the history
[FEATURE] 게시글 조회 기능 구현
  • Loading branch information
u-genuine authored Jan 21, 2025
2 parents af027f1 + 9245033 commit 459ef26
Show file tree
Hide file tree
Showing 20 changed files with 409 additions and 62 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@
import com.cotato.kampus.domain.board.dto.response.FavoriteBoardResponse;
import com.cotato.kampus.global.common.dto.DataResponse;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;

@Tag(name = "게시판(Board) API", description = "게시판 관련 API(게시글 API는 Post)")
@RestController
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
@RequestMapping("/v1/api/boards")
Expand All @@ -25,15 +28,17 @@ public class BoardController {
private final BoardService boardService;

@GetMapping("")
public ResponseEntity<DataResponse<BoardListResponse>> getBoardList(){
@Operation(summary = "게시판 목록 조회", description = "전체 게시판 목록을 조회합니다.")
public ResponseEntity<DataResponse<BoardListResponse>> getBoardList() {
return ResponseEntity.ok(DataResponse.from(
BoardListResponse.of(
BoardListResponse.of(
boardService.getBoardList()))
);
);
}

@GetMapping("/university")
public ResponseEntity<DataResponse<BoardResponse>> getUniversityBoard(){
@Operation(summary = "대학교 게시판 조회", description = "대학교 관련 게시판 정보를 조회합니다.")
public ResponseEntity<DataResponse<BoardResponse>> getUniversityBoard() {
return ResponseEntity.ok(DataResponse.from(
BoardResponse.of(
boardService.getUniversityBoard()
Expand All @@ -42,7 +47,8 @@ public ResponseEntity<DataResponse<BoardResponse>> getUniversityBoard(){
}

@GetMapping("/favorite")
public ResponseEntity<DataResponse<BoardListResponse>> getFavoriteBoardList(){
@Operation(summary = "즐겨찾기 게시판 목록 조회", description = "즐겨찾기에 등록된 게시판 목록을 조회합니다.")
public ResponseEntity<DataResponse<BoardListResponse>> getFavoriteBoardList() {
return ResponseEntity.ok(DataResponse.from(
BoardListResponse.of(
boardService.getFavoriteBoardList()
Expand All @@ -51,24 +57,26 @@ public ResponseEntity<DataResponse<BoardListResponse>> getFavoriteBoardList(){
}

@PostMapping("/favorite/{boardId}")
@Operation(summary = "게시판 즐겨찾기 추가", description = "특정 게시판을 즐겨찾기에 추가합니다.")
public ResponseEntity<DataResponse<FavoriteBoardResponse>> addFavoriteBoard(
@PathVariable Long boardId
){
) {
return ResponseEntity.ok(DataResponse.from(
FavoriteBoardResponse.of(
boardService.addFavoriteBoard(boardId)
)
));
}

@DeleteMapping("favorite/{boardId}")
@DeleteMapping("/favorite/{boardId}")
@Operation(summary = "게시판 즐겨찾기 삭제", description = "특정 게시판을 즐겨찾기에서 삭제합니다.")
public ResponseEntity<DataResponse<FavoriteBoardResponse>> removeFavoriteBoard(
@PathVariable Long boardId
){
) {
return ResponseEntity.ok(DataResponse.from(
FavoriteBoardResponse.of(
boardService.removeFavoriteBoard(boardId)
)
));
}
}
}

This file was deleted.

67 changes: 50 additions & 17 deletions src/main/java/com/cotato/kampus/domain/post/api/PostController.java
Original file line number Diff line number Diff line change
@@ -1,57 +1,90 @@
package com.cotato.kampus.domain.post.api;

import java.util.List;

import org.springframework.http.MediaType;
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.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import com.cotato.kampus.domain.post.application.PostService;
import com.cotato.kampus.domain.post.dto.request.PostCreateRequest;
import com.cotato.kampus.domain.post.dto.response.PostCreateResponse;
import com.cotato.kampus.domain.post.dto.response.PostDeleteResponse;
import com.cotato.kampus.domain.post.dto.response.PostDetailResponse;
import com.cotato.kampus.domain.post.dto.response.PostSliceFindResponse;
import com.cotato.kampus.global.common.dto.DataResponse;
import com.cotato.kampus.global.error.exception.ImageException;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;

@Tag(name = "게시글(Post) API", description = "게시글 관련 API(게시판 API는 Board)")
@RestController
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
@RequestMapping("/v1/api/posts")
public class PostController {

private final PostService postService;

@PostMapping("")
@PostMapping(value = "", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@Operation(summary = "게시글 생성", description = "게시글 생성 요청입니다.")
public ResponseEntity<DataResponse<PostCreateResponse>> createPost(
@RequestPart PostCreateRequest request,
@RequestPart(required = false) List<MultipartFile> images) throws ImageException {

@Parameter(description = "Post creation request")
@ModelAttribute PostCreateRequest request) throws ImageException {
return ResponseEntity.ok(DataResponse.from(
PostCreateResponse.of(
postService.createPost(
request.boardId(),
request.title(),
request.content(),
request.postCategory(),
request.anonymity(),
images
request.boardId(),
request.title(),
request.content(),
request.postCategory(),
request.anonymity(),
request.images()
)
)
));
}

@GetMapping("/boards/{boardId}")
@Operation(summary = "게시판의 게시글 리스트 조회", description = "BoardId에 해당하는 전체 게시글을 최신순으로 정렬한 후 슬라이싱 하여 조회합니다.(슬라이스 별 기본 게시글 수: 10)")
public ResponseEntity<DataResponse<PostSliceFindResponse>> findPosts(
@PathVariable Long boardId,
@RequestParam(required = false, defaultValue = "0") int page) {
return ResponseEntity.ok(
DataResponse.from(
PostSliceFindResponse.from(
postService.findPosts(boardId, page)
)
)
);
}

@GetMapping("{postId}")
@Operation(summary = "게시글 상세 조회", description = "게시글을 세부 내역을 조회합니다.")
public ResponseEntity<DataResponse<PostDetailResponse>> findPostDetail(
@PathVariable Long postId) {
return ResponseEntity.ok(
DataResponse.from(
PostDetailResponse.from(
postService.findPostDetail(postId)
)
)
);
}

@DeleteMapping("/{postId}")
@Operation(summary = "게시글 삭제", description = "postId를 통해 게시글 삭제")
public ResponseEntity<DataResponse<PostDeleteResponse>> deletePost(
@PathVariable Long postId
){

) {
return ResponseEntity.ok(DataResponse.from(
PostDeleteResponse.of(
postService.deletePost(
Expand All @@ -60,4 +93,4 @@ public ResponseEntity<DataResponse<PostDeleteResponse>> deletePost(
)
));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.cotato.kampus.domain.post.application;

import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import com.cotato.kampus.domain.common.application.ApiUserResolver;
import com.cotato.kampus.domain.common.enums.Anonymity;
import com.cotato.kampus.domain.post.dto.AnonymousOrPostAuthor;
import com.cotato.kampus.domain.post.dto.PostDto;
import com.cotato.kampus.domain.user.dao.UserRepository;
import com.cotato.kampus.domain.user.domain.User;
import com.cotato.kampus.global.error.ErrorCode;
import com.cotato.kampus.global.error.exception.AppException;

import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;

@Component
@Transactional(readOnly = true)
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
public class PostAuthorResolver {

private final UserRepository userRepository;
private final ApiUserResolver apiUserResolver;

public AnonymousOrPostAuthor getAuthor(PostDto postDto) {
AnonymousOrPostAuthor anonymousOrPostAuthor;

User author = userRepository.findById(postDto.userId())
.orElseThrow(() -> new AppException(ErrorCode.USER_NOT_FOUND));

// 현재 사용자가 게시글 작성자인지 확인
Boolean isAuthor = apiUserResolver.getUserId().equals(author.getId());

if (postDto.anonymity().equals(Anonymity.ANONYMOUS)) {
// 익명 사용자 정보 반환
return AnonymousOrPostAuthor.of(true, isAuthor, -1L, "Anonymous", "Default profile image");
} else {
// 작성자 정보 반환
return AnonymousOrPostAuthor.of(false, isAuthor, author.getId(), author.getUsername(),
author.getProfileImage());
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
package com.cotato.kampus.domain.post.application;

import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import com.cotato.kampus.domain.post.dao.PostPhotoRepository;
import com.cotato.kampus.domain.post.dao.PostRepository;
import com.cotato.kampus.domain.post.domain.Post;
import com.cotato.kampus.domain.post.domain.PostPhoto;
import com.cotato.kampus.domain.post.dto.PostDto;
import com.cotato.kampus.domain.post.dto.PostWithPhotos;
import com.cotato.kampus.global.common.dto.CustomPageRequest;
import com.cotato.kampus.global.error.ErrorCode;
import com.cotato.kampus.global.error.exception.AppException;

Expand All @@ -15,18 +22,44 @@
@Transactional(readOnly = true)
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
public class PostFinder {
private final PostRepository postRepository;

public Post getPost(Long postId){
private final PostRepository postRepository;
private final PostPhotoRepository postPhotoRepository;
private static final Integer PAGE_SIZE = 10;
public static final String SORT_PROPERTY = "createdTime";

public Post getPost(Long postId) {
return postRepository.findById(postId)
.orElseThrow(() -> new AppException(ErrorCode.POST_NOT_FOUND));
}

public Long getAuthorId(Long postId){
public Long getAuthorId(Long postId) {
Post post = postRepository.findById(postId)
.orElseThrow(() -> new AppException(ErrorCode.POST_NOT_FOUND));

return post.getUserId();
}

public Slice<PostWithPhotos> findPosts(Long boardId, int page) {
// 1. Post 리스트를 Slice로 조회
CustomPageRequest customPageRequest = new CustomPageRequest(page, PAGE_SIZE, Sort.Direction.DESC);
Slice<Post> posts = postRepository.findAllByBoardIdOrderByCreatedTimeDesc(
boardId,
customPageRequest.of(SORT_PROPERTY)
);

// 2. Post -> PostWithPhotos 매핑
return posts.map(post -> {
PostPhoto postPhoto = postPhotoRepository.findFirstByPostIdOrderByCreatedTimeDesc(post.getId())
.orElse(null);
return PostWithPhotos.from(post, postPhoto);
});
}

public PostDto findPost(Long postId) {
Post post = postRepository.findById(postId)
.orElseThrow(() -> new AppException(ErrorCode.POST_NOT_FOUND));

return PostDto.from(post);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.cotato.kampus.domain.post.application;

import java.util.List;

import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import com.cotato.kampus.domain.post.dao.PostPhotoRepository;
import com.cotato.kampus.domain.post.domain.PostPhoto;

import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;

@Component
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
@Transactional(readOnly = true)
public class PostImageFinder {

private final PostPhotoRepository postPhotoRepository;

public List<String> findPostPhotos(Long postId) {
return postPhotoRepository.findALlByPostId(postId).stream()
.map(PostPhoto::getPhotoUrl)
.toList();
}
}
Loading

0 comments on commit 459ef26

Please sign in to comment.