From 783cf33bab0f66e323d86425895558877bd509af Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Wed, 6 Dec 2023 14:17:19 +0900 Subject: [PATCH 1/8] =?UTF-8?q?feat:=20S3=E4=BB=A5=E5=A4=96=E3=81=AB?= =?UTF-8?q?=E3=82=82=E3=83=A1=E3=83=87=E3=82=A3=E3=82=A2=E3=82=92=E4=BF=9D?= =?UTF-8?q?=E5=AD=98=E3=81=A7=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hideout/application/config/AwsConfig.kt | 2 +- .../application/config/SpringConfig.kt | 13 +++-- .../media/LocalFileSystemMediaDataStore.kt | 58 +++++++++++++++++++ .../core/service/media/S3MediaDataStore.kt | 32 +++++----- 4 files changed, 84 insertions(+), 21 deletions(-) create mode 100644 src/main/kotlin/dev/usbharu/hideout/core/service/media/LocalFileSystemMediaDataStore.kt diff --git a/src/main/kotlin/dev/usbharu/hideout/application/config/AwsConfig.kt b/src/main/kotlin/dev/usbharu/hideout/application/config/AwsConfig.kt index 67820efd..45db658d 100644 --- a/src/main/kotlin/dev/usbharu/hideout/application/config/AwsConfig.kt +++ b/src/main/kotlin/dev/usbharu/hideout/application/config/AwsConfig.kt @@ -10,7 +10,7 @@ import java.net.URI @Configuration class AwsConfig { @Bean - fun s3Client(awsConfig: StorageConfig): S3Client { + fun s3Client(awsConfig: S3StorageConfig): S3Client { return S3Client.builder() .endpointOverride(URI.create(awsConfig.endpoint)) .region(Region.of(awsConfig.region)) diff --git a/src/main/kotlin/dev/usbharu/hideout/application/config/SpringConfig.kt b/src/main/kotlin/dev/usbharu/hideout/application/config/SpringConfig.kt index 89275999..dfbd3c27 100644 --- a/src/main/kotlin/dev/usbharu/hideout/application/config/SpringConfig.kt +++ b/src/main/kotlin/dev/usbharu/hideout/application/config/SpringConfig.kt @@ -14,7 +14,7 @@ class SpringConfig { lateinit var config: ApplicationConfig @Autowired - lateinit var storageConfig: StorageConfig + lateinit var s3StorageConfig: S3StorageConfig @Bean fun requestLoggingFilter(): CommonsRequestLoggingFilter { @@ -33,9 +33,8 @@ data class ApplicationConfig( val url: URL ) -@ConfigurationProperties("hideout.storage") -data class StorageConfig( - val useS3: Boolean, +@ConfigurationProperties("hideout.storage.s3") +data class S3StorageConfig( val endpoint: String, val publicUrl: String, val bucket: String, @@ -44,6 +43,12 @@ data class StorageConfig( val secretKey: String ) +@ConfigurationProperties("hideout.storage.local") +data class LocalStorageConfig( + val path: String, + val publicUrl: String? +) + @ConfigurationProperties("hideout.character-limit") data class CharacterLimit( val general: General = General(), diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/media/LocalFileSystemMediaDataStore.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/media/LocalFileSystemMediaDataStore.kt new file mode 100644 index 00000000..4c1e4e45 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/media/LocalFileSystemMediaDataStore.kt @@ -0,0 +1,58 @@ +package dev.usbharu.hideout.core.service.media + +import dev.usbharu.hideout.application.config.ApplicationConfig +import dev.usbharu.hideout.application.config.LocalStorageConfig +import org.springframework.stereotype.Service +import java.nio.file.Path +import kotlin.io.path.copyTo +import kotlin.io.path.deleteIfExists +import kotlin.io.path.outputStream + +@Service +class LocalFileSystemMediaDataStore( + applicationConfig: ApplicationConfig, + localStorageConfig: LocalStorageConfig +) : MediaDataStore { + + private val savePath: Path = Path.of(localStorageConfig.path) + + private val publicUrl = localStorageConfig.publicUrl ?: "${applicationConfig.url}/files/" + + override suspend fun save(dataMediaSave: MediaSave): SavedMedia { + val fileSavePath = buildSavePath(savePath, dataMediaSave.name) + val thumbnailSavePath = buildSavePath(savePath, "thumbnail-" + dataMediaSave.name) + + + + dataMediaSave.thumbnailInputStream?.inputStream()?.buffered() + ?.transferTo(thumbnailSavePath.outputStream().buffered()) + dataMediaSave.fileInputStream.inputStream().buffered().transferTo(fileSavePath.outputStream().buffered()) + + return SuccessSavedMedia( + dataMediaSave.name, + publicUrl + dataMediaSave.name, + publicUrl + "thumbnail-" + dataMediaSave.name + ) + } + + override suspend fun save(dataSaveRequest: MediaSaveRequest): SavedMedia { + val fileSavePath = buildSavePath(savePath, dataSaveRequest.name) + val thumbnailSavePath = buildSavePath(savePath, "thumbnail-" + dataSaveRequest.name) + + dataSaveRequest.filePath.copyTo(fileSavePath) + dataSaveRequest.thumbnailPath?.copyTo(thumbnailSavePath) + + return SuccessSavedMedia( + dataSaveRequest.name, + publicUrl + dataSaveRequest.name, + publicUrl + "thumbnail-" + dataSaveRequest.name + ) + } + + override suspend fun delete(id: String) { + buildSavePath(savePath, id).deleteIfExists() + buildSavePath(savePath, "thumbnail-$id").deleteIfExists() + } + + private fun buildSavePath(savePath: Path, name: String): Path = savePath.resolve(name) +} diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/media/S3MediaDataStore.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/media/S3MediaDataStore.kt index 4377fdc0..4220162c 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/service/media/S3MediaDataStore.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/media/S3MediaDataStore.kt @@ -1,6 +1,6 @@ package dev.usbharu.hideout.core.service.media -import dev.usbharu.hideout.application.config.StorageConfig +import dev.usbharu.hideout.application.config.S3StorageConfig import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll @@ -14,16 +14,16 @@ import software.amazon.awssdk.services.s3.model.GetUrlRequest import software.amazon.awssdk.services.s3.model.PutObjectRequest @Service -class S3MediaDataStore(private val s3Client: S3Client, private val storageConfig: StorageConfig) : MediaDataStore { +class S3MediaDataStore(private val s3Client: S3Client, private val s3StorageConfig: S3StorageConfig) : MediaDataStore { override suspend fun save(dataMediaSave: MediaSave): SavedMedia { val fileUploadRequest = PutObjectRequest.builder() - .bucket(storageConfig.bucket) + .bucket(s3StorageConfig.bucket) .key(dataMediaSave.name) .build() val thumbnailKey = "thumbnail-${dataMediaSave.name}" val thumbnailUploadRequest = PutObjectRequest.builder() - .bucket(storageConfig.bucket) + .bucket(s3StorageConfig.bucket) .key(thumbnailKey) .build() @@ -36,7 +36,7 @@ class S3MediaDataStore(private val s3Client: S3Client, private val storageConfig RequestBody.fromBytes(dataMediaSave.thumbnailInputStream) ) s3Client.utilities() - .getUrl(GetUrlRequest.builder().bucket(storageConfig.bucket).key(thumbnailKey).build()) + .getUrl(GetUrlRequest.builder().bucket(s3StorageConfig.bucket).key(thumbnailKey).build()) } else { null } @@ -44,14 +44,14 @@ class S3MediaDataStore(private val s3Client: S3Client, private val storageConfig async { s3Client.putObject(fileUploadRequest, RequestBody.fromBytes(dataMediaSave.fileInputStream)) s3Client.utilities() - .getUrl(GetUrlRequest.builder().bucket(storageConfig.bucket).key(dataMediaSave.name).build()) + .getUrl(GetUrlRequest.builder().bucket(s3StorageConfig.bucket).key(dataMediaSave.name).build()) } ) } return SuccessSavedMedia( name = dataMediaSave.name, - url = "${storageConfig.publicUrl}/${storageConfig.bucket}/${dataMediaSave.name}", - thumbnailUrl = "${storageConfig.publicUrl}/${storageConfig.bucket}/$thumbnailKey" + url = "${s3StorageConfig.publicUrl}/${s3StorageConfig.bucket}/${dataMediaSave.name}", + thumbnailUrl = "${s3StorageConfig.publicUrl}/${s3StorageConfig.bucket}/$thumbnailKey" ) } @@ -59,19 +59,19 @@ class S3MediaDataStore(private val s3Client: S3Client, private val storageConfig logger.info("MEDIA upload. {}", dataSaveRequest.name) val fileUploadRequest = PutObjectRequest.builder() - .bucket(storageConfig.bucket) + .bucket(s3StorageConfig.bucket) .key(dataSaveRequest.name) .build() - logger.info("MEDIA upload. bucket: {} key: {}", storageConfig.bucket, dataSaveRequest.name) + logger.info("MEDIA upload. bucket: {} key: {}", s3StorageConfig.bucket, dataSaveRequest.name) val thumbnailKey = "thumbnail-${dataSaveRequest.name}" val thumbnailUploadRequest = PutObjectRequest.builder() - .bucket(storageConfig.bucket) + .bucket(s3StorageConfig.bucket) .key(thumbnailKey) .build() - logger.info("MEDIA upload. bucket: {} key: {}", storageConfig.bucket, thumbnailKey) + logger.info("MEDIA upload. bucket: {} key: {}", s3StorageConfig.bucket, thumbnailKey) withContext(Dispatchers.IO) { awaitAll( @@ -92,8 +92,8 @@ class S3MediaDataStore(private val s3Client: S3Client, private val storageConfig } val successSavedMedia = SuccessSavedMedia( name = dataSaveRequest.name, - url = "${storageConfig.publicUrl}/${storageConfig.bucket}/${dataSaveRequest.name}", - thumbnailUrl = "${storageConfig.publicUrl}/${storageConfig.bucket}/$thumbnailKey" + url = "${s3StorageConfig.publicUrl}/${s3StorageConfig.bucket}/${dataSaveRequest.name}", + thumbnailUrl = "${s3StorageConfig.publicUrl}/${s3StorageConfig.bucket}/$thumbnailKey" ) logger.info("SUCCESS Media upload. {}", dataSaveRequest.name) @@ -108,9 +108,9 @@ class S3MediaDataStore(private val s3Client: S3Client, private val storageConfig } override suspend fun delete(id: String) { - val fileDeleteRequest = DeleteObjectRequest.builder().bucket(storageConfig.bucket).key(id).build() + val fileDeleteRequest = DeleteObjectRequest.builder().bucket(s3StorageConfig.bucket).key(id).build() val thumbnailDeleteRequest = - DeleteObjectRequest.builder().bucket(storageConfig.bucket).key("thumbnail-$id").build() + DeleteObjectRequest.builder().bucket(s3StorageConfig.bucket).key("thumbnail-$id").build() s3Client.deleteObject(fileDeleteRequest) s3Client.deleteObject(thumbnailDeleteRequest) } From eb6e0622dcc8e2388acc12c6fa6bd9abf36eac8a Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Wed, 6 Dec 2023 14:24:36 +0900 Subject: [PATCH 2/8] =?UTF-8?q?doc:=20LocalFileSystemMediaDataStore.kt?= =?UTF-8?q?=E3=81=AB=E3=83=89=E3=82=AD=E3=83=A5=E3=83=A1=E3=83=B3=E3=83=88?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../media/LocalFileSystemMediaDataStore.kt | 14 +++++++++++ .../core/service/media/MediaDataStore.kt | 24 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/media/LocalFileSystemMediaDataStore.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/media/LocalFileSystemMediaDataStore.kt index 4c1e4e45..0e0ef8cb 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/service/media/LocalFileSystemMediaDataStore.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/media/LocalFileSystemMediaDataStore.kt @@ -9,6 +9,15 @@ import kotlin.io.path.deleteIfExists import kotlin.io.path.outputStream @Service +/** + * ローカルファイルシステムにメディアを保存します + * + * @constructor + * ApplicationConfigとLocalStorageConfigをもとに作成 + * + * @param applicationConfig ApplicationConfig + * @param localStorageConfig LocalStorageConfig + */ class LocalFileSystemMediaDataStore( applicationConfig: ApplicationConfig, localStorageConfig: LocalStorageConfig @@ -49,6 +58,11 @@ class LocalFileSystemMediaDataStore( ) } + /** + * メディアを削除します。サムネイルも削除されます。 + * + * @param id 削除するメディアのid [SuccessSavedMedia.name]を指定します。 + */ override suspend fun delete(id: String) { buildSavePath(savePath, id).deleteIfExists() buildSavePath(savePath, "thumbnail-$id").deleteIfExists() diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/media/MediaDataStore.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/media/MediaDataStore.kt index 03ee3e9f..478ddc50 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/service/media/MediaDataStore.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/media/MediaDataStore.kt @@ -1,7 +1,31 @@ package dev.usbharu.hideout.core.service.media +/** + * メディアを保存するインタフェース + * + */ interface MediaDataStore { + /** + * InputStreamを使用してメディアを保存します + * + * @param dataMediaSave FileとThumbnailのinputStream + * @return 保存されたメディア + */ suspend fun save(dataMediaSave: MediaSave): SavedMedia + + /** + * 一時ファイルのパスを使用してメディアを保存します + * + * @param dataSaveRequest FileとThumbnailのパス + * @return 保存されたメディア + */ suspend fun save(dataSaveRequest: MediaSaveRequest): SavedMedia + + /** + * メディアを削除します + * 実装はサムネイル、メタデータなども削除するべきです。 + * + * @param id 削除するメディアのid 通常は[SuccessSavedMedia.name]を指定します。 + */ suspend fun delete(id: String) } From d70f507d9eb245350cb06abc55fb01e04e293613 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Wed, 6 Dec 2023 14:29:54 +0900 Subject: [PATCH 3/8] =?UTF-8?q?doc:=20LocalStorageConfig=E3=81=AB=E3=83=89?= =?UTF-8?q?=E3=82=AD=E3=83=A5=E3=83=A1=E3=83=B3=E3=83=88=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dev/usbharu/hideout/application/config/SpringConfig.kt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/kotlin/dev/usbharu/hideout/application/config/SpringConfig.kt b/src/main/kotlin/dev/usbharu/hideout/application/config/SpringConfig.kt index dfbd3c27..9dfe2f5e 100644 --- a/src/main/kotlin/dev/usbharu/hideout/application/config/SpringConfig.kt +++ b/src/main/kotlin/dev/usbharu/hideout/application/config/SpringConfig.kt @@ -43,6 +43,13 @@ data class S3StorageConfig( val secretKey: String ) + +/** + * メディアの保存にローカルファイルシステムを使用する際のコンフィグ + * + * @property path フォゾンする場所へのパス。 /から始めると絶対パスとなります。 + * @property publicUrl 公開用URL 省略可能 指定するとHideoutがファイルを配信しなくなります。 + */ @ConfigurationProperties("hideout.storage.local") data class LocalStorageConfig( val path: String, From 14228467c33f3f2a1a572ec323a1e16f4f6ff3cd Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Wed, 6 Dec 2023 14:39:31 +0900 Subject: [PATCH 4/8] =?UTF-8?q?refactor:=20LocalFileSystemMediaDataStore.k?= =?UTF-8?q?t=E3=81=AB=E3=83=AD=E3=82=B0=E3=81=A8=E3=82=A8=E3=83=A9?= =?UTF-8?q?=E3=83=BC=E3=83=8F=E3=83=B3=E3=83=89=E3=83=AA=E3=83=B3=E3=82=B0?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../media/LocalFileSystemMediaDataStore.kt | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/media/LocalFileSystemMediaDataStore.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/media/LocalFileSystemMediaDataStore.kt index 0e0ef8cb..3102fea5 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/service/media/LocalFileSystemMediaDataStore.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/media/LocalFileSystemMediaDataStore.kt @@ -2,6 +2,7 @@ package dev.usbharu.hideout.core.service.media import dev.usbharu.hideout.application.config.ApplicationConfig import dev.usbharu.hideout.application.config.LocalStorageConfig +import org.slf4j.LoggerFactory import org.springframework.stereotype.Service import java.nio.file.Path import kotlin.io.path.copyTo @@ -45,12 +46,22 @@ class LocalFileSystemMediaDataStore( } override suspend fun save(dataSaveRequest: MediaSaveRequest): SavedMedia { + logger.info("START Media upload. {}", dataSaveRequest.name) val fileSavePath = buildSavePath(savePath, dataSaveRequest.name) val thumbnailSavePath = buildSavePath(savePath, "thumbnail-" + dataSaveRequest.name) - dataSaveRequest.filePath.copyTo(fileSavePath) - dataSaveRequest.thumbnailPath?.copyTo(thumbnailSavePath) + val fileSavePathString = fileSavePath.toAbsolutePath().toString() + logger.info("MEDIA save. path: {}", fileSavePathString) + try { + dataSaveRequest.filePath.copyTo(fileSavePath) + dataSaveRequest.thumbnailPath?.copyTo(thumbnailSavePath) + } catch (e: Exception) { + logger.warn("FAILED to Save the media.", e) + return FaildSavedMedia("FAILED to Save the media.", "Failed copy to path: $fileSavePathString", e) + } + + logger.info("SUCCESS Media upload. {}", dataSaveRequest.name) return SuccessSavedMedia( dataSaveRequest.name, publicUrl + dataSaveRequest.name, @@ -64,9 +75,18 @@ class LocalFileSystemMediaDataStore( * @param id 削除するメディアのid [SuccessSavedMedia.name]を指定します。 */ override suspend fun delete(id: String) { - buildSavePath(savePath, id).deleteIfExists() - buildSavePath(savePath, "thumbnail-$id").deleteIfExists() + logger.info("START Media delete. id: {}", id) + try { + buildSavePath(savePath, id).deleteIfExists() + buildSavePath(savePath, "thumbnail-$id").deleteIfExists() + } catch (e: Exception) { + logger.warn("FAILED Media delete. id: {}", id, e) + } } private fun buildSavePath(savePath: Path, name: String): Path = savePath.resolve(name) + + companion object { + private val logger = LoggerFactory.getLogger(LocalFileSystemMediaDataStore::class.java) + } } From 70794e9537d8fa9e6833391e3061ceba56a40b2d Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Wed, 6 Dec 2023 15:31:50 +0900 Subject: [PATCH 5/8] =?UTF-8?q?fix:=20=E3=83=AA=E3=82=BD=E3=83=BC=E3=82=B9?= =?UTF-8?q?=E3=81=AE=E3=82=AF=E3=83=AD=E3=83=BC=E3=82=BA=E3=82=92=E8=A1=8C?= =?UTF-8?q?=E3=81=86=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../media/LocalFileSystemMediaDataStore.kt | 39 +++++++++++++------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/media/LocalFileSystemMediaDataStore.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/media/LocalFileSystemMediaDataStore.kt index 3102fea5..420e82bf 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/service/media/LocalFileSystemMediaDataStore.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/media/LocalFileSystemMediaDataStore.kt @@ -5,7 +5,9 @@ import dev.usbharu.hideout.application.config.LocalStorageConfig import org.slf4j.LoggerFactory import org.springframework.stereotype.Service import java.nio.file.Path +import java.nio.file.StandardOpenOption import kotlin.io.path.copyTo +import kotlin.io.path.createDirectories import kotlin.io.path.deleteIfExists import kotlin.io.path.outputStream @@ -20,28 +22,43 @@ import kotlin.io.path.outputStream * @param localStorageConfig LocalStorageConfig */ class LocalFileSystemMediaDataStore( - applicationConfig: ApplicationConfig, - localStorageConfig: LocalStorageConfig + applicationConfig: ApplicationConfig, localStorageConfig: LocalStorageConfig ) : MediaDataStore { - private val savePath: Path = Path.of(localStorageConfig.path) + private val savePath: Path = Path.of(localStorageConfig.path).toAbsolutePath() private val publicUrl = localStorageConfig.publicUrl ?: "${applicationConfig.url}/files/" + init { + savePath.createDirectories() + } + override suspend fun save(dataMediaSave: MediaSave): SavedMedia { val fileSavePath = buildSavePath(savePath, dataMediaSave.name) val thumbnailSavePath = buildSavePath(savePath, "thumbnail-" + dataMediaSave.name) + dataMediaSave.thumbnailInputStream?.inputStream()?.use { + it.buffered().use { bufferedInputStream -> + thumbnailSavePath.outputStream(StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE) + .use { outputStream -> + outputStream.buffered().use { + bufferedInputStream.transferTo(it) + } + } + } + } - dataMediaSave.thumbnailInputStream?.inputStream()?.buffered() - ?.transferTo(thumbnailSavePath.outputStream().buffered()) - dataMediaSave.fileInputStream.inputStream().buffered().transferTo(fileSavePath.outputStream().buffered()) + + dataMediaSave.fileInputStream.inputStream().use { + it.buffered().use { bufferedInputStream -> + fileSavePath.outputStream(StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE) + .use { outputStream -> outputStream.buffered().use { bufferedInputStream.transferTo(it) } } + } + } return SuccessSavedMedia( - dataMediaSave.name, - publicUrl + dataMediaSave.name, - publicUrl + "thumbnail-" + dataMediaSave.name + dataMediaSave.name, publicUrl + dataMediaSave.name, publicUrl + "thumbnail-" + dataMediaSave.name ) } @@ -63,9 +80,7 @@ class LocalFileSystemMediaDataStore( logger.info("SUCCESS Media upload. {}", dataSaveRequest.name) return SuccessSavedMedia( - dataSaveRequest.name, - publicUrl + dataSaveRequest.name, - publicUrl + "thumbnail-" + dataSaveRequest.name + dataSaveRequest.name, publicUrl + dataSaveRequest.name, publicUrl + "thumbnail-" + dataSaveRequest.name ) } From 76f883913a2a5c539dc7d1d4bdd99aaa203e973c Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Wed, 6 Dec 2023 15:32:15 +0900 Subject: [PATCH 6/8] =?UTF-8?q?test:=20LocalFileSystemMediaDataStore.kt?= =?UTF-8?q?=E3=81=AE=E3=83=86=E3=82=B9=E3=83=88=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + .../LocalFileSystemMediaDataStoreTest.kt | 121 ++++++++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 src/test/kotlin/dev/usbharu/hideout/core/service/media/LocalFileSystemMediaDataStoreTest.kt diff --git a/.gitignore b/.gitignore index 0d7dbff3..1f876f7f 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,5 @@ out/ /tomcat/ /tomcat-e2e/ /e2eTest.log +/files/test-media-29599b059-06e1-49eb-91b8-1405244e57ce.png +/files/thumbnail-test-media-29599b059-06e1-49eb-91b8-1405244e57ce.png diff --git a/src/test/kotlin/dev/usbharu/hideout/core/service/media/LocalFileSystemMediaDataStoreTest.kt b/src/test/kotlin/dev/usbharu/hideout/core/service/media/LocalFileSystemMediaDataStoreTest.kt new file mode 100644 index 00000000..e2854912 --- /dev/null +++ b/src/test/kotlin/dev/usbharu/hideout/core/service/media/LocalFileSystemMediaDataStoreTest.kt @@ -0,0 +1,121 @@ +package dev.usbharu.hideout.core.service.media + +import dev.usbharu.hideout.application.config.ApplicationConfig +import dev.usbharu.hideout.application.config.LocalStorageConfig +import kotlinx.coroutines.test.runTest +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import java.net.URL +import java.nio.file.Path +import java.util.* +import kotlin.io.path.readBytes +import kotlin.io.path.toPath + +class LocalFileSystemMediaDataStoreTest { + + private val path = String.javaClass.classLoader.getResource("400x400.png")?.toURI()?.toPath()!! + + @Test + fun `save inputStreamを使用して正常に保存できる`() = runTest { + val applicationConfig = ApplicationConfig(URL("https://example.com")) + val storageConfig = LocalStorageConfig("files", null) + + val localFileSystemMediaDataStore = LocalFileSystemMediaDataStore(applicationConfig, storageConfig) + + val fileInputStream = path.readBytes() + + assertThat(fileInputStream.size).isNotEqualTo(0) + + val mediaSave = MediaSave( + "test-media-1${UUID.randomUUID()}.png", + "", + fileInputStream, + fileInputStream + ) + + val save = localFileSystemMediaDataStore.save(mediaSave) + + assertThat(save).isInstanceOf(SuccessSavedMedia::class.java) + + save as SuccessSavedMedia + + assertThat(Path.of("files").toAbsolutePath().resolve(save.name)) + .exists() + .hasSize(fileInputStream.size.toLong()) + assertThat(Path.of("files").toAbsolutePath().resolve("thumbnail-" + save.name)) + .exists() + .hasSize(fileInputStream.size.toLong()) + } + + @Test + fun 一時ファイルを使用して正常に保存できる() = runTest { + val applicationConfig = ApplicationConfig(URL("https://example.com")) + val storageConfig = LocalStorageConfig("files", null) + + val localFileSystemMediaDataStore = LocalFileSystemMediaDataStore(applicationConfig, storageConfig) + + val fileInputStream = path.readBytes() + + assertThat(fileInputStream.size).isNotEqualTo(0) + + val saveRequest = MediaSaveRequest( + "test-media-2${UUID.randomUUID()}.png", + "", + path, + path + ) + + val save = localFileSystemMediaDataStore.save(saveRequest) + + assertThat(save).isInstanceOf(SuccessSavedMedia::class.java) + + save as SuccessSavedMedia + + assertThat(Path.of("files").toAbsolutePath().resolve(save.name)) + .exists() + .hasSize(fileInputStream.size.toLong()) + assertThat(Path.of("files").toAbsolutePath().resolve("thumbnail-" + save.name)) + .exists() + .hasSize(fileInputStream.size.toLong()) + } + + @Test + fun idを使用して削除できる() = runTest { + val applicationConfig = ApplicationConfig(URL("https://example.com")) + val storageConfig = LocalStorageConfig("files", null) + + val localFileSystemMediaDataStore = LocalFileSystemMediaDataStore(applicationConfig, storageConfig) + + val fileInputStream = path.readBytes() + + assertThat(fileInputStream.size).isNotEqualTo(0) + + val saveRequest = MediaSaveRequest( + "test-media-2${UUID.randomUUID()}.png", + "", + path, + path + ) + + val save = localFileSystemMediaDataStore.save(saveRequest) + + assertThat(save).isInstanceOf(SuccessSavedMedia::class.java) + + save as SuccessSavedMedia + + assertThat(Path.of("files").toAbsolutePath().resolve(save.name)) + .exists() + .hasSize(fileInputStream.size.toLong()) + assertThat(Path.of("files").toAbsolutePath().resolve("thumbnail-" + save.name)) + .exists() + .hasSize(fileInputStream.size.toLong()) + + + localFileSystemMediaDataStore.delete(save.name) + + assertThat(Path.of("files").toAbsolutePath().resolve(save.name)) + .doesNotExist() + assertThat(Path.of("files").toAbsolutePath().resolve("thumbnail-" + save.name)) + .doesNotExist() + } +} From 065b9bb01fd95592c25fb663c90323f11c0f5400 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Wed, 6 Dec 2023 16:15:06 +0900 Subject: [PATCH 7/8] =?UTF-8?q?chore:=20gitignore=E3=82=92=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 1f876f7f..84c07d25 100644 --- a/.gitignore +++ b/.gitignore @@ -42,5 +42,4 @@ out/ /tomcat/ /tomcat-e2e/ /e2eTest.log -/files/test-media-29599b059-06e1-49eb-91b8-1405244e57ce.png -/files/thumbnail-test-media-29599b059-06e1-49eb-91b8-1405244e57ce.png +/files/ From ca5ad234502fbf803ddb1b1f938db0110601d50d Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Wed, 6 Dec 2023 16:15:38 +0900 Subject: [PATCH 8/8] =?UTF-8?q?feat:=20Spring=20Configuration=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/e2eTest/resources/application.yml | 8 +------- src/intTest/resources/application.yml | 8 +------- .../dev/usbharu/hideout/application/config/AwsConfig.kt | 2 ++ .../usbharu/hideout/application/config/SpringConfig.kt | 8 ++++---- .../core/service/media/LocalFileSystemMediaDataStore.kt | 2 ++ .../hideout/core/service/media/S3MediaDataStore.kt | 2 ++ 6 files changed, 12 insertions(+), 18 deletions(-) diff --git a/src/e2eTest/resources/application.yml b/src/e2eTest/resources/application.yml index 1013de15..330660e2 100644 --- a/src/e2eTest/resources/application.yml +++ b/src/e2eTest/resources/application.yml @@ -8,13 +8,7 @@ hideout: private-key: "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC7VJTUt9Us8cKjMzEfYyjiWA4R4/M2bS1GB4t7NXp98C3SC6dVMvDuictGeurT8jNbvJZHtCSuYEvuNMoSfm76oqFvAp8Gy0iz5sxjZmSnXyCdPEovGhLa0VzMaQ8s+CLOyS56YyCFGeJZqgtzJ6GR3eqoYSW9b9UMvkBpZODSctWSNGj3P7jRFDO5VoTwCQAWbFnOjDfH5Ulgp2PKSQnSJP3AJLQNFNe7br1XbrhV//eO+t51mIpGSDCUv3E0DDFcWDTH9cXDTTlRZVEiR2BwpZOOkE/Z0/BVnhZYL71oZV34bKfWjQIt6V/isSMahdsAASACp4ZTGtwiVuNd9tybAgMBAAECggEBAKTmjaS6tkK8BlPXClTQ2vpz/N6uxDeS35mXpqasqskVlaAidgg/sWqpjXDbXr93otIMLlWsM+X0CqMDgSXKejLS2jx4GDjI1ZTXg++0AMJ8sJ74pWzVDOfmCEQ/7wXs3+cbnXhKriO8Z036q92Qc1+N87SI38nkGa0ABH9CN83HmQqt4fB7UdHzuIRe/me2PGhIq5ZBzj6h3BpoPGzEP+x3l9YmK8t/1cN0pqI+dQwYdgfGjackLu/2qH80MCF7IyQaseZUOJyKrCLtSD/Iixv/hzDEUPfOCjFDgTpzf3cwta8+oE4wHCo1iI1/4TlPkwmXx4qSXtmw4aQPz7IDQvECgYEA8KNThCO2gsC2I9PQDM/8Cw0O983WCDY+oi+7JPiNAJwv5DYBqEZB1QYdj06YD16XlC/HAZMsMku1na2TN0driwenQQWzoev3g2S7gRDoS/FCJSI3jJ+kjgtaA7Qmzlgk1TxODN+G1H91HW7t0l7VnL27IWyYo2qRRK3jzxqUiPUCgYEAx0oQs2reBQGMVZnApD1jeq7n4MvNLcPvt8b/eU9iUv6Y4Mj0Suo/AU8lYZXm8ubbqAlwz2VSVunD2tOplHyMUrtCtObAfVDUAhCndKaA9gApgfb3xw1IKbuQ1u4IF1FJl3VtumfQn//LiH1B3rXhcdyo3/vIttEk48RakUKClU8CgYEAzV7W3COOlDDcQd935DdtKBFRAPRPAlspQUnzMi5eSHMD/ISLDY5IiQHbIH83D4bvXq0X7qQoSBSNP7Dvv3HYuqMhf0DaegrlBuJllFVVq9qPVRnKxt1Il2HgxOBvbhOT+9in1BzA+YJ99UzC85O0Qz06A+CmtHEy4aZ2kj5hHjECgYEAmNS4+A8Fkss8Js1RieK2LniBxMgmYml3pfVLKGnzmng7H2+cwPLhPIzIuwytXywh2bzbsYEfYx3EoEVgMEpPhoarQnYPukrJO4gwE2o5Te6T5mJSZGlQJQj9q4ZB2Dfzet6INsK0oG8XVGXSpQvQh3RUYekCZQkBBFcpqWpbIEsCgYAnM3DQf3FJoSnXaMhrVBIovic5l0xFkEHskAjFTevO86Fsz1C2aSeRKSqGFoOQ0tmJzBEs1R6KqnHInicDTQrKhArgLXX4v3CddjfTRJkFWDbE/CkvKZNOrcf1nhaGCPspRJj2KUkj1Fhl9Cncdn/RsYEONbwQSjIfMPkvxF+8HQ==" public-key: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyehkd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdgcKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbcmwIDAQAB" storage: - use-s3: true - endpoint: "http://localhost:8082/test-hideout" - public-url: "http://localhost:8082/test-hideout" - bucket: "test-hideout" - region: "auto" - access-key: "" - secret-key: "" + type: local spring: flyway: diff --git a/src/intTest/resources/application.yml b/src/intTest/resources/application.yml index b40fcd91..c73fc1f3 100644 --- a/src/intTest/resources/application.yml +++ b/src/intTest/resources/application.yml @@ -8,13 +8,7 @@ hideout: private-key: "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC7VJTUt9Us8cKjMzEfYyjiWA4R4/M2bS1GB4t7NXp98C3SC6dVMvDuictGeurT8jNbvJZHtCSuYEvuNMoSfm76oqFvAp8Gy0iz5sxjZmSnXyCdPEovGhLa0VzMaQ8s+CLOyS56YyCFGeJZqgtzJ6GR3eqoYSW9b9UMvkBpZODSctWSNGj3P7jRFDO5VoTwCQAWbFnOjDfH5Ulgp2PKSQnSJP3AJLQNFNe7br1XbrhV//eO+t51mIpGSDCUv3E0DDFcWDTH9cXDTTlRZVEiR2BwpZOOkE/Z0/BVnhZYL71oZV34bKfWjQIt6V/isSMahdsAASACp4ZTGtwiVuNd9tybAgMBAAECggEBAKTmjaS6tkK8BlPXClTQ2vpz/N6uxDeS35mXpqasqskVlaAidgg/sWqpjXDbXr93otIMLlWsM+X0CqMDgSXKejLS2jx4GDjI1ZTXg++0AMJ8sJ74pWzVDOfmCEQ/7wXs3+cbnXhKriO8Z036q92Qc1+N87SI38nkGa0ABH9CN83HmQqt4fB7UdHzuIRe/me2PGhIq5ZBzj6h3BpoPGzEP+x3l9YmK8t/1cN0pqI+dQwYdgfGjackLu/2qH80MCF7IyQaseZUOJyKrCLtSD/Iixv/hzDEUPfOCjFDgTpzf3cwta8+oE4wHCo1iI1/4TlPkwmXx4qSXtmw4aQPz7IDQvECgYEA8KNThCO2gsC2I9PQDM/8Cw0O983WCDY+oi+7JPiNAJwv5DYBqEZB1QYdj06YD16XlC/HAZMsMku1na2TN0driwenQQWzoev3g2S7gRDoS/FCJSI3jJ+kjgtaA7Qmzlgk1TxODN+G1H91HW7t0l7VnL27IWyYo2qRRK3jzxqUiPUCgYEAx0oQs2reBQGMVZnApD1jeq7n4MvNLcPvt8b/eU9iUv6Y4Mj0Suo/AU8lYZXm8ubbqAlwz2VSVunD2tOplHyMUrtCtObAfVDUAhCndKaA9gApgfb3xw1IKbuQ1u4IF1FJl3VtumfQn//LiH1B3rXhcdyo3/vIttEk48RakUKClU8CgYEAzV7W3COOlDDcQd935DdtKBFRAPRPAlspQUnzMi5eSHMD/ISLDY5IiQHbIH83D4bvXq0X7qQoSBSNP7Dvv3HYuqMhf0DaegrlBuJllFVVq9qPVRnKxt1Il2HgxOBvbhOT+9in1BzA+YJ99UzC85O0Qz06A+CmtHEy4aZ2kj5hHjECgYEAmNS4+A8Fkss8Js1RieK2LniBxMgmYml3pfVLKGnzmng7H2+cwPLhPIzIuwytXywh2bzbsYEfYx3EoEVgMEpPhoarQnYPukrJO4gwE2o5Te6T5mJSZGlQJQj9q4ZB2Dfzet6INsK0oG8XVGXSpQvQh3RUYekCZQkBBFcpqWpbIEsCgYAnM3DQf3FJoSnXaMhrVBIovic5l0xFkEHskAjFTevO86Fsz1C2aSeRKSqGFoOQ0tmJzBEs1R6KqnHInicDTQrKhArgLXX4v3CddjfTRJkFWDbE/CkvKZNOrcf1nhaGCPspRJj2KUkj1Fhl9Cncdn/RsYEONbwQSjIfMPkvxF+8HQ==" public-key: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyehkd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdgcKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbcmwIDAQAB" storage: - use-s3: true - endpoint: "http://localhost:8082/test-hideout" - public-url: "http://localhost:8082/test-hideout" - bucket: "test-hideout" - region: "auto" - access-key: "" - secret-key: "" + type: local spring: flyway: diff --git a/src/main/kotlin/dev/usbharu/hideout/application/config/AwsConfig.kt b/src/main/kotlin/dev/usbharu/hideout/application/config/AwsConfig.kt index 45db658d..ad40a0bd 100644 --- a/src/main/kotlin/dev/usbharu/hideout/application/config/AwsConfig.kt +++ b/src/main/kotlin/dev/usbharu/hideout/application/config/AwsConfig.kt @@ -1,5 +1,6 @@ package dev.usbharu.hideout.application.config +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import software.amazon.awssdk.auth.credentials.AwsBasicCredentials @@ -10,6 +11,7 @@ import java.net.URI @Configuration class AwsConfig { @Bean + @ConditionalOnProperty("hideout.storage.type", havingValue = "s3") fun s3Client(awsConfig: S3StorageConfig): S3Client { return S3Client.builder() .endpointOverride(URI.create(awsConfig.endpoint)) diff --git a/src/main/kotlin/dev/usbharu/hideout/application/config/SpringConfig.kt b/src/main/kotlin/dev/usbharu/hideout/application/config/SpringConfig.kt index 9dfe2f5e..f3e088ab 100644 --- a/src/main/kotlin/dev/usbharu/hideout/application/config/SpringConfig.kt +++ b/src/main/kotlin/dev/usbharu/hideout/application/config/SpringConfig.kt @@ -1,6 +1,7 @@ package dev.usbharu.hideout.application.config import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty import org.springframework.boot.context.properties.ConfigurationProperties import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration @@ -13,9 +14,6 @@ class SpringConfig { @Autowired lateinit var config: ApplicationConfig - @Autowired - lateinit var s3StorageConfig: S3StorageConfig - @Bean fun requestLoggingFilter(): CommonsRequestLoggingFilter { val loggingFilter = CommonsRequestLoggingFilter() @@ -34,6 +32,7 @@ data class ApplicationConfig( ) @ConfigurationProperties("hideout.storage.s3") +@ConditionalOnProperty("hideout.storage.type", havingValue = "s3") data class S3StorageConfig( val endpoint: String, val publicUrl: String, @@ -51,8 +50,9 @@ data class S3StorageConfig( * @property publicUrl 公開用URL 省略可能 指定するとHideoutがファイルを配信しなくなります。 */ @ConfigurationProperties("hideout.storage.local") +@ConditionalOnProperty("hideout.storage.type", havingValue = "local", matchIfMissing = true) data class LocalStorageConfig( - val path: String, + val path: String = "files", val publicUrl: String? ) diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/media/LocalFileSystemMediaDataStore.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/media/LocalFileSystemMediaDataStore.kt index 420e82bf..a7300081 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/service/media/LocalFileSystemMediaDataStore.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/media/LocalFileSystemMediaDataStore.kt @@ -3,6 +3,7 @@ package dev.usbharu.hideout.core.service.media import dev.usbharu.hideout.application.config.ApplicationConfig import dev.usbharu.hideout.application.config.LocalStorageConfig import org.slf4j.LoggerFactory +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty import org.springframework.stereotype.Service import java.nio.file.Path import java.nio.file.StandardOpenOption @@ -12,6 +13,7 @@ import kotlin.io.path.deleteIfExists import kotlin.io.path.outputStream @Service +@ConditionalOnProperty("hideout.storage.type", havingValue = "local", matchIfMissing = true) /** * ローカルファイルシステムにメディアを保存します * diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/media/S3MediaDataStore.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/media/S3MediaDataStore.kt index 4220162c..8b0f2235 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/service/media/S3MediaDataStore.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/media/S3MediaDataStore.kt @@ -6,6 +6,7 @@ import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.withContext import org.slf4j.LoggerFactory +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty import org.springframework.stereotype.Service import software.amazon.awssdk.core.sync.RequestBody import software.amazon.awssdk.services.s3.S3Client @@ -14,6 +15,7 @@ import software.amazon.awssdk.services.s3.model.GetUrlRequest import software.amazon.awssdk.services.s3.model.PutObjectRequest @Service +@ConditionalOnProperty("hideout.storage.type", havingValue = "s3") class S3MediaDataStore(private val s3Client: S3Client, private val s3StorageConfig: S3StorageConfig) : MediaDataStore { override suspend fun save(dataMediaSave: MediaSave): SavedMedia { val fileUploadRequest = PutObjectRequest.builder()