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

feat(KL-119): add s3 image #59

Merged
merged 65 commits into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
552fb6e
KL-119/feat: add s3 config
ohhamma Aug 21, 2024
15d1341
KL-119/feat: add FileExtension enum
ohhamma Aug 21, 2024
272da0c
KL-119/feat: add Image entity
ohhamma Aug 22, 2024
ac6c8f3
KL-119/feat: add ImageCreateRequest
ohhamma Aug 22, 2024
0f43f1b
KL-119/feat: add PresignedUrlResponse
ohhamma Aug 22, 2024
3ad89d9
KL-119/feat: add ImageRepository
ohhamma Aug 22, 2024
30ef446
KL-119/feat: create image presigned url
ohhamma Aug 22, 2024
cf9e717
docs: rename invalid controller tags
ohhamma Aug 23, 2024
2e7ae97
KL-119/refactor:
ohhamma Aug 23, 2024
17ca8f9
KL-119/refactor: separate profile image upload method
ohhamma Aug 23, 2024
2b13870
feat: add FileExtension.WEBP
ohhamma Aug 26, 2024
75a5323
feat: add cloudfront domain
ohhamma Aug 26, 2024
98f465f
feat: add UploadState
ohhamma Aug 26, 2024
dda7700
refactor: move
ohhamma Aug 26, 2024
12cfbfa
KL-119/fix: FileExtension JPG value to jpeg
ohhamma Aug 27, 2024
7ff3d53
KL-119/refactor: change Gender static method logic
ohhamma Aug 27, 2024
e6f8a8a
KL-119/refactor: change user ImageType
ohhamma Aug 27, 2024
defa912
KL-119/feat: add targetId
ohhamma Aug 27, 2024
0536b03
KL-119/feat: add ImageKeyGenerator
ohhamma Aug 27, 2024
0593b9c
kL-119/feat: user image upload complete
ohhamma Aug 27, 2024
0b9d2f9
KL-119/fix: convert gender with static method
ohhamma Aug 27, 2024
f655ea2
KL-119/feat: find image by image url
ohhamma Aug 27, 2024
617b93c
KL-119/refactor: add profileImageUrl
ohhamma Aug 27, 2024
e99e83f
KL-119/rename: change user image upload request name
ohhamma Aug 27, 2024
da14747
KL-119/fix: find test user by id 1
ohhamma Aug 27, 2024
032105e
KL-119/fix: change to field profile image url
ohhamma Aug 27, 2024
8b450e5
KL-119/feat: add UserNotFoundException
ohhamma Aug 27, 2024
7111165
KL-119/feat: update user info with profile image
ohhamma Aug 27, 2024
4063ace
KL-119/chore: remove unused method
ohhamma Aug 27, 2024
8e5610a
KL-119/test: edit test by code changes
ohhamma Aug 27, 2024
0879d18
KL-119/chore: change s3 cloudfront domain env name
ohhamma Aug 27, 2024
07c1a9a
KL-119/chore: delete test system property
ohhamma Aug 27, 2024
8bfadd1
KL-119/test: fix checkstyle errors
ohhamma Aug 27, 2024
01b9c22
KL-119/test: typo in comment test
ohhamma Aug 27, 2024
39c5ed1
KL-119/test: fix UserServiceTest error
ohhamma Aug 27, 2024
ceeec18
KL-119/test: fix error in user test
ohhamma Aug 27, 2024
ef78819
KL-119/test: fix error in product test
ohhamma Aug 27, 2024
91d9b24
KL-119/refactor: separate UserService
ohhamma Aug 27, 2024
5a9237c
KL-119/feat: create product image upload presigned url
ohhamma Aug 27, 2024
b3d1e60
KL-119/feat: upload complete product image
ohhamma Aug 27, 2024
b40e042
KL-119/feat: update user image when upload complete
ohhamma Aug 27, 2024
8815214
KL-119/refactor: add final keyword and private constructor
ohhamma Aug 27, 2024
b1a807a
KL-119/refactor: separate update product tags
ohhamma Aug 27, 2024
c155848
KL-119/remove: remove image util
ohhamma Aug 27, 2024
db21cfe
KL-119/chore: remove validateProfileImageUrl
ohhamma Aug 27, 2024
3d0888b
KL-119/feat: add ProductImage
ohhamma Aug 27, 2024
c809faf
KL-119/feat: update product images when upload complete
ohhamma Aug 27, 2024
75d038a
feat: delete previously upload complete user images
ohhamma Aug 28, 2024
b79c4b4
KL-119/chore: change aws sdk release version
ohhamma Aug 28, 2024
801a1b9
KL-119/refactor: rename enum and reduce duplicate codes
ohhamma Aug 28, 2024
8571976
KL-119/fix: checkstyle error
ohhamma Aug 28, 2024
7a8f1b2
KL-119/test: fix test errors
ohhamma Aug 28, 2024
84fdfc0
KL-119/refactor: unify image upload request dto
ohhamma Aug 28, 2024
29a9fa1
KL-119/feat: add ProductImageResponse
ohhamma Aug 28, 2024
ebebec4
KL-119/test: fix test errors
ohhamma Aug 28, 2024
33dea51
merge from develop
ohhamma Aug 28, 2024
27c5c23
KL-119/chore: edit workflow secret env logic
ohhamma Aug 28, 2024
f20d792
KL-119/fix: add spring profile active for test
ohhamma Aug 28, 2024
0d8ef59
KL-119/fix: restore workflow and add test system property
ohhamma Aug 28, 2024
dfe2726
KL-119/chore: remove comment
ohhamma Aug 28, 2024
8da39c3
KL-119/chore: edit workflow secret env logic
ohhamma Aug 28, 2024
9cf53b3
KL-119/chore: add newline
ohhamma Aug 28, 2024
4313c5c
KL-119/fix: fix uninteded logic in enum
ohhamma Aug 28, 2024
576e640
KL-119/chore: remove unused methods
ohhamma Aug 28, 2024
0073c8f
KL-119/chore: rename as OUTDATED
ohhamma Aug 28, 2024
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
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