Skip to content
Merged
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
1 change: 1 addition & 0 deletions .github/workflows/dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ jobs:
deploy:
name: Build & Deploy to OCI (Dev)
runs-on: ubuntu-latest
environment: Test-Server

steps:
# (1) 코드 체크아웃
Expand Down
10 changes: 6 additions & 4 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ dependencies {

// Feature Flag by AWS AppConfig
implementation(platform("software.amazon.awssdk:bom:2.27.21"))
implementation 'software.amazon.awssdk:s3'
implementation 'software.amazon.awssdk:appconfig'
implementation 'software.amazon.awssdk:appconfigdata'

Expand Down Expand Up @@ -118,10 +119,11 @@ dependencies {

// Test Container
testImplementation 'org.junit.jupiter:junit-jupiter:5.8.1'
testImplementation 'org.testcontainers:testcontainers:1.19.8'
testImplementation 'org.testcontainers:junit-jupiter:1.19.8'
testImplementation 'org.testcontainers:mariadb:1.19.8'
testImplementation 'org.testcontainers:chromadb:1.19.8'
testImplementation "org.testcontainers:testcontainers:2.0.2"
testImplementation "org.testcontainers:testcontainers-junit-jupiter:2.0.2"
testImplementation "org.testcontainers:testcontainers-mariadb:2.0.2"
testImplementation "org.testcontainers:testcontainers-chromadb:2.0.2"
testImplementation "org.apache.commons:commons-lang3:3.18.0"
testImplementation 'io.projectreactor:reactor-test'
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.kustacks.kuring.common.exception;

import com.kustacks.kuring.common.exception.code.ErrorCode;
import lombok.Getter;

@Getter
public class InfrastructureException extends RuntimeException {
private final ErrorCode errorCode;

public InfrastructureException(ErrorCode errorCode) {
this.errorCode = errorCode;
}

public InfrastructureException(ErrorCode errorCode, Exception e) {
super(e);
this.errorCode = errorCode;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,11 @@ public enum ErrorCode {
*/
DOMAIN_CANNOT_CREATE("해당 도메인을 생성할 수 없습니다."),
DEPARTMENT_NOT_FOUND("해당 학과를 찾을 수 없습니다."),
QUESTION_COUNT_NOT_ENOUGH(HttpStatus.TOO_MANY_REQUESTS, "남은 질문 횟수가 부족합니다.");
QUESTION_COUNT_NOT_ENOUGH(HttpStatus.TOO_MANY_REQUESTS, "남은 질문 횟수가 부족합니다."),

STORAGE_S3_SDK_PROBLEM(HttpStatus.INTERNAL_SERVER_ERROR, "S3 클라이언트 통신 간 에러가 발생했습니다."),
FILE_IO_EXCEPTION(HttpStatus.INTERNAL_SERVER_ERROR, "파일을 읽어들이는데 문제가 발생했습니다.")
;

private final HttpStatus httpStatus;
private final String message;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.kustacks.kuring.common.dto.ErrorResponse;
import com.kustacks.kuring.common.exception.AdminException;
import com.kustacks.kuring.common.exception.BusinessException;
import com.kustacks.kuring.common.exception.InfrastructureException;
import com.kustacks.kuring.common.exception.InternalLogicException;
import com.kustacks.kuring.common.exception.InvalidStateException;
import com.kustacks.kuring.common.exception.NoPermissionException;
Expand Down Expand Up @@ -79,6 +80,13 @@ public ResponseEntity<ErrorResponse> FirebaseSubscribeExceptionHandler(FirebaseS
.body(new ErrorResponse(ErrorCode.API_FB_SERVER_ERROR));
}

@ExceptionHandler
public ResponseEntity<ErrorResponse> InfrastructureExceptionHandler(InfrastructureException exception) {
log.warn("{} {}", exception.getClass().getName(), exception.getErrorCode().getMessage(), exception);
return ResponseEntity.status(exception.getErrorCode().getHttpStatus())
.body(new ErrorResponse(exception.getErrorCode()));
}

@ExceptionHandler
public ResponseEntity<ErrorResponse> InvalidStateExceptionHandler(InvalidStateException exception) {
log.info("[InvalidStateException] {}", exception.getMessage());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.kustacks.kuring.common.properties;

import jakarta.validation.constraints.*;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;

@Validated
@ConfigurationProperties(prefix = "cloud.storage")
public record CloudStorageProperties(
@NotBlank String region,
String endpoint, // OCI인 경우에만 사용.
@NotBlank String bucket,
@NotNull Credentials credentials
) {
public record Credentials(
@NotBlank String accessKey,
@NotBlank String secretKey
) {

}
}
81 changes: 81 additions & 0 deletions src/main/java/com/kustacks/kuring/config/CloudStorageConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package com.kustacks.kuring.config;

import com.kustacks.kuring.common.properties.CloudStorageProperties;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.S3ClientBuilder;
import software.amazon.awssdk.services.s3.S3Configuration;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;

import java.net.URI;

/**
* Local, Test환경 에서는 사용하지 않는다.
* Prod, Dev환경에서 사용한다.
* Prod : AWS S3 서비스 이용
* Dev : OCI Storage 서비스 이용.
* 서비스 사용은 S3기술로 호환가능. Presigned 기술도 V4지원.
*/
@Configuration
@RequiredArgsConstructor
public class CloudStorageConfig {

private final CloudStorageProperties properties;

@Bean
@Profile("prod")
public S3Client awsS3Client() {
return defaultS3ClientBuilder()
.build();
}

@Bean
@Profile("prod")
public S3Presigner awsS3Presigner() {
return defaultS3PresignerBuilder()
.build();
}

@Bean
@Profile("dev")
public S3Client oracleStorageClient() {
return defaultS3ClientBuilder()
.endpointOverride(URI.create(properties.endpoint())) //OCI는 엔드포인트를 강제 지정한다.
.forcePathStyle(true) // 강제로 PathStyle 지정.
.build();
}

@Bean
@Profile("dev")
public S3Presigner oracleS3Presigner() {
return defaultS3PresignerBuilder()
.endpointOverride(URI.create(properties.endpoint()))
.serviceConfiguration(S3Configuration.builder()
.pathStyleAccessEnabled(true)
.build())
.build();
}

private S3ClientBuilder defaultS3ClientBuilder() {
return S3Client.builder()
.region(Region.of(properties.region()))
.credentialsProvider(staticCredentialsProvider());
}

private S3Presigner.Builder defaultS3PresignerBuilder() {
return S3Presigner.builder()
.region(Region.of(properties.region()))
.credentialsProvider(staticCredentialsProvider());
}

private StaticCredentialsProvider staticCredentialsProvider() {
return StaticCredentialsProvider
.create(AwsBasicCredentials.create(properties.credentials().accessKey(), properties.credentials().secretKey()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@ private void loggingNoticeSendInfo(List<NoticeMessageDto> notificationDtoList) {

log.info("전송된 공지 목록은 다음과 같습니다.");
for (NoticeMessageDto noticeMessageDto : notificationDtoList) {
log.info("아이디 = {}, 날짜 = {}, 카테고리 = {}, 제목 = {}",
log.info("ID = {}, ArticleId = {}, 날짜 = {}, 카테고리 = {}, 제목 = {}",
noticeMessageDto.getId(),
noticeMessageDto.getArticleId(),
noticeMessageDto.getPostedDate(),
noticeMessageDto.getCategory(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,16 @@ public void saveAllDepartmentNotices(List<DepartmentNotice> departmentNotices) {
this.noticeJdbcRepository.saveAllDepartmentNotices(departmentNotices);
}

@Override
public List<Notice> saveAllCategoryNoticesAndReturn(List<Notice> notices) {
return this.noticeRepository.saveAll(notices);
}

@Override
public List<DepartmentNotice> saveAllDepartmentNoticesAndReturn(List<DepartmentNotice> notices) {
return this.noticeRepository.saveAll(notices);
}

@Override
public void deleteAllByIdsAndCategory(CategoryName categoryName, List<String> articleIds) {
this.noticeRepository.deleteAllByIdsAndCategory(categoryName, articleIds);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ public interface NoticeCommandPort {

void saveAllCategoryNotices(List<Notice> notices);
void saveAllDepartmentNotices(List<DepartmentNotice> departmentNotices);
List<Notice> saveAllCategoryNoticesAndReturn(List<Notice> notices);
List<DepartmentNotice> saveAllDepartmentNoticesAndReturn(List<DepartmentNotice> notices);
void deleteAllByIdsAndCategory(CategoryName categoryName, List<String> articleIds);
void deleteAllByIdsAndDepartment(DepartmentName departmentName, List<String> articleIds);
void changeNoticeImportantToFalseByArticleId(CategoryName categoryName, List<String> articleIds);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ private List<NoticeDepartmentNameResult> convertDepartmentNameDtos(List<DeptInfo
}

private boolean isDepartmentSearchRequest(String type, String department) {
return type.equals("dep") && !department.isEmpty();
return type.equals("dep") && department != null && !department.isBlank();
}

private boolean isDepartment(String categoryName) {
Expand Down
Loading
Loading