diff --git a/build.gradle b/build.gradle index 903c2868..4cecab6f 100644 --- a/build.gradle +++ b/build.gradle @@ -1,116 +1,119 @@ import org.hidetake.gradle.swagger.generator.GenerateSwaggerUI buildscript { - ext { - restdocsApiSpecVersion = '0.18.3' // restdocsApiSpecVersion 버전 변수 설정 - } + ext { + restdocsApiSpecVersion = '0.18.3' // restdocsApiSpecVersion 버전 변수 설정 + } } plugins { - id 'java' - id 'org.springframework.boot' version '3.1.4' - id 'io.spring.dependency-management' version '1.1.3' - id 'com.epages.restdocs-api-spec' version "${restdocsApiSpecVersion}" - id 'org.hidetake.swagger.generator' version '2.18.2' + id 'java' + id 'org.springframework.boot' version '3.1.4' + id 'io.spring.dependency-management' version '1.1.3' + id 'com.epages.restdocs-api-spec' version "${restdocsApiSpecVersion}" + id 'org.hidetake.swagger.generator' version '2.18.2' } group = 'coffee-meet' version = '0.0.1-SNAPSHOT' java { - sourceCompatibility = '17' + sourceCompatibility = '17' } configurations { - compileOnly { - extendsFrom annotationProcessor - } + compileOnly { + extendsFrom annotationProcessor + } } repositories { - mavenCentral() + mavenCentral() } ext { - set('snippetsDir', file("build/generated-snippets")) + set('snippetsDir', file("build/generated-snippets")) } dependencies { - /* Database */ - implementation 'org.springframework.boot:spring-boot-starter-data-jpa' - implementation 'org.springframework.boot:spring-boot-starter-data-redis' - runtimeOnly 'com.mysql:mysql-connector-j' - runtimeOnly 'com.h2database:h2' - - /* Spring */ - implementation 'org.springframework.boot:spring-boot-starter-validation' - implementation 'org.springframework.boot:spring-boot-starter-web' - - /* JWT */ - implementation 'io.jsonwebtoken:jjwt-api:0.11.5' - implementation 'io.jsonwebtoken:jjwt-impl:0.11.5' - implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5' - - /* Lombok */ - compileOnly 'org.projectlombok:lombok' - annotationProcessor 'org.projectlombok:lombok' - - /* Test */ - testImplementation 'org.springframework.boot:spring-boot-starter-test' - testImplementation group: 'org.instancio', name: 'instancio-junit', version: '3.0.0' - - /* TestContainer */ - testImplementation "org.testcontainers:testcontainers:1.19.0" - testImplementation "org.testcontainers:junit-jupiter:1.19.0" - - /* Docs & UI */ - testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc' - testImplementation "com.epages:restdocs-api-spec-mockmvc:${restdocsApiSpecVersion}" - implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0' - swaggerUI 'org.webjars:swagger-ui:4.11.1' + /* Database */ + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.springframework.boot:spring-boot-starter-data-redis' + runtimeOnly 'com.mysql:mysql-connector-j' + runtimeOnly 'com.h2database:h2' + + /* Spring */ + implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'org.springframework.boot:spring-boot-starter-web' + + /* JWT */ + implementation 'io.jsonwebtoken:jjwt-api:0.11.5' + implementation 'io.jsonwebtoken:jjwt-impl:0.11.5' + implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5' + + /* Lombok */ + compileOnly 'org.projectlombok:lombok' + annotationProcessor 'org.projectlombok:lombok' + + /* Test */ + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation group: 'org.instancio', name: 'instancio-junit', version: '3.0.0' + + /* TestContainer */ + testImplementation "org.testcontainers:testcontainers:1.19.0" + testImplementation "org.testcontainers:junit-jupiter:1.19.0" + + /* Docs & UI */ + testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc' + testImplementation "com.epages:restdocs-api-spec-mockmvc:${restdocsApiSpecVersion}" + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0' + swaggerUI 'org.webjars:swagger-ui:4.11.1' + + /* Cloud */ + implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' } tasks.named('test') { - useJUnitPlatform() + useJUnitPlatform() } swaggerSources { - sample { - setInputFile(file("${project.buildDir}/api-spec/openapi3.yaml")) - } + sample { + setInputFile(file("${project.buildDir}/api-spec/openapi3.yaml")) + } } openapi3 { - servers = [ - { url = "http://localhost:8080" }, - { url = "http://13.209.253.204:8080" } - ] - title = "API 문서" - description = "RestDocsWithSwagger Docs" - version = "0.0.1" - format = "yaml" + servers = [ + { url = "http://localhost:8080" }, + { url = "http://13.209.253.204:8080" } + ] + title = "API 문서" + description = "RestDocsWithSwagger Docs" + version = "0.0.1" + format = "yaml" } tasks.withType(GenerateSwaggerUI) { - dependsOn 'openapi3' - doFirst { - def swaggerUIFile = file("${openapi3.outputDirectory}/openapi3.yaml") - - def securitySchemesContent = " securitySchemes:\n" + \ - " APIKey:\n" + \ - " type: apiKey\n" + \ - " name: Authorization\n" + \ - " in: header\n" + \ - "security:\n" + - " - APIKey: [] # Apply the security scheme here" - - swaggerUIFile.append securitySchemesContent - } + dependsOn 'openapi3' + doFirst { + def swaggerUIFile = file("${openapi3.outputDirectory}/openapi3.yaml") + + def securitySchemesContent = " securitySchemes:\n" + \ + " APIKey:\n" + \ + " type: apiKey\n" + \ + " name: Authorization\n" + \ + " in: header\n" + \ + "security:\n" + + " - APIKey: [] # Apply the security scheme here" + + swaggerUIFile.append securitySchemesContent + } } bootJar { - dependsOn generateSwaggerUISample - from("${generateSwaggerUISample.outputDir}") { - into 'static/docs' - } + dependsOn generateSwaggerUISample + from("${generateSwaggerUISample.outputDir}") { + into 'static/docs' + } } diff --git a/src/main/java/coffeemeet/server/admin/domain/Admin.java b/src/main/java/coffeemeet/server/admin/domain/Admin.java index 2f122938..c5f4341c 100644 --- a/src/main/java/coffeemeet/server/admin/domain/Admin.java +++ b/src/main/java/coffeemeet/server/admin/domain/Admin.java @@ -1,5 +1,6 @@ package coffeemeet.server.admin.domain; +import coffeemeet.server.common.entity.BaseEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Id; @@ -12,7 +13,7 @@ @Getter @Table(name = "admins") @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class Admin { +public class Admin extends BaseEntity { @Id @Column(nullable = false) @@ -20,4 +21,5 @@ public class Admin { @Column(nullable = false) private String password; + } diff --git a/src/main/java/coffeemeet/server/certification/controller/CertificationController.java b/src/main/java/coffeemeet/server/certification/controller/CertificationController.java new file mode 100644 index 00000000..59eb9616 --- /dev/null +++ b/src/main/java/coffeemeet/server/certification/controller/CertificationController.java @@ -0,0 +1,38 @@ +package coffeemeet.server.certification.controller; + +import static org.springframework.http.HttpStatus.CREATED; + +import coffeemeet.server.certification.service.CertificationService; +import coffeemeet.server.common.util.FileUtils; +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/certification") +public class CertificationController { + + private final CertificationService certificationService; + + @PostMapping("/users/{userId}/business-card") + @ResponseStatus(CREATED) + public void uploadBusinessCard( + @PathVariable("userId") + @NotNull(message = "유저 ID는 null일 수 없습니다.") + long userId, + @RequestPart("businessCard") + @NotNull(message = "명함 이미지는 null일 수 없습니다.") + MultipartFile businessCard + ) { + certificationService.uploadBusinessCard(userId, + FileUtils.convertMultipartFileToFile(businessCard)); + } + +} diff --git a/src/main/java/coffeemeet/server/certification/service/BusinessCardS3KeyGenerator.java b/src/main/java/coffeemeet/server/certification/service/BusinessCardS3KeyGenerator.java new file mode 100644 index 00000000..052bf629 --- /dev/null +++ b/src/main/java/coffeemeet/server/certification/service/BusinessCardS3KeyGenerator.java @@ -0,0 +1,14 @@ +package coffeemeet.server.certification.service; + +import java.time.LocalDateTime; +import java.util.UUID; +import org.springframework.stereotype.Service; + +@Service +public class BusinessCardS3KeyGenerator { + + public String generate() { + return String.format("BusinessCard-%s-%s", LocalDateTime.now(), UUID.randomUUID()); + } + +} diff --git a/src/main/java/coffeemeet/server/certification/service/CertificationService.java b/src/main/java/coffeemeet/server/certification/service/CertificationService.java new file mode 100644 index 00000000..61b9831c --- /dev/null +++ b/src/main/java/coffeemeet/server/certification/service/CertificationService.java @@ -0,0 +1,26 @@ +package coffeemeet.server.certification.service; + +import coffeemeet.server.common.media.S3MediaService; +import coffeemeet.server.common.util.FileUtils; +import coffeemeet.server.user.service.UserService; +import java.io.File; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class CertificationService { + + private final S3MediaService s3MediaService; + private final UserService userService; + private final BusinessCardS3KeyGenerator businessCardS3KeyGenerator; + + public void uploadBusinessCard(long userId, File file) { + String key = businessCardS3KeyGenerator.generate(); + + s3MediaService.upload(key, file); + userService.updateBusinessCardUrl(userId, s3MediaService.getUrl(key)); + + FileUtils.delete(file); + } +} diff --git a/src/main/java/coffeemeet/server/common/config/S3Config.java b/src/main/java/coffeemeet/server/common/config/S3Config.java new file mode 100644 index 00000000..67f8409a --- /dev/null +++ b/src/main/java/coffeemeet/server/common/config/S3Config.java @@ -0,0 +1,37 @@ +package coffeemeet.server.common.config; + +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class S3Config { + + private final String accessKey; + private final String secretKey; + private final String region; + + public S3Config( + @Value("${cloud.aws.credentials.access-key}") String accessKey, + @Value("${cloud.aws.credentials.secret-key}") String secretKey, + @Value("${cloud.aws.region.static}") String region) { + this.accessKey = accessKey; + this.secretKey = secretKey; + this.region = region; + } + + @Bean + public AmazonS3 amazonS3Client() { + BasicAWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey); + return AmazonS3ClientBuilder + .standard() + .withRegion(region) + .withCredentials(new AWSStaticCredentialsProvider(credentials)) + .build(); + } + +} diff --git a/src/main/java/coffeemeet/server/common/media/S3MediaService.java b/src/main/java/coffeemeet/server/common/media/S3MediaService.java new file mode 100644 index 00000000..0d0f5932 --- /dev/null +++ b/src/main/java/coffeemeet/server/common/media/S3MediaService.java @@ -0,0 +1,34 @@ +package coffeemeet.server.common.media; + +import com.amazonaws.services.s3.AmazonS3; +import java.io.File; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +@Service +public class S3MediaService { + + private final AmazonS3 amazonS3; + private final String bucketName; + + public S3MediaService( + AmazonS3 amazonS3, + @Value("${cloud.aws.s3.bucket}") String bucketName + ) { + this.amazonS3 = amazonS3; + this.bucketName = bucketName; + } + + public void upload(String key, File file) { + amazonS3.putObject(bucketName, key, file); + } + + public void delete(String key) { + amazonS3.deleteObject(bucketName, key); + } + + public String getUrl(String key) { + return amazonS3.getUrl(bucketName, key).toExternalForm(); + } + +} diff --git a/src/main/java/coffeemeet/server/common/util/FileUtils.java b/src/main/java/coffeemeet/server/common/util/FileUtils.java new file mode 100644 index 00000000..caf7f615 --- /dev/null +++ b/src/main/java/coffeemeet/server/common/util/FileUtils.java @@ -0,0 +1,42 @@ +package coffeemeet.server.common.util; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.springframework.web.multipart.MultipartFile; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class FileUtils { + + private static final String MULTIPART_FILE_TRANSFER_ERROR = "MULTIPART FILE을 FILE로 변환 중 오류가 발생했습니다."; + private static final String FILE_DELETE_ERROR = "FILE 삭제 중 오류가 발생했습니다."; + + public static class FileIOException extends RuntimeException { + + public FileIOException(String message, Throwable e) { + super(message, e); + } + + } + + public static File convertMultipartFileToFile(MultipartFile multipartFile) { + try { + File file = File.createTempFile("temp", multipartFile.getOriginalFilename()); + multipartFile.transferTo(file); + return file; + } catch (IOException e) { + throw new FileIOException(MULTIPART_FILE_TRANSFER_ERROR, e); + } + } + + public static void delete(File file) { + try { + Files.delete(file.toPath()); + } catch (IOException e) { + throw new FileIOException(FILE_DELETE_ERROR, e); + } + } + +} diff --git a/src/main/java/coffeemeet/server/report/domain/Report.java b/src/main/java/coffeemeet/server/report/domain/Report.java index 1e1259f6..1ec564b5 100644 --- a/src/main/java/coffeemeet/server/report/domain/Report.java +++ b/src/main/java/coffeemeet/server/report/domain/Report.java @@ -1,5 +1,6 @@ package coffeemeet.server.report.domain; +import coffeemeet.server.common.entity.BaseEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; @@ -16,7 +17,7 @@ @Getter @Table(name = "reports") @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class Report { +public class Report extends BaseEntity { private static final int TITLE_MAX_LENGTH = 20; private static final int REASON_MAX_LENGTH = 200; diff --git a/src/main/java/coffeemeet/server/user/domain/Birth.java b/src/main/java/coffeemeet/server/user/domain/Birth.java index 506fe396..e772f1db 100644 --- a/src/main/java/coffeemeet/server/user/domain/Birth.java +++ b/src/main/java/coffeemeet/server/user/domain/Birth.java @@ -2,21 +2,22 @@ import jakarta.persistence.Column; import jakarta.persistence.Embeddable; +import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; import org.springframework.util.StringUtils; @Getter @Embeddable -@NoArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class Birth { private static final int BIRTH_LENGTH = 4; - @Column(nullable = false, length = 4) + @Column(nullable = false, length = BIRTH_LENGTH) String year; - @Column(nullable = false, length = 4) + @Column(nullable = false, length = BIRTH_LENGTH) String day; public Birth(String year, String day) { diff --git a/src/main/java/coffeemeet/server/user/domain/Certification.java b/src/main/java/coffeemeet/server/user/domain/Certification.java index 3c6b2577..e398f4b9 100644 --- a/src/main/java/coffeemeet/server/user/domain/Certification.java +++ b/src/main/java/coffeemeet/server/user/domain/Certification.java @@ -4,14 +4,14 @@ import jakarta.persistence.Embeddable; import jakarta.persistence.Embedded; import lombok.Getter; -import lombok.NoArgsConstructor; -import org.springframework.util.StringUtils; @Getter @Embeddable -@NoArgsConstructor public class Certification { + private static final String DEFAULT_EMAIL = "default@default.com"; + private static final String DEFAULT = "default"; + @Embedded @Column(nullable = false) private CompanyEmail companyEmail; @@ -25,24 +25,15 @@ public class Certification { @Column(nullable = false) private String department; - public Certification(CompanyEmail companyEmail, String businessCardUrl, String department) { - validateBusinessCardUrl(businessCardUrl); - validateDepartment(department); - this.companyEmail = companyEmail; - this.businessCardUrl = businessCardUrl; - this.department = department; - } - - private void validateBusinessCardUrl(String businessCardUrl) { - if (!StringUtils.hasText(businessCardUrl)) { - throw new IllegalArgumentException("올바르지 않은 명함 url입니다."); - } + public Certification() { + this.companyEmail = new CompanyEmail(DEFAULT_EMAIL); + this.businessCardUrl = DEFAULT; + this.department = DEFAULT; + isCertificated = false; } - private void validateDepartment(String department) { - if (!StringUtils.hasText(department)) { - throw new IllegalArgumentException("올바르지 않은 부서 이름입니다."); - } + public void updateBusinessCardUrl(String newBusinessCardUrl) { + this.businessCardUrl = newBusinessCardUrl; } public void certificate() { diff --git a/src/main/java/coffeemeet/server/user/domain/Email.java b/src/main/java/coffeemeet/server/user/domain/Email.java index 14cd302e..e52f12d3 100644 --- a/src/main/java/coffeemeet/server/user/domain/Email.java +++ b/src/main/java/coffeemeet/server/user/domain/Email.java @@ -2,12 +2,13 @@ import coffeemeet.server.common.util.Patterns; import jakarta.persistence.Embeddable; +import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @Getter @Embeddable -@NoArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class Email { private String email; diff --git a/src/main/java/coffeemeet/server/user/domain/OAuthInfo.java b/src/main/java/coffeemeet/server/user/domain/OAuthInfo.java index da841c4c..16055bbe 100644 --- a/src/main/java/coffeemeet/server/user/domain/OAuthInfo.java +++ b/src/main/java/coffeemeet/server/user/domain/OAuthInfo.java @@ -3,13 +3,14 @@ import jakarta.persistence.Embeddable; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; +import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; import org.springframework.util.StringUtils; @Getter @Embeddable -@NoArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class OAuthInfo { @Enumerated(value = EnumType.STRING) diff --git a/src/main/java/coffeemeet/server/user/domain/Profile.java b/src/main/java/coffeemeet/server/user/domain/Profile.java index 10aaa25a..97b82806 100644 --- a/src/main/java/coffeemeet/server/user/domain/Profile.java +++ b/src/main/java/coffeemeet/server/user/domain/Profile.java @@ -3,6 +3,7 @@ import jakarta.persistence.Column; import jakarta.persistence.Embeddable; import jakarta.persistence.Embedded; +import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -10,7 +11,7 @@ @Getter @Embeddable -@NoArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class Profile { private static final int NICKNAME_MAX_LENGTH = 20; diff --git a/src/main/java/coffeemeet/server/user/domain/ReportInfo.java b/src/main/java/coffeemeet/server/user/domain/ReportInfo.java index cee774c8..1bf0c209 100644 --- a/src/main/java/coffeemeet/server/user/domain/ReportInfo.java +++ b/src/main/java/coffeemeet/server/user/domain/ReportInfo.java @@ -3,15 +3,16 @@ import jakarta.persistence.Column; import jakarta.persistence.Embeddable; import java.time.LocalDateTime; +import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @Getter @Embeddable -@NoArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class ReportInfo { - private static final int REPORT_COUNT_MIN_LENGTH = 0; + private static final int REPORT_MIN_COUNT = 0; @Column(nullable = false) private int reportedCount; @@ -27,13 +28,13 @@ public ReportInfo(int reportedCount, LocalDateTime sanctionPeriod) { } private void validateReportedCount(int reportedCount) { - if (reportedCount < 0) { + if (reportedCount < REPORT_MIN_COUNT) { throw new IllegalArgumentException("올바르지 않은 신고횟수입니다."); } } private void validateSanctionPeriod(LocalDateTime sanctionPeriod) { - if (sanctionPeriod != null) { + if (sanctionPeriod == null) { throw new IllegalArgumentException("올바르지 않은 제재 기간입니다."); } } diff --git a/src/main/java/coffeemeet/server/user/domain/User.java b/src/main/java/coffeemeet/server/user/domain/User.java index b7e9f0f5..811aa3d9 100644 --- a/src/main/java/coffeemeet/server/user/domain/User.java +++ b/src/main/java/coffeemeet/server/user/domain/User.java @@ -1,6 +1,7 @@ package coffeemeet.server.user.domain; import coffeemeet.server.chatting_room.domain.ChattingRoom; +import coffeemeet.server.common.entity.AdvancedBaseEntity; import jakarta.persistence.Column; import jakarta.persistence.Embedded; import jakarta.persistence.Entity; @@ -20,7 +21,7 @@ @Table(name = "users") @Where(clause = "is_deleted = false") @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class User { +public class User extends AdvancedBaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -57,4 +58,8 @@ public User( this.profile = profile; } + public void updateBusinessCardUrl(String newBusinessCardUrl) { + certification.updateBusinessCardUrl(newBusinessCardUrl); + } + } diff --git a/src/main/java/coffeemeet/server/user/service/UserService.java b/src/main/java/coffeemeet/server/user/service/UserService.java new file mode 100644 index 00000000..66625ce2 --- /dev/null +++ b/src/main/java/coffeemeet/server/user/service/UserService.java @@ -0,0 +1,22 @@ +package coffeemeet.server.user.service; + +import coffeemeet.server.user.domain.User; +import coffeemeet.server.user.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class UserService { + + private final UserRepository userRepository; + + @Transactional + public void updateBusinessCardUrl(Long userId, String businessCardUrl) { + User user = userRepository.findById(userId) + .orElseThrow(IllegalArgumentException::new); + user.updateBusinessCardUrl(businessCardUrl); + } + +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 8304cf69..f8f17edb 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -36,4 +36,16 @@ logging: hibernate: orm: jdbc: - bind: trace \ No newline at end of file + bind: trace +cloud: + aws: + s3: + bucket: ${CLOUD_AWS_S3_BUCKET_NAME} + credentials: + access-key: ${CLOUD_AWS_CREDENTIALS_ACCESS_KEY} + secret-key: ${CLOUD_AWS_CREDENTIALS_SECRET_KEY} + region: + static: ${CLOUD_AWS_REGION_STATIC} + auto: false + stack: + auto: false