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

[BSVR-68] 미디어 업로드를 위한 presigned url 생성 컴포넌트 #16

Merged
merged 25 commits into from
Jul 16, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
402f65c
feat(module) : ncp module 생성
EunjiShin Jul 7, 2024
53628c6
build: spring cloud starter aws dependency 추가
EunjiShin Jul 7, 2024
8034f54
build: openfeign dependency 추가
EunjiShin Jul 7, 2024
7177f15
feat: ncp 모듈 공통 configuration, object storage configuration 추가
EunjiShin Jul 7, 2024
cca7b68
feat: MediaUploadPort 추가
EunjiShin Jul 7, 2024
53f77d3
feat: presigned url 생성 컴포넌트 추가
EunjiShin Jul 8, 2024
c497298
refactor: 현재 시간 조회 부분 분리
EunjiShin Jul 8, 2024
bc65b73
feat: 1차 와이어프레임 변경사항 반영
EunjiShin Jul 9, 2024
2b9037a
feat: media 관련 exception 추가
EunjiShin Jul 9, 2024
a3ccf7f
feat: Media 생성자에 not null 체크 로직 추가
EunjiShin Jul 9, 2024
596c9b6
test(Media) : Media 테스트 추가
EunjiShin Jul 9, 2024
b2290f0
test(Media) : media 도메인 테스트 추가
EunjiShin Jul 9, 2024
ed44d89
test(ImageExtension) : 이미지 확장자 테스트 추가
EunjiShin Jul 9, 2024
afd8dd5
test : 좌석 미디어 확장자 테스트 추가
EunjiShin Jul 9, 2024
207345e
test(PresignedUrlGenerator) : presigned url 생성자 테스트 추가
EunjiShin Jul 9, 2024
1fce40a
feat: config에 profile 설정 추가
EunjiShin Jul 9, 2024
093b360
feat: config에 profile 설정 추가
EunjiShin Jul 9, 2024
c760e34
feat: appliction 모듈에 ncp 서브모듈 profile 추가
pminsung12 Jul 16, 2024
b81544d
feat: ncp 모듈의 application.yaml 설정 + 키는 .env 로 관리
pminsung12 Jul 16, 2024
3f221cd
feat: 도커에 env 설정 추가
pminsung12 Jul 16, 2024
8708f58
feat: application.yaml에 정의한 prefix 적용
pminsung12 Jul 16, 2024
1802095
feat: controller 생성
EunjiShin Jul 16, 2024
0cf3fae
fix: ncp 관련 에러 수정
EunjiShin Jul 16, 2024
d4f8126
fix : resolve merge conflict
EunjiShin Jul 16, 2024
d692dad
feat: api endpoint prefix 추가
EunjiShin Jul 16, 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
1 change: 1 addition & 0 deletions application/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ dependencies {
implementation(project(":domain"))
implementation(project(":usecase"))
implementation(project(":infrastructure:jpa"))
implementation(project(":infrastructure:ncp"))

// spring
implementation("org.springframework.boot:spring-boot-starter")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package org.depromeet.spot.application.common.config;

import org.depromeet.spot.jpa.config.JpaConfig;
import org.depromeet.spot.ncp.NcpConfig;
import org.depromeet.spot.usecase.config.UsecaseConfig;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@ComponentScan(basePackages = {"org.depromeet.spot.application"})
@Configuration
@Import(value = {UsecaseConfig.class, JpaConfig.class})
@Import(value = {UsecaseConfig.class, JpaConfig.class, NcpConfig.class})
public class SpotApplicationConfig {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package org.depromeet.spot.application.media.controller;

public class MediaUploadController {}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import org.depromeet.spot.application.member.dto.request.MemberRequest;
import org.depromeet.spot.application.member.dto.response.MemberResponse;
import org.depromeet.spot.usecase.port.in.MemberUsecase;
import org.depromeet.spot.usecase.port.in.member.MemberUsecase;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
Expand Down
5 changes: 5 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ subprojects {
// lombok
compileOnly("org.projectlombok:lombok:_")
annotationProcessor("org.projectlombok:lombok:_")
testCompileOnly("org.projectlombok:lombok:_")
testAnnotationProcessor("org.projectlombok:lombok:_")

// configuration processor
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor:_")

// test
testImplementation("org.springframework.boot:spring-boot-starter-test:_")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.depromeet.spot.common.exception.media;

import org.depromeet.spot.common.exception.ErrorCode;
import org.springframework.http.HttpStatus;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public enum MediaErrorCode implements ErrorCode {
INVALID_EXTENSION(HttpStatus.BAD_REQUEST, "ME001", "허용하지 않는 확장자입니다."),
INVALID_STADIUM_MEDIA(HttpStatus.BAD_REQUEST, "ME002", "경기장과 관련된 미디어 파일이 아닙니다."),
INVALID_REVIEW_MEDIA(HttpStatus.BAD_REQUEST, "ME003", "리뷰와 관련된 미디어 파일이 아닙니다."),
INVALID_MEDIA(HttpStatus.INTERNAL_SERVER_ERROR, "ME004", "잘못된 미디어 형식입니다."),
;

private final HttpStatus status;
private final String code;
private String message;

public MediaErrorCode appended(final String s) {
message = message + " {" + s + "}";
return this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package org.depromeet.spot.common.exception.media;

import org.depromeet.spot.common.exception.BusinessException;

public abstract class MediaException extends BusinessException {

protected MediaException(MediaErrorCode errorCode) {
super(errorCode);
}

public static class InvalidExtensionException extends MediaException {
public InvalidExtensionException() {
super(MediaErrorCode.INVALID_EXTENSION);
}

public InvalidExtensionException(final String s) {
super(MediaErrorCode.INVALID_EXTENSION.appended(s));
}
}

public static class InvalidStadiumMediaException extends MediaException {
public InvalidStadiumMediaException() {
super(MediaErrorCode.INVALID_STADIUM_MEDIA);
}
}

public static class InvalidReviewMediaException extends MediaException {
public InvalidReviewMediaException() {
super(MediaErrorCode.INVALID_REVIEW_MEDIA);
}
}

public static class InvalidMediaException extends MediaException {
public InvalidMediaException() {
super(MediaErrorCode.INVALID_MEDIA);
}
}
}
1 change: 1 addition & 0 deletions domain/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
dependencies {
implementation(project(":common"))
}

tasks.jar { enabled = true }
Expand Down
28 changes: 28 additions & 0 deletions domain/src/main/java/org/depromeet/spot/domain/media/Media.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.depromeet.spot.domain.media;

import org.depromeet.spot.common.exception.media.MediaException.InvalidMediaException;

import lombok.Getter;

@Getter
public class Media {

private final String url;
private final String fileName;

public Media(final String url, final String fileName) {
checkIsValidMedia(url, fileName);
this.url = url;
this.fileName = fileName;
}

private void checkIsValidMedia(final String url, final String fileName) {
if (isBlankOrNull(url) || isBlankOrNull(fileName)) {
throw new InvalidMediaException();
}
}

private boolean isBlankOrNull(final String str) {
return str == null || str.isBlank();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.depromeet.spot.domain.media;

public enum MediaProperty {
REVIEW,
STADIUM,
;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.depromeet.spot.domain.media.extension;

import java.util.Arrays;
import java.util.Map;
import java.util.stream.Collectors;

import org.depromeet.spot.common.exception.media.MediaException.InvalidExtensionException;

import lombok.Getter;

@Getter
public enum ImageExtension {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1차 MVP에서는 영상을 관리하지 않기 때문에, 이미지 확장자만 추가했어!
안드에게 문의해보니 보통 사진이 jpg, jpeg, png로 들어온다고 답변 받아서 요 값들로 enum 생성한 상태!

JPG("jpg"),
JPEG("jpeg"),
PNG("png"),
;

private final String value;

private static final Map<String, ImageExtension> cachedImageExtension =
Arrays.stream(ImageExtension.values())
.collect(
Collectors.toMap(extension -> extension.value, extension -> extension));

ImageExtension(final String value) {
this.value = value;
}

public static boolean isValid(final String reqExtension) {
return cachedImageExtension.containsKey(reqExtension);
}

public static ImageExtension from(final String reqExtension) {
ImageExtension extension = cachedImageExtension.get(reqExtension);
if (extension == null) {
throw new InvalidExtensionException(reqExtension);
}
return extension;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package org.depromeet.spot.domain.media.extension;

import java.util.Arrays;
import java.util.Map;
import java.util.stream.Collectors;

import org.depromeet.spot.common.exception.media.MediaException.InvalidExtensionException;

import lombok.Getter;

@Getter
public enum StadiumSeatMediaExtension {
Copy link
Collaborator Author

@EunjiShin EunjiShin Jul 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

공연장 좌석 배치도를 관리하기 위한 확장자 enum!
1차 MVP에서 좌석 배치도는 디자인 파트에서 코드 삽입한 svg 파일만 사용할 수 있어.

SVG("svg"),
;

private final String value;

private static final Map<String, StadiumSeatMediaExtension> cachedStadiumMedia =
Arrays.stream(StadiumSeatMediaExtension.values())
.collect(
Collectors.toMap(extension -> extension.value, extension -> extension));

StadiumSeatMediaExtension(final String value) {
this.value = value;
}

public static boolean isValid(final String reqExtension) {
return cachedStadiumMedia.containsKey(reqExtension);
}

public static StadiumSeatMediaExtension from(final String reqExtension) {
StadiumSeatMediaExtension extension = cachedStadiumMedia.get(reqExtension);
if (extension == null) {
throw new InvalidExtensionException(reqExtension);
}
return extension;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package org.depromeet.spot.domain.media;

import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;

import org.depromeet.spot.common.exception.media.MediaException.InvalidMediaException;
import org.junit.jupiter.api.Test;

class MediaTest {

@Test
public void url이_공백이면_미디어를_생성할_수_없다() {
// given
final String url = "";
final String fileName = "file";

// when
// then
assertThatThrownBy(() -> new Media(url, fileName))
.isInstanceOf(InvalidMediaException.class);
}

@Test
public void url이_null이면_미디어를_생성할_수_없다() {
// given
final String url = null;
final String fileName = "file";

// when
// then
assertThatThrownBy(() -> new Media(url, fileName))
.isInstanceOf(InvalidMediaException.class);
}

@Test
public void fileName이_공백이면_미디어를_생성할_수_없다() {
// given
final String url = "url";
final String fileName = "";

// when
// then
assertThatThrownBy(() -> new Media(url, fileName))
.isInstanceOf(InvalidMediaException.class);
}

@Test
public void fileName이_null이면_미디어를_생성할_수_없다() {
// given
final String url = "url";
final String fileName = "";

// when
// then
assertThatThrownBy(() -> new Media(url, fileName))
.isInstanceOf(InvalidMediaException.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package org.depromeet.spot.domain.media.extension;

import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

import org.depromeet.spot.common.exception.media.MediaException.InvalidExtensionException;
import org.junit.jupiter.api.Test;

class ImageExtensionTest {

@Test
public void 유효한_리뷰_미디어_확장자인지_판별할_수_있다() {
// given
final String validValue = "jpg";
final String invalidValue = "mp4";

// when
final boolean checkValid = ImageExtension.isValid(validValue);
final boolean checkInvalid = ImageExtension.isValid(invalidValue);

// then
assertTrue(checkValid);
assertFalse(checkInvalid);
}

@Test
public void value로_리뷰_미디어_확장자를_찾을_수_있다() {
// given
final String jpgValue = "jpg";
final String jpegValue = "jpeg";
final String pngValue = "png";

// when
ImageExtension jpgResult = ImageExtension.from(jpgValue);
ImageExtension jpegResult = ImageExtension.from(jpegValue);
ImageExtension pngResult = ImageExtension.from(pngValue);

// then
assertAll(
() -> assertEquals(jpgResult, ImageExtension.JPG),
() -> assertEquals(jpegResult, ImageExtension.JPEG),
() -> assertEquals(pngResult, ImageExtension.PNG));
}

@Test
public void 유효하지_않은_value로_좌석_미디어_확장자를_찾을_수_없다() {
// given
final String value = "test!";

// when
// then
assertThatThrownBy(() -> ImageExtension.from(value))
.isInstanceOf(InvalidExtensionException.class);
}
}
Loading
Loading