diff --git a/server/src/main/java/com/green/greenEarthForUs/Exception/ImageDeletionException.java b/server/src/main/java/com/green/greenEarthForUs/Exception/ImageDeletionException.java new file mode 100644 index 00000000..1feb618e --- /dev/null +++ b/server/src/main/java/com/green/greenEarthForUs/Exception/ImageDeletionException.java @@ -0,0 +1,20 @@ +package com.green.greenEarthForUs.Exception; + +public class ImageDeletionException extends Throwable{ + public ImageDeletionException() { + super(); + } + + public ImageDeletionException(String message) { + super(message); + } + + public ImageDeletionException(String message, Throwable cause) { + super(message, cause); + } + + public ImageDeletionException(Throwable cause) { + super(cause); + } + +} diff --git a/server/src/main/java/com/green/greenEarthForUs/Exception/ImageUploadException.java b/server/src/main/java/com/green/greenEarthForUs/Exception/ImageUploadException.java new file mode 100644 index 00000000..8e1312a3 --- /dev/null +++ b/server/src/main/java/com/green/greenEarthForUs/Exception/ImageUploadException.java @@ -0,0 +1,12 @@ +package com.green.greenEarthForUs.Exception; + +public class ImageUploadException extends RuntimeException { + + public ImageUploadException(String message) { + super(message); + } + + public ImageUploadException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/server/src/main/java/com/green/greenEarthForUs/Image/Service/AmazonS3Config.java b/server/src/main/java/com/green/greenEarthForUs/Image/Service/AmazonS3Config.java index d4e6720f..25fc3887 100644 --- a/server/src/main/java/com/green/greenEarthForUs/Image/Service/AmazonS3Config.java +++ b/server/src/main/java/com/green/greenEarthForUs/Image/Service/AmazonS3Config.java @@ -5,6 +5,8 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; + + @Configuration public class AmazonS3Config { diff --git a/server/src/main/java/com/green/greenEarthForUs/Image/Service/ImageService.java b/server/src/main/java/com/green/greenEarthForUs/Image/Service/ImageService.java index 888d293c..30d2c1a2 100644 --- a/server/src/main/java/com/green/greenEarthForUs/Image/Service/ImageService.java +++ b/server/src/main/java/com/green/greenEarthForUs/Image/Service/ImageService.java @@ -8,7 +8,7 @@ import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; -import java.net.MalformedURLException; + import java.net.URL; import java.io.IOException; import java.io.InputStream; diff --git a/server/src/main/java/com/green/greenEarthForUs/post/Controller/PostController.java b/server/src/main/java/com/green/greenEarthForUs/post/Controller/PostController.java index 74586c09..3418f760 100644 --- a/server/src/main/java/com/green/greenEarthForUs/post/Controller/PostController.java +++ b/server/src/main/java/com/green/greenEarthForUs/post/Controller/PostController.java @@ -1,11 +1,14 @@ package com.green.greenEarthForUs.post.Controller; +import com.green.greenEarthForUs.Exception.ImageDeletionException; +import com.green.greenEarthForUs.Image.Service.ImageService; import com.green.greenEarthForUs.post.DTO.PostPatchDto; import com.green.greenEarthForUs.post.DTO.PostPostDto; import com.green.greenEarthForUs.post.DTO.PostResponseDto; import com.green.greenEarthForUs.post.Entity.Post; import com.green.greenEarthForUs.post.Mapper.PostMapper; import com.green.greenEarthForUs.post.Service.PostService; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -24,30 +27,25 @@ public class PostController { final PostService postService; private final PostMapper mapper; - public PostController(PostService postService, PostMapper mapper) { + private final ImageService imageService; + @Autowired + public PostController(PostService postService, PostMapper mapper, ImageService imageService) { this.postService = postService; this.mapper = mapper; + this.imageService = imageService; } // 게시글 생성 @PostMapping("/{user-id}") public ResponseEntity createPost(@PathVariable(value = "user-id") Long userId, - @ModelAttribute PostPostDto postPostDto, - MultipartHttpServletRequest request) throws IOException { - - // 파일 - MultipartFile multipartFile = request.getFile("image"); - if (multipartFile != null && !multipartFile.isEmpty()) { - // 파일 이름 가져오기 - String originalFileName = multipartFile.getOriginalFilename(); - // 파일 내용(byte 배열) 가져오기 - byte[] fileContent = multipartFile.getBytes(); - - postPostDto.setBodyImageFileName(originalFileName); - postPostDto.setBodyImage(fileContent); - } + @RequestParam("image") MultipartFile image, + @RequestBody PostPostDto postPostDto) throws IOException { + + String imageUrl = imageService.uploadImage(image); // 이미지 업로드하고 + + Post createdPost = postService.createPost(userId, postPostDto, image); + createdPost.setImageUrl(imageUrl); - Post createdPost = postService.createPost(userId, postPostDto); PostResponseDto responseDto = mapper.postToPostResponseDto(createdPost); return ResponseEntity.status(HttpStatus.CREATED).body(responseDto); @@ -99,7 +97,7 @@ public ResponseEntity updatePost(@PathVariable(value = "user-id // 게시글 삭제 @DeleteMapping("/{user-id}/{post-id}") public ResponseEntity deletePost(@PathVariable(value = "user-id") Long userId, - @PathVariable(value = "post-id") Long postId) { + @PathVariable(value = "post-id") Long postId) throws ImageDeletionException { postService.deletePost(postId, userId); return ResponseEntity.noContent().build(); diff --git a/server/src/main/java/com/green/greenEarthForUs/post/DTO/PostDto.java b/server/src/main/java/com/green/greenEarthForUs/post/DTO/PostDto.java index afe19c60..0988c4f6 100644 --- a/server/src/main/java/com/green/greenEarthForUs/post/DTO/PostDto.java +++ b/server/src/main/java/com/green/greenEarthForUs/post/DTO/PostDto.java @@ -14,9 +14,7 @@ public class PostDto { // 엔티티 조회, 내용 수정 private String body; - private String bodyImageFileName; - - private byte[] bodyImage; + private String imageUrl; private Boolean open; diff --git a/server/src/main/java/com/green/greenEarthForUs/post/DTO/PostPatchDto.java b/server/src/main/java/com/green/greenEarthForUs/post/DTO/PostPatchDto.java index e3c8c1cc..24bff5ad 100644 --- a/server/src/main/java/com/green/greenEarthForUs/post/DTO/PostPatchDto.java +++ b/server/src/main/java/com/green/greenEarthForUs/post/DTO/PostPatchDto.java @@ -17,9 +17,7 @@ public class PostPatchDto { private String title; - private String bodyImageFileName; - - private byte[] bodyImage; + private String imageUrl; private String body; diff --git a/server/src/main/java/com/green/greenEarthForUs/post/DTO/PostPostDto.java b/server/src/main/java/com/green/greenEarthForUs/post/DTO/PostPostDto.java index d864dd63..a9e5c23a 100644 --- a/server/src/main/java/com/green/greenEarthForUs/post/DTO/PostPostDto.java +++ b/server/src/main/java/com/green/greenEarthForUs/post/DTO/PostPostDto.java @@ -16,9 +16,7 @@ public class PostPostDto { // 생성 private Boolean open; - private String bodyImageFileName; - - private byte[] bodyImage; + private String imageUrl; public Boolean isOpen() { return open != null && open; // open 필드가 null인 경우나 false인 경우에 구현 이렇게 해주어야함 diff --git a/server/src/main/java/com/green/greenEarthForUs/post/DTO/PostResponseDto.java b/server/src/main/java/com/green/greenEarthForUs/post/DTO/PostResponseDto.java index ed7d9e4f..08e055d7 100644 --- a/server/src/main/java/com/green/greenEarthForUs/post/DTO/PostResponseDto.java +++ b/server/src/main/java/com/green/greenEarthForUs/post/DTO/PostResponseDto.java @@ -20,9 +20,7 @@ public class PostResponseDto { private String open; - private String bodyImageFileName; - - private byte[] bodyImage; + private String imageUrl; private LocalDateTime createdAt; diff --git a/server/src/main/java/com/green/greenEarthForUs/post/Entity/Post.java b/server/src/main/java/com/green/greenEarthForUs/post/Entity/Post.java index 8ae9f823..873d3779 100644 --- a/server/src/main/java/com/green/greenEarthForUs/post/Entity/Post.java +++ b/server/src/main/java/com/green/greenEarthForUs/post/Entity/Post.java @@ -8,7 +8,8 @@ import javax.persistence.*; import java.time.LocalDateTime; -@Getter @Setter +@Getter +@Setter @NoArgsConstructor @AllArgsConstructor @Entity @@ -25,7 +26,7 @@ public class Post { private Boolean open; @Column - private String writer; + private String imageUrl; @Column private String type; // type 게시글의 유형 @@ -33,14 +34,8 @@ public class Post { @Column private String title; - @Column - private String bodyImageFileName; - - @Lob - @Column - private byte[] bodyImage; - @Column(name= "created_at") + @Column(name = "created_at") private LocalDateTime createdAt; @ManyToOne diff --git a/server/src/main/java/com/green/greenEarthForUs/post/Service/PostService.java b/server/src/main/java/com/green/greenEarthForUs/post/Service/PostService.java index 8ea033ee..4558c754 100644 --- a/server/src/main/java/com/green/greenEarthForUs/post/Service/PostService.java +++ b/server/src/main/java/com/green/greenEarthForUs/post/Service/PostService.java @@ -1,6 +1,8 @@ package com.green.greenEarthForUs.post.Service; +import com.green.greenEarthForUs.Exception.ImageDeletionException; import com.green.greenEarthForUs.Exception.UnauthorizedException; +import com.green.greenEarthForUs.Image.Service.ImageService; import com.green.greenEarthForUs.post.DTO.PostPatchDto; import com.green.greenEarthForUs.post.DTO.PostPostDto; import com.green.greenEarthForUs.post.DTO.PostResponseDto; @@ -13,6 +15,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; import javax.persistence.EntityNotFoundException; import java.io.IOException; @@ -30,23 +33,30 @@ public class PostService { private final PostMapper mapper; private final UserService userService; - public PostService(PostRepository postsRepository, UserRepository userRepository, PostMapper mapper, UserService userService) { + private final ImageService imageService; + @Autowired + public PostService(PostRepository postsRepository, UserRepository userRepository, PostMapper mapper, + UserService userService, ImageService imageService) { this.postsRepository = postsRepository; this.userRepository = userRepository; this.mapper = mapper; this.userService = userService; + this.imageService = imageService; } // 게시글 생성 @Transactional - public Post createPost(Long userId, PostPostDto postPostDto) throws IOException { // 유저, 게시글 - + public Post createPost(Long userId, PostPostDto postPostDto, MultipartFile image) throws IOException { // 유저, 게시글 User user = userRepository.findById(userId) .orElseThrow(() -> new EntityNotFoundException("User not found with ID: " + userId)); // 유저 조회 + String imageUrl = imageService.uploadImage(image); + Post post = mapper.postPostDtoToPost(postPostDto); post.setUser(user); - post.setCreatedAt(LocalDateTime.now()); // 게시글 생성 + post.setCreatedAt(LocalDateTime.now()); // 게시글 생성하고 + + post.setImageUrl(imageUrl); // 이미지 url 넣고 post.setOpen(postPostDto.isOpen()); @@ -121,10 +131,20 @@ public PostResponseDto updatePost(Long userId, Long postId, PostPatchDto postPat // 게시글 삭제 @Transactional - public void deletePost(Long userId, Long postId) { + public void deletePost(Long userId, Long postId) throws ImageDeletionException { Post existingPost = postsRepository.findById(postId) .orElseThrow(() -> new EntityNotFoundException("Post not found with ID: " + postId)); + //이미지 삭제하기 + String imageUrl = existingPost.getImageUrl(); + if(imageUrl != null) { + try { + imageService.deleteImage(imageUrl); + } catch (Exception e) { + throw new ImageDeletionException("Failed to delete image: " + imageUrl, e); + } + } + // 작성자 요청자 일치하는지 User user = existingPost.getUser(); if (!user.getUserId().equals(userId)) { @@ -134,16 +154,5 @@ public void deletePost(Long userId, Long postId) { postsRepository.delete(existingPost); } - -// 페이지네이션 -// @Transactional -// public Page getLatestPosts(int page) { -// -// int pageNumber = page > 0 ? page - 1 : 0; -// -// Pageable pageable = PageRequest.of(pageNumber, 10, Sort.by("createdAt").descending()); -// -// return postsRepository.findAll(pageable); -// } } diff --git a/server/src/main/java/com/green/greenEarthForUs/user/Entity/User.java b/server/src/main/java/com/green/greenEarthForUs/user/Entity/User.java index 27b20b71..74f5c8bd 100644 --- a/server/src/main/java/com/green/greenEarthForUs/user/Entity/User.java +++ b/server/src/main/java/com/green/greenEarthForUs/user/Entity/User.java @@ -35,6 +35,7 @@ public class User { @Enumerated(EnumType.STRING) // Post 횟수에 따라서 땅(0개) → 새싹(1개) → 조금 더 자란 새싹(5개) → 꽃봉오리(10개) → 꽃(20개) private UserGrade grade; // 씨앗 -> 새싹 -> 점점 자라는 느낌 누적 게시글 기준 .. + private String imageUrl; // 이미지 저장할 필드 추가하기 @Column @@ -49,12 +50,6 @@ public class User { @Column private LocalDateTime createAt; - @Column - private String profileImageFileName; - - @Column - private byte[] profileImage; - @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) // 사용자를 삭제할 때 관련 게시물, 이미지도 자동적으로 삭제됨 private List posts; diff --git a/server/src/main/java/com/green/greenEarthForUs/user/controller/UserController.java b/server/src/main/java/com/green/greenEarthForUs/user/controller/UserController.java index 1c878d5f..5cd05a7b 100644 --- a/server/src/main/java/com/green/greenEarthForUs/user/controller/UserController.java +++ b/server/src/main/java/com/green/greenEarthForUs/user/controller/UserController.java @@ -1,12 +1,14 @@ package com.green.greenEarthForUs.user.controller; +import com.green.greenEarthForUs.Image.Service.ImageService; import com.green.greenEarthForUs.user.Entity.User; import com.green.greenEarthForUs.user.dto.UserPatchDto; import com.green.greenEarthForUs.user.dto.UserPostDto; import com.green.greenEarthForUs.user.dto.UserResponseDto; import com.green.greenEarthForUs.user.mapper.UserMapper; import com.green.greenEarthForUs.user.service.UserService; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -22,27 +24,24 @@ public class UserController { // 이미지 데이터를 바이너리 형태로 private UserService userService; private UserMapper mapper; - - public UserController(UserService userService, UserMapper mapper) { + private final ImageService imageService; + @Autowired + public UserController(UserService userService, UserMapper mapper, ImageService imageService) { this.userService = userService; this.mapper = mapper; + this.imageService = imageService; } // 사용자 등록 - @PostMapping("/") - public ResponseEntity createUser(@ModelAttribute UserPostDto userPostDto, - @RequestParam("image") MultipartFile imageFile) throws IOException { - // 이미지 파일 업로드 - if (imageFile != null && !imageFile.isEmpty()) { - String originalFileName = imageFile.getOriginalFilename(); - byte[] fileContent = imageFile.getBytes(); - - userPostDto.setImageFileName(originalFileName); - userPostDto.setImage(fileContent); - } + @PostMapping() + public ResponseEntity createUser(@RequestParam("image") MultipartFile image, + @RequestBody UserPostDto userPostDto) throws IOException { + + String imageUrl = imageService.uploadImage(image); + + User createUser = userService.createUser(userPostDto, imageUrl); - User createdUser = userService.createUser(userPostDto, imageFile); - UserResponseDto responseDto = mapper.userToUserResponseDto(createdUser); + UserResponseDto responseDto = mapper.userToUserResponseDto(createUser); return ResponseEntity.status(HttpStatus.CREATED).body(responseDto); } diff --git a/server/src/main/java/com/green/greenEarthForUs/user/dto/UserPatchDto.java b/server/src/main/java/com/green/greenEarthForUs/user/dto/UserPatchDto.java index 50b2f7e9..c2f6ebae 100644 --- a/server/src/main/java/com/green/greenEarthForUs/user/dto/UserPatchDto.java +++ b/server/src/main/java/com/green/greenEarthForUs/user/dto/UserPatchDto.java @@ -14,7 +14,5 @@ public class UserPatchDto { private String passwordAnswer; - private String ImageFileName; - - private byte[] fileImage; + private String imageUrl; } \ No newline at end of file diff --git a/server/src/main/java/com/green/greenEarthForUs/user/dto/UserPostDto.java b/server/src/main/java/com/green/greenEarthForUs/user/dto/UserPostDto.java index 1e7dc290..95a7ddfc 100644 --- a/server/src/main/java/com/green/greenEarthForUs/user/dto/UserPostDto.java +++ b/server/src/main/java/com/green/greenEarthForUs/user/dto/UserPostDto.java @@ -3,7 +3,8 @@ import lombok.Getter; import lombok.Setter; -@Getter @Setter +@Getter +@Setter public class UserPostDto { // 가입 private Long userId; @@ -16,8 +17,6 @@ public class UserPostDto { // 가입 private String passwordAnswer; - private String ImageFileName; - - private byte[] Image; + private String imageUrl; } diff --git a/server/src/main/java/com/green/greenEarthForUs/user/dto/UserResponseDto.java b/server/src/main/java/com/green/greenEarthForUs/user/dto/UserResponseDto.java index a50a3e30..0bb643a7 100644 --- a/server/src/main/java/com/green/greenEarthForUs/user/dto/UserResponseDto.java +++ b/server/src/main/java/com/green/greenEarthForUs/user/dto/UserResponseDto.java @@ -19,8 +19,6 @@ public class UserResponseDto { private LocalDateTime createdAt; - private String profileImageFileName; - - private byte[] profileImage; + private String imageUrl; } diff --git a/server/src/main/java/com/green/greenEarthForUs/user/service/UserService.java b/server/src/main/java/com/green/greenEarthForUs/user/service/UserService.java index f935d666..eec502bb 100644 --- a/server/src/main/java/com/green/greenEarthForUs/user/service/UserService.java +++ b/server/src/main/java/com/green/greenEarthForUs/user/service/UserService.java @@ -1,5 +1,7 @@ package com.green.greenEarthForUs.user.service; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.model.ObjectMetadata; import com.green.greenEarthForUs.Exception.BusinessLogicException; import com.green.greenEarthForUs.Exception.ExceptionCode; import com.green.greenEarthForUs.user.Entity.User; @@ -7,36 +9,35 @@ import com.green.greenEarthForUs.user.dto.UserPatchDto; import com.green.greenEarthForUs.user.dto.UserPostDto; import com.green.greenEarthForUs.user.mapper.UserMapper; +import org.hibernate.procedure.spi.ParameterRegistrationImplementor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.util.Optional; +import java.util.UUID; @Service public class UserService { private final UserRepository userRepository; private final UserMapper mapper; + public UserService(UserRepository userRepository, UserMapper mapper) { this.userRepository = userRepository; this.mapper = mapper; } + // 사용자 등록 - public User createUser(UserPostDto userPostDto, MultipartFile imageFile) throws IOException { + public User createUser(UserPostDto userPostDto, String imageUrl) { - // 이미지 파일 업로드 처리 - if (imageFile != null && !imageFile.isEmpty()) { - String originalFileName = imageFile.getOriginalFilename(); - byte[] fileContent = imageFile.getBytes(); + User user = mapper.userPostDtoToUser(userPostDto); - userPostDto.setImageFileName(originalFileName); - userPostDto.setImage(fileContent); - } + // 이미지 업로드 + user.setImageUrl(imageUrl); - User user = mapper.userPostDtoToUser(userPostDto); return userRepository.save(user); } @@ -63,7 +64,7 @@ public User updateUser(Long userId, UserPatchDto userPatchDto) { existing.setPassword(userPatchDto.getPassword()); existing.setPasswordQuestion(userPatchDto.getPasswordQuestion()); existing.setPasswordAnswer(userPatchDto.getPasswordAnswer()); - existing.setProfileImage(userPatchDto.getFileImage()); + existing.setImageUrl(userPatchDto.getImageUrl()); return userRepository.save(existing); } @@ -106,6 +107,7 @@ public void updateGradePostCount(User user) { } } + private User.UserGrade calculateUserGrade(int postCount) { if (postCount == 0) { return User.UserGrade.LAND; @@ -118,7 +120,6 @@ private User.UserGrade calculateUserGrade(int postCount) { } else { return User.UserGrade.FLOWER; } + } } - -