Skip to content

Commit

Permalink
Merge pull request #4 from STUDIO-EYE/feat/EPIC-80-post
Browse files Browse the repository at this point in the history
[Feat] 게시글 생성,수정,삭제 시 파일 저장,삭제 구현
  • Loading branch information
ibaesuyeon authored Apr 13, 2024
2 parents b4b0079 + eab601b commit 2087a82
Show file tree
Hide file tree
Showing 10 changed files with 223 additions and 21 deletions.
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ dependencies {

//wire mock
testImplementation("org.springframework.cloud:spring-cloud-contract-wiremock")

//aws s3
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
}

sonarqube {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.util.List;

@Tag(name = "[기획 /제작/ 편집] 게시글 작성, 수정, 삭제, 상세 조회 API")
@RestController
Expand All @@ -21,24 +26,31 @@ public class PostController {

private final PostServiceImpl postServiceImpl;

// @ExceptionHandler(MaxUploadSizeExceededException.class)
@Operation(summary = "기획/제작/편집 게시글 작성 API")
@PostMapping
public CommonResult createPost(/* @AuthenticationPrincipal User user, */ @Valid @RequestBody CreatePostRequestDto createPostDto){
return postServiceImpl.createPost(/* user, */ createPostDto.toServiceRequest());
public CommonResult createPost(/* @AuthenticationPrincipal User user,*/
@Valid @RequestPart CreatePostRequestDto createPostDto,
@RequestPart(value = "files", required = false) List<MultipartFile> files) throws IOException {
return postServiceImpl.createPost(/* user, */ createPostDto.toServiceRequest(), files);
}

@Operation(summary = "기획/제작/편집 게시글 상세 조회 API")
@GetMapping
public CommonResult retrieveDetailPost(/* @AuthenticationPrincipal User user */ @Valid RetrieveDetailPostRequestDto retrieveDetailPostRequestDto ){
public CommonResult retrieveDetailPost(/* @AuthenticationPrincipal User user */
@Valid RetrieveDetailPostRequestDto retrieveDetailPostRequestDto ){
System.out.println("sdfslakfjklafsjlkjflad" + retrieveDetailPostRequestDto);

return postServiceImpl.retrieveDetailPost(/* user, */ retrieveDetailPostRequestDto.toServiceRequest());
}

// @ExceptionHandler(MaxUploadSizeExceededException.class)
@Operation(summary = "기획/제작/편집 게시글 수정 API")
@PutMapping
public CommonResult updatePost(/* @AuthenticationPrincipal User user */ @Valid @RequestBody UpdatePostRequestDto updatePostRequestDto){
return postServiceImpl.updatePost(/* user, */ updatePostRequestDto.toServiceRequest());
public CommonResult updatePost(/* @AuthenticationPrincipal User user */
@Valid @RequestPart UpdatePostRequestDto updatePostRequestDto,
@RequestPart(value = "files", required = false) List<MultipartFile> files) throws IOException {
return postServiceImpl.updatePost(/* user, */ updatePostRequestDto.toServiceRequest(), files);
}

@Operation(summary = "기획/제작/편집 게시글 삭제 API")
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/com/mju/management/domain/post/domain/Post.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import com.mju.management.domain.comment.domain.Comment;
Expand Down Expand Up @@ -60,6 +61,9 @@ public class Post {
@OneToMany(mappedBy = "post", cascade = CascadeType.REMOVE)
private List<CommentEntity> commentList = new ArrayList<>();

// @OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true)
@OneToMany(mappedBy = "post", cascade = CascadeType.REMOVE)
private List<PostFile> postFiles;

@Builder
public Post(String title, String content, Category category, Long writerId) {
Expand Down
29 changes: 29 additions & 0 deletions src/main/java/com/mju/management/domain/post/domain/PostFile.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.mju.management.domain.post.domain;

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

@Entity
@Builder
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class PostFile {

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

private String fileName;

private String filePath;

private String s3key;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "post_id")
private Post post;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.mju.management.domain.post.infrastructure;

import com.mju.management.domain.post.domain.PostFile;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface PostFileRepository extends JpaRepository<PostFile, Long> {

List<PostFile> findByPostId(Long postId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,6 @@

import com.mju.management.domain.post.infrastructure.Category;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;

public record UpdatePostRequestServiceDto(
Long projectId,
Long postId,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,35 @@
package com.mju.management.domain.post.service;

import static com.mju.management.global.model.Exception.ExceptionList.*;

import java.util.Optional;

import com.mju.management.domain.comment.service.port.CommentRepository;
import com.mju.management.domain.post.controller.response.PostDetailResponse;
import com.mju.management.domain.post.domain.Post;
import com.mju.management.domain.post.domain.PostFile;
import com.mju.management.domain.post.infrastructure.PostFileRepository;
import com.mju.management.domain.post.infrastructure.PostRepository;
import com.mju.management.domain.post.model.dto.request.CreatePostRequestServiceDto;
import com.mju.management.domain.post.model.dto.request.DeletePostRequestServiceDto;
import com.mju.management.domain.post.model.dto.request.RetrieveDetailPostRequestServiceDto;
import com.mju.management.domain.post.model.dto.request.UpdatePostRequestServiceDto;
import com.mju.management.domain.project.infrastructure.Project;
import com.mju.management.domain.project.infrastructure.ProjectRepository;
import com.mju.management.domain.user.dto.GetUserResponseDto;
import com.mju.management.domain.user.service.UserServiceImpl;
import com.mju.management.global.config.jwtInterceptor.JwtContextHolder;
import com.mju.management.global.model.Exception.ExceptionList;
import com.mju.management.global.model.Exception.UnauthorizedAccessException;
import com.mju.management.global.model.Result.CommonResult;
import com.mju.management.global.service.ResponseService;

import com.mju.management.global.service.S3Service;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import static com.mju.management.global.model.Exception.ExceptionList.*;

@Service
@Transactional
Expand All @@ -36,8 +41,10 @@ public class PostServiceImpl {
private final UserServiceImpl userService;
private final ResponseService responseService;
private final CommentRepository commentRepository;
private final PostFileRepository postFileRepository;
private final S3Service s3Service;

public CommonResult createPost(CreatePostRequestServiceDto dto) {
public CommonResult createPost(CreatePostRequestServiceDto dto, List<MultipartFile> files) throws IOException {
Optional<Project> optionalProject = projectRepository.findById(dto.projectId());
if (optionalProject.isEmpty()){
return responseService.getFailResult(INVALID_PROJECT_ID.getCode(), INVALID_PROJECT_ID.getMessage());
Expand All @@ -51,6 +58,21 @@ public CommonResult createPost(CreatePostRequestServiceDto dto) {
Post post = dto.toEntity(userId);
project.createPost(post);
postRepository.save(post);
// 파일 업로드
List<PostFile> postFiles = new ArrayList<>();

for (MultipartFile file : files) {
String s3key = s3Service.uploadFile(file);

postFiles.add(PostFile.builder()
.fileName(file.getOriginalFilename())
.filePath(s3Service.getUrl(s3key))
.s3key(s3key)
.post(post)
.build());
}
postFileRepository.saveAll(postFiles);

return responseService.getSuccessfulResultWithMessage("기획/제작/편집 게시글 작성에 성공하였습니다.");
}

Expand All @@ -74,7 +96,7 @@ public CommonResult retrieveDetailPost(RetrieveDetailPostRequestServiceDto dto)
return responseService.getSingleResult(PostDetailResponse.from(post, userService.getUsername(post.getWriterId())));
}

public CommonResult updatePost(UpdatePostRequestServiceDto dto) {
public CommonResult updatePost(UpdatePostRequestServiceDto dto, List<MultipartFile> newFiles ) throws IOException{
Optional<Project> optionalProject = projectRepository.findById(dto.projectId());
if (optionalProject.isEmpty()){
return responseService.getFailResult(INVALID_PROJECT_ID.getCode(), INVALID_PROJECT_ID.getMessage());
Expand All @@ -96,6 +118,24 @@ public CommonResult updatePost(UpdatePostRequestServiceDto dto) {
}

post.update(dto);
// 파일 삭제
List<PostFile> oldFiles = postFileRepository.findByPostId(post.getId());
for(PostFile file : oldFiles) {
s3Service.deleteFile(file.getS3key()); //s3 삭제
postFileRepository.deleteById(file.getId()); //엔티티 삭제
}
// 파일 다시 새로 업로드
List<PostFile> postFiles = new ArrayList<>();
for (MultipartFile file : newFiles) {
String s3key = s3Service.uploadFile(file);
postFiles.add(PostFile.builder()
.fileName(file.getOriginalFilename())
.filePath(s3Service.getUrl(s3key))
.s3key(s3key)
.post(post)
.build());
}
postFileRepository.saveAll(postFiles);
return responseService.getSuccessfulResultWithMessage("기획/제작/편집 게시글 수정에 성공하였습니다.");
}

Expand Down Expand Up @@ -123,7 +163,12 @@ public CommonResult deletePost(DeletePostRequestServiceDto dto) {
// 댓글들 삭제
commentRepository.deleteAll(post);


// 파일 삭제
List<PostFile> oldFiles = postFileRepository.findByPostId(post.getId());
for(PostFile file : oldFiles) {
s3Service.deleteFile(file.getS3key()); //s3 삭제
postFileRepository.deleteById(file.getId()); //엔티티 삭제
}
postRepository.delete(post);
return responseService.getSuccessfulResultWithMessage("기획/제작/편집 게시글 삭제에 성공하였습니다.");
}
Expand All @@ -134,6 +179,4 @@ private void checkMemberAuthorization(Project project, Long userId){
throw new UnauthorizedAccessException(ExceptionList.UNAUTHORIZED_ACCESS);
}



}
36 changes: 36 additions & 0 deletions src/main/java/com/mju/management/global/config/S3Config.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.mju.management.global.config;

import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class S3Config {

@Value("${cloud.aws.credentials.access-key}")
private String accessKey;

@Value("${cloud.aws.credentials.secret-key}")
private String secretKey;

@Value("${cloud.aws.region.static}")
private String region;

@Bean
public AmazonS3 amazonS3Client() {
AWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey);

return AmazonS3ClientBuilder
.standard()
.withCredentials(new AWSStaticCredentialsProvider(credentials))
.withRegion(region)
.build();
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public enum ExceptionList {

UNKNOWN(-9999, "알 수 없는 오류가 발생하였습니다."),

INVALID_PARAMETER(-5000, "인자가 잘못 전달되었거나 없습니다."),
EMPTY_USER(-5051, "유저 정보를 입력해 주세요."),
NOT_CORRECT_USER(-5052, "수강생이 아닙니다. 수강생으로 로그인 다시 부탁드립니다."),
NOT_ACCESS_USER(-5053, "접근할 수 없는 유저 입니다."),
Expand Down
66 changes: 66 additions & 0 deletions src/main/java/com/mju/management/global/service/S3Service.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.mju.management.global.service;

import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.CannedAccessControlList;
import com.amazonaws.services.s3.model.DeleteObjectRequest;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PutObjectRequest;
import com.mju.management.global.model.Exception.ExceptionList;
import com.mju.management.global.model.Exception.NonExistentException;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;
import java.util.UUID;

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class S3Service {
@Value("${cloud.aws.s3.bucket}")
private String BUCKET;

private final AmazonS3 amazonS3;

@Transactional
public String uploadFile(MultipartFile file) throws IOException {
if(file == null){
throw new NonExistentException(ExceptionList.INVALID_PARAMETER);
}
String fileName = file.getOriginalFilename().substring(0, file.getOriginalFilename().lastIndexOf("."))
+ "-" + convertToRandomName(file.getOriginalFilename());

ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setContentLength(file.getSize());
objectMetadata.setContentType(file.getContentType());
InputStream inputStream = file.getInputStream();

String filePath = BUCKET;
amazonS3.putObject(new PutObjectRequest(filePath, fileName, inputStream, objectMetadata)
.withCannedAcl(CannedAccessControlList.PublicRead));
return fileName;
}
@Transactional
public void deleteFile(String s3key) {
if(s3key == null){
throw new NonExistentException(ExceptionList.INVALID_PARAMETER);
}
amazonS3.deleteObject(new DeleteObjectRequest(BUCKET, s3key));
}

public String convertToRandomName(String originalFileName) {
String fileExtension = originalFileName.substring(originalFileName.lastIndexOf("."));
return UUID.randomUUID().toString().concat(fileExtension);
}

public String getUrl(String s3key) {
if(s3key == null){
throw new NonExistentException(ExceptionList.INVALID_PARAMETER);
}
return amazonS3.getUrl(BUCKET, s3key).toString();
}
}

0 comments on commit 2087a82

Please sign in to comment.