Skip to content

Commit

Permalink
Merge pull request #59 from taco-official/KL-119/상품사진-s-3-등록
Browse files Browse the repository at this point in the history
feat(KL-119): add s3 image
  • Loading branch information
ohhamma authored Aug 29, 2024
2 parents 2fdb4c7 + 0073c8f commit e01921e
Show file tree
Hide file tree
Showing 60 changed files with 1,070 additions and 208 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/makefile.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ jobs:

- name: Create .env file
run: |
jq -r 'to_entries|map("\(.key)=\(.value|tostring)")|.[]' <<< "$SECRETS_CONTEXT" > .env
env:
SECRETS_CONTEXT: ${{ toJson(secrets) }}
touch ./env
echo "${{ secrets.ENV }}" > ./.env
shell: bash

- name: Set up Gradle
uses: gradle/actions/setup-gradle@v3
Expand Down
6 changes: 5 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ dependencies {

// MySQL
runtimeOnly 'com.mysql:mysql-connector-j'

// S3
implementation platform('software.amazon.awssdk:bom:2.21.1')
implementation 'software.amazon.awssdk:s3'
}

checkstyle {
Expand All @@ -70,5 +74,5 @@ test {
useJUnitPlatform()
dependsOn 'checkstyleMain'
dependsOn 'checkstyleTest'
systemProperty 'spring.profiles.active', 'local,h2'
systemProperty 'spring.profiles.active', 'test'
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import taco.klkl.domain.category.service.SubcategoryTagService;

@Slf4j
@Tag(name = "7. 태그", description = "태그 관련 API")
@Tag(name = "6. 카테고리", description = "카테고리 관련 API")
@RestController
@RequestMapping("/v1/tags")
@RequiredArgsConstructor
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package taco.klkl.domain.image.controller;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import taco.klkl.domain.image.dto.request.ImageUploadRequest;
import taco.klkl.domain.image.dto.response.PresignedUrlResponse;
import taco.klkl.domain.image.service.ImageService;

@Slf4j
@RestController
@Tag(name = "0. 이미지", description = "이미지 관련 API")
@RequiredArgsConstructor
public class ImageController {

private final ImageService imageService;

@Operation(
summary = "유저 이미지 업로드 Presigned URL 생성",
description = "유저 이미지 업로드를 위한 Presigned URL를 생성합니다."
)
@PostMapping("/v1/users/me/upload-url")
public PresignedUrlResponse createUserImageUploadUrl(
@Valid @RequestBody final ImageUploadRequest request
) {
return imageService.createUserImageUploadUrl(request);
}

@Operation(
summary = "유저 이미지 업로드 완료 처리",
description = "유저 이미지 업로드를 완료 처리합니다."
)
@PostMapping("/v1/users/me/upload-complete")
public ResponseEntity<Void> uploadCompleteUserImage() {
imageService.uploadCompleteUserImage();
return ResponseEntity.ok().build();
}

@Operation(
summary = "상품 이미지 업로드 Presigned URL 생성",
description = "상품 이미지 업로드를 위한 Presigned URL를 생성합니다."
)
@PostMapping("/v1/products/{productId}/upload-url")
public PresignedUrlResponse createProductImageUploadUrl(
@PathVariable final Long productId,
@Valid @RequestBody final ImageUploadRequest request
) {
return imageService.createProductImageUploadUrl(productId, request);
}

@Operation(
summary = "상품 이미지 업로드 완료 처리",
description = "상품 이미지 업로드를 완료 처리합니다."
)
@PostMapping("/v1/products/{productId}/upload-complete")
public ResponseEntity<Void> uploadCompleteProductImage(
@PathVariable final Long productId
) {
imageService.uploadCompleteProductImage(productId);
return ResponseEntity.ok().build();
}
}
15 changes: 15 additions & 0 deletions src/main/java/taco/klkl/domain/image/dao/ImageRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package taco.klkl.domain.image.dao;

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

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

import taco.klkl.domain.image.domain.Image;
import taco.klkl.domain.image.domain.ImageType;

public interface ImageRepository extends JpaRepository<Image, Long> {
Optional<Image> findByImageTypeAndTargetId(final ImageType imageType, final Long targetId);

List<Image> findAllByImageTypeAndTargetId(final ImageType imageType, final Long targetId);
}
26 changes: 26 additions & 0 deletions src/main/java/taco/klkl/domain/image/domain/FileExtension.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package taco.klkl.domain.image.domain;

import java.util.Arrays;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import taco.klkl.domain.image.exception.FileExtensionNotFoundException;

@Getter
@RequiredArgsConstructor
public enum FileExtension {
JPG("jpeg"),
JPEG("jpeg"),
PNG("png"),
WEBP("webp"),
;

private final String value;

public static FileExtension from(final String fileExtension) throws FileExtensionNotFoundException {
return Arrays.stream(FileExtension.values())
.filter(extension -> extension.getValue().equals(fileExtension))
.findFirst()
.orElseThrow(FileExtensionNotFoundException::new);
}
}
105 changes: 105 additions & 0 deletions src/main/java/taco/klkl/domain/image/domain/Image.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package taco.klkl.domain.image.domain;

import java.time.LocalDateTime;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Entity(name = "image")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Image {

@Id
@Column(name = "image_id",
nullable = false
)
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

@Enumerated(EnumType.STRING)
@Column(
name = "image_type",
nullable = false
)
private ImageType imageType;

@Column(
name = "target_id",
nullable = false
)
private Long targetId;

@Column(
name = "image_key",
nullable = false
)
private String imageKey;

@Enumerated(EnumType.STRING)
@Column(
name = "file_extension",
nullable = false
)
private FileExtension fileExtension;

@Enumerated(EnumType.STRING)
@Column(
name = "upload_state",
nullable = false
)
private UploadState uploadState;

@Column(
name = "created_at",
nullable = false,
updatable = false
)
private LocalDateTime createdAt;

private Image(
final ImageType imageType,
final Long targetId,
final String imageKey,
final FileExtension fileExtension
) {
this.imageType = imageType;
this.targetId = targetId;
this.imageKey = imageKey;
this.fileExtension = fileExtension;
this.uploadState = UploadState.PENDING;
this.createdAt = LocalDateTime.now();
}

public static Image of(
final ImageType imageType,
final Long targetId,
final String imageUuid,
final FileExtension fileExtension
) {
return new Image(imageType, targetId, imageUuid, fileExtension);
}

public void uploadComplete() {
this.uploadState = UploadState.COMPLETE;
}

public void markAsOutdated() {
this.uploadState = UploadState.OUTDATED;
}

public String createFileName() {
return imageType.getValue() + "/"
+ targetId + "/"
+ imageKey + "."
+ fileExtension.getValue();
}
}
24 changes: 24 additions & 0 deletions src/main/java/taco/klkl/domain/image/domain/ImageType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package taco.klkl.domain.image.domain;

import java.util.Arrays;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import taco.klkl.domain.image.exception.ImageTypeNotFoundException;

@Getter
@RequiredArgsConstructor
public enum ImageType {
USER_IMAGE("user_image"),
PRODUCT_IMAGE("product_image"),
;

private final String value;

public static ImageType from(final String value) throws ImageTypeNotFoundException {
return Arrays.stream(ImageType.values())
.filter(type -> type.getValue().equals(value))
.findFirst()
.orElseThrow(ImageTypeNotFoundException::new);
}
}
25 changes: 25 additions & 0 deletions src/main/java/taco/klkl/domain/image/domain/UploadState.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package taco.klkl.domain.image.domain;

import java.util.Arrays;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import taco.klkl.domain.image.exception.UploadStateNotFoundException;

@Getter
@RequiredArgsConstructor
public enum UploadState {
PENDING("대기중"),
COMPLETE("완료"),
OUTDATED("폐기예정"),
;

private final String value;

public static UploadState from(final String value) throws UploadStateNotFoundException {
return Arrays.stream(UploadState.values())
.filter(state -> state.getValue().equals(value))
.findFirst()
.orElseThrow(UploadStateNotFoundException::new);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package taco.klkl.domain.image.dto.request;

import jakarta.validation.constraints.NotBlank;
import taco.klkl.global.common.constants.ImageValidationMessages;

public record ImageUploadRequest(
@NotBlank(message = ImageValidationMessages.FILE_EXTENSION_NOT_BLANK)
String fileExtension
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package taco.klkl.domain.image.dto.response;

public record PresignedUrlResponse(
String presignedUrl
) {
public static PresignedUrlResponse from(final String presignedUrl) {
return new PresignedUrlResponse(presignedUrl);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package taco.klkl.domain.image.exception;

import taco.klkl.global.error.exception.CustomException;
import taco.klkl.global.error.exception.ErrorCode;

public class FileExtensionNotFoundException extends CustomException {
public FileExtensionNotFoundException() {
super(ErrorCode.FILE_EXTENSION_NOT_FOUND);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package taco.klkl.domain.image.exception;

import taco.klkl.global.error.exception.CustomException;
import taco.klkl.global.error.exception.ErrorCode;

public class ImageNotFoundException extends CustomException {
public ImageNotFoundException() {
super(ErrorCode.IMAGE_NOT_FOUND);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package taco.klkl.domain.image.exception;

import taco.klkl.global.error.exception.CustomException;
import taco.klkl.global.error.exception.ErrorCode;

public class ImageTypeNotFoundException extends CustomException {
public ImageTypeNotFoundException() {
super(ErrorCode.IMAGE_TYPE_NOT_FOUND);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package taco.klkl.domain.image.exception;

import taco.klkl.global.error.exception.CustomException;
import taco.klkl.global.error.exception.ErrorCode;

public class ImageUploadNotCompleteException extends CustomException {
public ImageUploadNotCompleteException() {
super(ErrorCode.IMAGE_UPLOAD_NOT_COMPLETE);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package taco.klkl.domain.image.exception;

import taco.klkl.global.error.exception.CustomException;
import taco.klkl.global.error.exception.ErrorCode;

public class ImageUrlInvalidException extends CustomException {
public ImageUrlInvalidException() {
super(ErrorCode.IMAGE_URL_INVALID);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package taco.klkl.domain.image.exception;

import taco.klkl.global.error.exception.CustomException;
import taco.klkl.global.error.exception.ErrorCode;

public class UploadStateNotFoundException extends CustomException {
public UploadStateNotFoundException() {
super(ErrorCode.UPLOAD_STATE_NOT_FOUND);
}
}
Loading

0 comments on commit e01921e

Please sign in to comment.