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

이미지 관리 테이블 작성한다 #67

Open
wants to merge 19 commits into
base: week7
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
67 changes: 67 additions & 0 deletions src/main/java/poomasi/domain/image/controller/ImageController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package poomasi.domain.image.controller;

import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import poomasi.domain.image.dto.ImageRequest;
import poomasi.domain.image.entity.Image;
import poomasi.domain.image.entity.ImageType;
import poomasi.domain.image.service.ImageService;

import java.util.List;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/image")
public class ImageController {
private final ImageService imageService;

// 이미지 정보 저장
@PostMapping
public ResponseEntity<?> saveImageInfo(@RequestBody ImageRequest imageRequest) {
Image savedImage = imageService.saveImage(imageRequest);
return ResponseEntity.ok(savedImage);
}

// 여러 이미지 정보 저장
@PostMapping("/multiple")
public ResponseEntity<List<Image>> saveMultipleImages(@RequestBody List<ImageRequest> imageRequests) {
List<Image> savedImages = imageService.saveMultipleImages(imageRequests);
return ResponseEntity.ok(savedImages);
}

// 특정 이미지 삭제
@DeleteMapping("/delete/{id}")
public ResponseEntity<Void> deleteImage(@PathVariable Long id) {
imageService.deleteImage(id);
return ResponseEntity.noContent().build();
}

// 특정 이미지 조회
@GetMapping("/{id}")
public ResponseEntity<Image> getImage(@PathVariable Long id) {
return ResponseEntity.ok(imageService.getImageById(id));
}

// 모든 이미지 조회 (특정 referenceId에 따라)
@GetMapping("/reference/{type}/{referenceId}")
public ResponseEntity<List<Image>> getImagesByTypeAndReference(@PathVariable ImageType type, @PathVariable Long referenceId) {
List<Image> images = imageService.getImagesByTypeAndReferenceId(type, referenceId);
return ResponseEntity.ok(images);
}

// 이미지 정보 수정
@PutMapping("/{id}")
public ResponseEntity<?> updateImageInfo(@PathVariable Long id, @RequestBody ImageRequest imageRequest) {
Image updatedImage = imageService.updateImage(id, imageRequest);
return ResponseEntity.ok(updatedImage);
}

@PutMapping("/recover/{id}")
public ResponseEntity<Void> recoverImage(@PathVariable Long id) {
imageService.recoverImage(id);
return ResponseEntity.noContent().build();
}


}
16 changes: 16 additions & 0 deletions src/main/java/poomasi/domain/image/dto/ImageRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package poomasi.domain.image.dto;

import poomasi.domain.image.entity.Image;
import poomasi.domain.image.entity.ImageType;

public record ImageRequest(String objectKey, String imageUrl, ImageType type, Long referenceId) {
public Image toEntity(ImageRequest imageRequest){
return new Image(
imageRequest.objectKey,
imageRequest.imageUrl,
imageRequest.type,
imageRequest.referenceId
);
}
}

67 changes: 67 additions & 0 deletions src/main/java/poomasi/domain/image/entity/Image.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package poomasi.domain.image.entity;

import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.SQLDelete;
import poomasi.domain.image.dto.ImageRequest;

import java.time.LocalDateTime;
import java.util.Date;

@Entity
@Table(name = "image", uniqueConstraints = {
@UniqueConstraint(columnNames = {"type", "reference_id", "object_key"})
})
@Getter
@Setter
@NoArgsConstructor
@SQLDelete(sql = "UPDATE image SET deleted_at = current_timestamp WHERE id = ?")
public class Image {

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

@Column(nullable = false)
private String objectKey;

@Column(nullable = false)
private String imageUrl;

@Enumerated(EnumType.STRING)
@Column(nullable = false)
private ImageType type;

@Column(name = "reference_id", nullable = false)
private Long referenceId;

@Column(name = "created_at", nullable = false)
@Temporal(TemporalType.TIMESTAMP)
private Date createdAt = new Date();

@Column(name = "deleted_at")
private LocalDateTime deletedAt;

public Image(String objectKey, String imageUrl, ImageType type, Long referenceId) {
this.objectKey = objectKey;
this.imageUrl = imageUrl;
this.type = type;
this.referenceId = referenceId;
}

public void update(ImageRequest request) {
this.objectKey = request.objectKey();
this.imageUrl = request.imageUrl();
this.type = request.type();
this.referenceId = request.referenceId();
}

public ImageRequest toRequest(Image image){
return new ImageRequest(
image.objectKey,
image.imageUrl,
image.type,
image.referenceId
);
}
}
5 changes: 5 additions & 0 deletions src/main/java/poomasi/domain/image/entity/ImageType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package poomasi.domain.image.entity;

public enum ImageType {
FARM, FARM_REVIEW, PRODUCT, PRODUCT_REVIEW
}
15 changes: 15 additions & 0 deletions src/main/java/poomasi/domain/image/repository/ImageRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package poomasi.domain.image.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import poomasi.domain.image.entity.Image;
import poomasi.domain.image.entity.ImageType;

import java.util.List;
import java.util.Optional;

public interface ImageRepository extends JpaRepository<Image, Long> {
long countByTypeAndReferenceIdAndDeletedAtIsNull(ImageType type, Long referenceId);
List<Image> findByTypeAndReferenceIdAndDeletedAtIsNull(ImageType type, Long referenceId);
Optional<Image> findByIdAndDeletedAtIsNull(Long id);
Optional<Image> findByObjectKeyAndTypeAndReferenceId(String s, ImageType type, Long aLong);
}
109 changes: 109 additions & 0 deletions src/main/java/poomasi/domain/image/service/ImageService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package poomasi.domain.image.service;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import poomasi.domain.image.dto.ImageRequest;
import poomasi.domain.image.entity.Image;
import poomasi.domain.image.entity.ImageType;
import poomasi.domain.image.repository.ImageRepository;
import poomasi.global.error.BusinessException;

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

import static poomasi.global.error.BusinessError.*;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class ImageService {
private final ImageRepository imageRepository;

@Transactional
public Image saveImage(ImageRequest imageRequest) {
// 기존 이미지가 있는 경우 복구 또는 예외 처리 (실제 복구 로직과는 차이가 있음)
validateImageLimit(imageRequest);

Image image = findExistingOrRecoverableImage(imageRequest)
.map(existingImage -> recoverImageOrThrow(existingImage, imageRequest))
.orElseGet(() -> imageRequest.toEntity(imageRequest));

return imageRepository.save(image);
}

private Optional<Image> findExistingOrRecoverableImage(ImageRequest imageRequest) {
return imageRepository.findByObjectKeyAndTypeAndReferenceId(
imageRequest.objectKey(), imageRequest.type(), imageRequest.referenceId());
}

private Image recoverImageOrThrow(Image existingImage, ImageRequest imageRequest) {
if (existingImage.getDeletedAt() == null) {
throw new BusinessException(IMAGE_ALREADY_EXISTS);
}
existingImage.setDeletedAt(null);
existingImage.setCreatedAt(new Date());
existingImage.update(imageRequest);
return existingImage;
}

private void validateImageLimit(ImageRequest imageRequest) {
if (imageRepository.countByTypeAndReferenceIdAndDeletedAtIsNull(imageRequest.type(), imageRequest.referenceId()) >= 5) {
throw new BusinessException(IMAGE_LIMIT_EXCEED);
}
}

// 여러 이미지 저장
@Transactional
public List<Image> saveMultipleImages(List<ImageRequest> imageRequests) {
return imageRequests.stream()
.map(this::saveImage)
.collect(Collectors.toList());
}

@Transactional
public void deleteImage(Long id) {
imageRepository.deleteById(id);
}

public Image getImageById(Long id) {
return imageRepository.findByIdAndDeletedAtIsNull(id)
.orElseThrow(() -> new BusinessException(IMAGE_NOT_FOUND));
}

public List<Image> getImagesByTypeAndReferenceId(ImageType type, Long referenceId) {
return imageRepository.findByTypeAndReferenceIdAndDeletedAtIsNull(type, referenceId);
}

// 이미지 수정
@Transactional
public Image updateImage(Long id, ImageRequest imageRequest) {
Image image = getImageById(id);

if (!image.getType().equals(imageRequest.type()) ||
!image.getReferenceId().equals(imageRequest.referenceId())) {
validateImageLimit(imageRequest);
}

image.update(imageRequest);

return imageRepository.save(image);
}

@Transactional
public void recoverImage(Long id) {
Image image = imageRepository.findById(id)
.orElseThrow(() -> new BusinessException(IMAGE_NOT_FOUND));

if (image.getDeletedAt() == null) {
throw new BusinessException(IMAGE_ALREADY_EXISTS);
}

validateImageLimit(image.toRequest(image));

image.setDeletedAt(null);
imageRepository.save(image);
}
}
4 changes: 4 additions & 0 deletions src/main/java/poomasi/domain/member/dto/MemberResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package poomasi.domain.member.dto;

public record MemberResponse (Long id, String name, String email){
}
1 change: 1 addition & 0 deletions src/main/java/poomasi/domain/member/entity/Member.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public class Member {
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<WishList> wishLists;

@Column(name="deleted_at")
private LocalDateTime deletedAt;

public Member(String email, String password, LoginType loginType, Role role) {
Expand Down
9 changes: 7 additions & 2 deletions src/main/java/poomasi/global/config/s3/S3Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import poomasi.global.config.aws.AwsProperties;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3AsyncClient;
import software.amazon.awssdk.services.s3.S3Client;
Expand All @@ -20,7 +21,11 @@ public class S3Config {

@Bean("awsCredentials")
public AwsCredentialsProvider awsCredentials() {
return DefaultCredentialsProvider.create();
AwsBasicCredentials awsBasicCredentials = AwsBasicCredentials.create(
awsProperties.getAccess(),
awsProperties.getSecret()
);
return StaticCredentialsProvider.create(awsBasicCredentials);
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,26 @@

import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import poomasi.global.config.aws.AwsProperties;
import poomasi.global.config.s3.dto.request.PresignedUrlPutRequest;

@RestController
@RequiredArgsConstructor
public class TestController {
@RequestMapping("/api/s3")
public class S3PresignedUrlController {
private final S3PresignedUrlService s3PresignedUrlService;

@GetMapping("/presigned-url-put")
public ResponseEntity<?> presignedUrlPut() {
String presignedPutUrl = s3PresignedUrlService.createPresignedPutUrl("poomasi", "test", null);
return ResponseEntity.ok(presignedPutUrl);
}
private final AwsProperties awsProperties;

@GetMapping("/presigned-url-get")
public ResponseEntity<?> presignedUrlGet(@RequestParam String keyname) {
String presignedGetUrl = s3PresignedUrlService.createPresignedGetUrl("poomasi", keyname);
String presignedGetUrl = s3PresignedUrlService.createPresignedGetUrl(awsProperties.getS3().getBucket(), keyname);
return ResponseEntity.ok(presignedGetUrl);
}

@PostMapping("/presigned-url-put")
public ResponseEntity<?> presignedUrlPut(@RequestBody PresignedUrlPutRequest request) {
String presignedPutUrl = s3PresignedUrlService.createPresignedPutUrl(awsProperties.getS3().getBucket(), request.keyPrefix(), request.metadata());
return ResponseEntity.ok(presignedPutUrl);
}
}
Loading