Skip to content

Commit

Permalink
Merge pull request #32 from IT-Cotato/feat/21-post
Browse files Browse the repository at this point in the history
[Feat] Post create, delete 기능 구현
  • Loading branch information
goalSetter09 authored Jan 16, 2025
2 parents 3eb27c6 + 7698d5f commit 21e16ff
Show file tree
Hide file tree
Showing 12 changed files with 290 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.cotato.kampus.domain.post.api;

import java.util.List;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PatchMapping;
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.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.global.common.dto.DataResponse;
import com.cotato.kampus.global.error.exception.ImageException;

import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;

@RestController
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
@RequestMapping("/v1/api/posts")
public class PostController {

private final PostService postService;

@PostMapping("")
public ResponseEntity<DataResponse<PostCreateResponse>> createPost(
@RequestPart PostCreateRequest request,
@RequestPart(required = false) List<MultipartFile> images) throws ImageException {

return ResponseEntity.ok(DataResponse.from(
PostCreateResponse.of(
postService.createPost(
request.boardId(),
request.title(),
request.content(),
request.postCategory(),
images
)
)
));
}

@DeleteMapping("/{postId}")
public ResponseEntity<DataResponse<PostDeleteResponse>> deletePost(
@PathVariable Long postId
){
return ResponseEntity.ok(DataResponse.from(
PostDeleteResponse.of(
postService.deletePost(
postId
)
)
));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
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.PostStatus;
import com.cotato.kampus.domain.post.dao.PostRepository;
import com.cotato.kampus.domain.post.domain.Post;

import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;

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

private final ApiUserResolver apiUserResolver;
private final PostRepository postRepository;

@Transactional
public Long append(Long boardId, String title, String content, String postCategory){
Long userId = apiUserResolver.getUserId();

Post post = Post.builder()
.userId(userId)
.boardId(boardId)
.title(title)
.content(content)
.likes(0L)
.scraps(0L)
.anonymity(Anonymity.ANONYMOUS)
.postStatus(PostStatus.PUBLISHED)
.postCategory(postCategory)
.build();

return postRepository.save(post).getId();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.cotato.kampus.domain.post.application;

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

import com.cotato.kampus.domain.post.dao.PostRepository;
import com.cotato.kampus.domain.post.domain.Post;
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 PostFinder {
private final PostRepository postRepository;

public Post getById(Long postId){
return postRepository.findById(postId)
.orElseThrow(() -> new AppException(ErrorCode.POST_NOT_FOUND));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
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
@Transactional(readOnly = true)
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
public class PostImageAppender {

private final PostPhotoRepository postPhotoRepository;

@Transactional
public void appendAll(Long postId, List<String> imageUrls){
List<PostPhoto> postPhotos = imageUrls.stream()
.map(imageUrl -> PostPhoto.builder()
.postId(postId)
.photoUrl(imageUrl)
.build())
.toList();

postPhotoRepository.saveAll(postPhotos);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.cotato.kampus.domain.post.application;

import java.util.List;

import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import com.cotato.kampus.domain.product.application.PostDeleter;
import com.cotato.kampus.global.error.exception.ImageException;
import com.cotato.kampus.global.util.s3.S3Uploader;

import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor(access = lombok.AccessLevel.PROTECTED)
public class PostService {

private final S3Uploader s3Uploader;
private final PostAppender postAppender;
private final PostDeleter postDeleter;
private final PostImageAppender postImageAppender;
private static final String PRODUCT_IMAGE_FOLDER = "post";

@Transactional
public Long createPost(
Long boardId,
String title,
String content,
String postCategory,
List<MultipartFile> images
) throws ImageException {
// s3에 이미지 업로드
List<String> imageUrls = (images == null || images.isEmpty()) ?
List.of() : s3Uploader.uploadFiles(images, PRODUCT_IMAGE_FOLDER);

// 게시글 추가
Long postId = postAppender.append(boardId, title, content, postCategory);

// 게시글 이미지 추가
postImageAppender.appendAll(postId, imageUrls);

return postId;
}

@Transactional
public Long deletePost(Long postId){
postDeleter.delete(postId);

return postId;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.cotato.kampus.domain.post.dao;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

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

@Repository
public interface PostPhotoRepository extends JpaRepository<PostPhoto, Long> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.cotato.kampus.domain.post.dao;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import com.cotato.kampus.domain.post.domain.Post;

@Repository
public interface PostRepository extends JpaRepository<Post, Long> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.cotato.kampus.domain.post.dto.request;

public record PostCreateRequest(
Long boardId,
String title,
String content,
String postCategory
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.cotato.kampus.domain.post.dto.response;

public record PostCreateResponse(Long id) {
public static PostCreateResponse of(Long id){
return new PostCreateResponse(id);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.cotato.kampus.domain.post.dto.response;

public record PostDeleteResponse(Long id) {
public static PostDeleteResponse of(Long id){
return new PostDeleteResponse(id);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.cotato.kampus.domain.product.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.post.application.PostFinder;
import com.cotato.kampus.domain.post.dao.PostRepository;
import com.cotato.kampus.domain.post.domain.Post;
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 PostDeleter {
private final ApiUserResolver apiUserResolver;
private final PostRepository postRepository;
private final PostFinder postFinder;

public void delete(Long postId){
User user = apiUserResolver.getUser();

Post post = postFinder.getById(postId);

if(post.getUserId() != user.getId()){
throw new AppException(ErrorCode.POST_NOT_AUTHOR);
}
postRepository.delete(post);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public enum ErrorCode {

//Post
POST_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 게시글을 찾을 수 없습니다.", "POST-001"),
POST_NOT_AUTHOR(HttpStatus.FORBIDDEN, "게시글 작성자가 아니므로 삭제할 수 없습니다.", "POST-002"),

//File
FILE_EXTENSION_FAULT(HttpStatus.BAD_REQUEST, "F-001", "해당 파일 확장자 명이 존재하지 않습니다."),
Expand Down

0 comments on commit 21e16ff

Please sign in to comment.