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) }