feat: ローカルのアップロードの処理の一部が完成

This commit is contained in:
usbharu 2023-10-04 18:41:36 +09:00
parent fa720d8423
commit 374f0144c7
Signed by: usbharu
GPG Key ID: 6556747BF94EEBC8
24 changed files with 258 additions and 54 deletions

View File

@ -10,9 +10,23 @@ class SpringConfig {
@Autowired @Autowired
lateinit var config: ApplicationConfig lateinit var config: ApplicationConfig
@Autowired
lateinit var storageConfig: StorageConfig
} }
@ConfigurationProperties("hideout") @ConfigurationProperties("hideout")
data class ApplicationConfig( data class ApplicationConfig(
val url: URL val url: URL
) )
@ConfigurationProperties("hideout.storage")
data class StorageConfig(
val useS3: Boolean,
val endpoint: String,
val publicUrl: String,
val bucket: String,
val region: String,
val accessKey: String,
val secretKey: String
)

View File

@ -6,5 +6,5 @@ data class MediaSave(
val name: String, val name: String,
val prefix: String, val prefix: String,
val fileInputStream: InputStream, val fileInputStream: InputStream,
val thumbnailInputStream: InputStream val thumbnailInputStream: InputStream?
) )

View File

@ -0,0 +1,8 @@
package dev.usbharu.hideout.domain.model.hideout.dto
enum class FileType {
Image,
Video,
Audio,
Unknown
}

View File

@ -0,0 +1,7 @@
package dev.usbharu.hideout.domain.model.hideout.dto
data class RemoteMedia(
val name: String,
val url: String,
val mediaType: String
)

View File

@ -1,4 +1,4 @@
package dev.usbharu.hideout.service.media package dev.usbharu.hideout.domain.model.hideout.dto
sealed class SavedMedia(val success: Boolean) sealed class SavedMedia(val success: Boolean)

View File

@ -1,6 +1,6 @@
package dev.usbharu.hideout.domain.model.hideout.entity package dev.usbharu.hideout.domain.model.hideout.entity
import dev.usbharu.hideout.service.media.FileTypeDeterminationService import dev.usbharu.hideout.domain.model.hideout.dto.FileType
data class Media( data class Media(
val id: Long, val id: Long,
@ -8,6 +8,6 @@ data class Media(
val url: String, val url: String,
val remoteUrl: String?, val remoteUrl: String?,
val thumbnailUrl: String?, val thumbnailUrl: String?,
val type: FileTypeDeterminationService.FileType, val type: FileType,
val blurHash: String? val blurHash: String?
) )

View File

@ -0,0 +1,14 @@
package dev.usbharu.hideout.exception.media
open class MediaConvertException : MediaException {
constructor() : super()
constructor(message: String?) : super(message)
constructor(message: String?, cause: Throwable?) : super(message, cause)
constructor(cause: Throwable?) : super(cause)
constructor(message: String?, cause: Throwable?, enableSuppression: Boolean, writableStackTrace: Boolean) : super(
message,
cause,
enableSuppression,
writableStackTrace
)
}

View File

@ -0,0 +1,14 @@
package dev.usbharu.hideout.exception.media
open class MediaFileSizeException : MediaException {
constructor() : super()
constructor(message: String?) : super(message)
constructor(message: String?, cause: Throwable?) : super(message, cause)
constructor(cause: Throwable?) : super(cause)
constructor(message: String?, cause: Throwable?, enableSuppression: Boolean, writableStackTrace: Boolean) : super(
message,
cause,
enableSuppression,
writableStackTrace
)
}

View File

@ -0,0 +1,14 @@
package dev.usbharu.hideout.exception.media
class MediaFileSizeIsZeroException : MediaFileSizeException {
constructor() : super()
constructor(message: String?) : super(message)
constructor(message: String?, cause: Throwable?) : super(message, cause)
constructor(cause: Throwable?) : super(cause)
constructor(message: String?, cause: Throwable?, enableSuppression: Boolean, writableStackTrace: Boolean) : super(
message,
cause,
enableSuppression,
writableStackTrace
)
}

View File

@ -1,6 +1,6 @@
package dev.usbharu.hideout.exception.media package dev.usbharu.hideout.exception.media
open class MediaUploadException : MediaException { open class MediaSaveException : MediaException {
constructor() : super() constructor() : super()
constructor(message: String?) : super(message) constructor(message: String?) : super(message)
constructor(message: String?, cause: Throwable?) : super(message, cause) constructor(message: String?, cause: Throwable?) : super(message, cause)

View File

@ -0,0 +1,14 @@
package dev.usbharu.hideout.exception.media
class UnsupportedMediaException : MediaException {
constructor() : super()
constructor(message: String?) : super(message)
constructor(message: String?, cause: Throwable?) : super(message, cause)
constructor(cause: Throwable?) : super(cause)
constructor(message: String?, cause: Throwable?, enableSuppression: Boolean, writableStackTrace: Boolean) : super(
message,
cause,
enableSuppression,
writableStackTrace
)
}

View File

@ -1,9 +1,9 @@
package dev.usbharu.hideout.repository package dev.usbharu.hideout.repository
import dev.usbharu.hideout.domain.model.hideout.dto.FileType
import dev.usbharu.hideout.exception.FailedToGetResourcesException import dev.usbharu.hideout.exception.FailedToGetResourcesException
import dev.usbharu.hideout.service.core.IdGenerateService import dev.usbharu.hideout.service.core.IdGenerateService
import dev.usbharu.hideout.service.media.FileTypeDeterminationService
import dev.usbharu.hideout.util.singleOr import dev.usbharu.hideout.util.singleOr
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
@ -63,7 +63,7 @@ class MediaRepositoryImpl(private val idGenerateService: IdGenerateService) : Me
this[Media.url], this[Media.url],
this[Media.remoteUrl], this[Media.remoteUrl],
this[Media.thumbnailUrl], this[Media.thumbnailUrl],
FileTypeDeterminationService.FileType.values().first { it.ordinal == this[Media.type] }, FileType.values().first { it.ordinal == this[Media.type] },
this[Media.blurhash], this[Media.blurhash],
) )
} }

View File

@ -6,6 +6,9 @@ import dev.usbharu.hideout.service.media.MediaService
class MediaApiServiceImpl(private val mediaService: MediaService) : MediaApiService { class MediaApiServiceImpl(private val mediaService: MediaService) : MediaApiService {
override suspend fun postMedia(media: Media): MediaAttachment { override suspend fun postMedia(media: Media): MediaAttachment {
mediaService.uploadLocalMedia(media) val uploadLocalMedia = mediaService.uploadLocalMedia(media)
return MediaAttachment(
)
} }
} }

View File

@ -1,12 +1,8 @@
package dev.usbharu.hideout.service.media package dev.usbharu.hideout.service.media
import dev.usbharu.hideout.domain.model.hideout.dto.FileType
interface FileTypeDeterminationService { interface FileTypeDeterminationService {
fun fileType(byteArray: ByteArray, filename: String, contentType: String?): FileType fun fileType(byteArray: ByteArray, filename: String, contentType: String?): FileType
enum class FileType {
Image,
Video,
Audio,
Unknown
}
} }

View File

@ -1,5 +1,6 @@
package dev.usbharu.hideout.service.media package dev.usbharu.hideout.service.media
import dev.usbharu.hideout.domain.model.hideout.dto.FileType
import org.springframework.stereotype.Component import org.springframework.stereotype.Component
@Component @Component
@ -8,19 +9,19 @@ class FileTypeDeterminationServiceImpl : FileTypeDeterminationService {
byteArray: ByteArray, byteArray: ByteArray,
filename: String, filename: String,
contentType: String? contentType: String?
): FileTypeDeterminationService.FileType { ): FileType {
if (contentType == null) { if (contentType == null) {
return FileTypeDeterminationService.FileType.Unknown return FileType.Unknown
} }
if (contentType.startsWith("image")) { if (contentType.startsWith("image")) {
return FileTypeDeterminationService.FileType.Image return FileType.Image
} }
if (contentType.startsWith("video")) { if (contentType.startsWith("video")) {
return FileTypeDeterminationService.FileType.Video return FileType.Video
} }
if (contentType.startsWith("audio")) { if (contentType.startsWith("audio")) {
return FileTypeDeterminationService.FileType.Audio return FileType.Audio
} }
return FileTypeDeterminationService.FileType.Unknown return FileType.Unknown
} }
} }

View File

@ -1,8 +1,9 @@
package dev.usbharu.hideout.service.media package dev.usbharu.hideout.service.media
import dev.usbharu.hideout.domain.model.MediaSave import dev.usbharu.hideout.domain.model.MediaSave
import dev.usbharu.hideout.domain.model.hideout.dto.SavedMedia
interface MediaDataStore { interface MediaDataStore {
suspend fun save(dataMediaSave: MediaSave) suspend fun save(dataMediaSave: MediaSave): SavedMedia
suspend fun delete(id: Long) suspend fun delete(id: Long)
} }

View File

@ -1,7 +1,9 @@
package dev.usbharu.hideout.service.media package dev.usbharu.hideout.service.media
import dev.usbharu.hideout.domain.model.hideout.dto.RemoteMedia
import dev.usbharu.hideout.domain.model.hideout.form.Media import dev.usbharu.hideout.domain.model.hideout.form.Media
interface MediaService { interface MediaService {
suspend fun uploadLocalMedia(media: Media): SavedMedia suspend fun uploadLocalMedia(media: Media): dev.usbharu.hideout.domain.model.hideout.entity.Media
suspend fun uploadRemoteMedia(remoteMedia: RemoteMedia)
} }

View File

@ -1,56 +1,87 @@
package dev.usbharu.hideout.service.media package dev.usbharu.hideout.service.media
import dev.usbharu.hideout.domain.model.MediaSave import dev.usbharu.hideout.domain.model.MediaSave
import dev.usbharu.hideout.domain.model.hideout.dto.FaildSavedMedia
import dev.usbharu.hideout.domain.model.hideout.dto.FileType
import dev.usbharu.hideout.domain.model.hideout.dto.RemoteMedia
import dev.usbharu.hideout.domain.model.hideout.dto.SuccessSavedMedia
import dev.usbharu.hideout.domain.model.hideout.form.Media import dev.usbharu.hideout.domain.model.hideout.form.Media
import dev.usbharu.hideout.exception.media.MediaException import dev.usbharu.hideout.exception.media.MediaFileSizeIsZeroException
import dev.usbharu.hideout.exception.media.MediaSaveException
import dev.usbharu.hideout.exception.media.UnsupportedMediaException
import dev.usbharu.hideout.repository.MediaRepository
import dev.usbharu.hideout.service.media.converter.MediaProcessService
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import java.util.*
import javax.imageio.ImageIO import javax.imageio.ImageIO
import dev.usbharu.hideout.domain.model.hideout.entity.Media as EntityMedia
@Service @Service
class MediaServiceImpl( class MediaServiceImpl(
private val mediaDataStore: MediaDataStore, private val mediaDataStore: MediaDataStore,
private val fileTypeDeterminationService: FileTypeDeterminationService, private val fileTypeDeterminationService: FileTypeDeterminationService,
private val mediaBlurhashService: MediaBlurhashService private val mediaBlurhashService: MediaBlurhashService,
private val mediaRepository: MediaRepository,
private val mediaProcessService: MediaProcessService
) : MediaService { ) : MediaService {
override suspend fun uploadLocalMedia(media: Media): SavedMedia { override suspend fun uploadLocalMedia(media: Media): EntityMedia {
if (media.file.size == 0L) { if (media.file.size == 0L) {
return FaildSavedMedia( throw MediaFileSizeIsZeroException("Media file size is zero.")
"File size is 0.",
"Cannot upload a file with a file size of 0."
)
} }
val fileType = fileTypeDeterminationService.fileType(media.file.bytes, media.file.name, media.file.contentType) val fileType = fileTypeDeterminationService.fileType(media.file.bytes, media.file.name, media.file.contentType)
if (fileType != FileTypeDeterminationService.FileType.Image) { if (fileType != FileType.Image) {
return FaildSavedMedia("Unsupported file type.", "FileType: $fileType is not supported.") throw UnsupportedMediaException("FileType: $fileType is not supported.")
} }
try { val process = mediaProcessService.process(fileType, media.file.inputStream, media.thumbnail?.inputStream)
mediaDataStore.save(
MediaSave( val dataMediaSave = MediaSave(
media.file.name, UUID.randomUUID().toString(),
"", "",
media.file.inputStream, process.first,
media.thumbnail.inputStream process.second
) )
val save = try {
mediaDataStore.save(dataMediaSave)
} catch (e: Exception) {
logger.warn("Failed save media", e)
throw MediaSaveException("Failed save media.", e)
}
if (save.success.not()) {
save as FaildSavedMedia
logger.warn("Failed save media. reason: ${save.reason}")
logger.warn(save.description, save.trace)
throw MediaSaveException("Failed save media.")
}
save as SuccessSavedMedia
val blurHash = withContext(Dispatchers.IO) {
mediaBlurhashService.generateBlurhash(ImageIO.read(media.file.bytes.inputStream()))
}
return mediaRepository.save(
EntityMedia(
id = mediaRepository.generateId(),
name = media.file.name,
url = save.url,
remoteUrl = null,
thumbnailUrl = save.thumbnailUrl,
type = fileType,
blurHash = blurHash
) )
} catch (e: MediaException) {
return FaildSavedMedia(
"Faild to upload.",
e.localizedMessage,
e
) )
} }
val withContext = withContext(Dispatchers.IO) { override suspend fun uploadRemoteMedia(remoteMedia: RemoteMedia) {
mediaBlurhashService.generateBlurhash(ImageIO.read(media.file.inputStream))
} }
return SuccessSavedMedia( companion object {
media.file.name, "", "", private val logger = LoggerFactory.getLogger(MediaServiceImpl::class.java)
withContext
)
} }
} }

View File

@ -0,0 +1,7 @@
package dev.usbharu.hideout.service.media
import java.io.InputStream
interface ThumbnailGenerateService {
fun generate(bufferedImage: InputStream, width: Int, height: Int): InputStream
}

View File

@ -0,0 +1,9 @@
package dev.usbharu.hideout.service.media.converter
import dev.usbharu.hideout.domain.model.hideout.dto.FileType
import java.io.InputStream
interface MediaConverter {
fun isSupport(fileType: FileType): Boolean
fun convert(inputStream: InputStream): InputStream
}

View File

@ -0,0 +1,8 @@
package dev.usbharu.hideout.service.media.converter
import dev.usbharu.hideout.domain.model.hideout.dto.FileType
import java.io.InputStream
interface MediaConverterRoot {
suspend fun convert(fileType: FileType, inputStream: InputStream): InputStream
}

View File

@ -0,0 +1,8 @@
package dev.usbharu.hideout.service.media.converter
import dev.usbharu.hideout.domain.model.hideout.dto.FileType
import java.io.InputStream
interface MediaProcessService {
suspend fun process(fileType: FileType, file: InputStream, thumbnail: InputStream?): Pair<InputStream, InputStream>
}

View File

@ -0,0 +1,41 @@
package dev.usbharu.hideout.service.media.converter
import dev.usbharu.hideout.domain.model.hideout.dto.FileType
import dev.usbharu.hideout.exception.media.MediaConvertException
import dev.usbharu.hideout.service.media.ThumbnailGenerateService
import org.slf4j.LoggerFactory
import java.io.InputStream
class MediaProcessServiceImpl(
private val mediaConverterRoot: MediaConverterRoot,
private val thumbnailGenerateService: ThumbnailGenerateService
) : MediaProcessService {
override suspend fun process(
fileType: FileType,
file: InputStream,
thumbnail: InputStream?
): Pair<InputStream, InputStream> {
val fileInputStream = try {
mediaConverterRoot.convert(fileType, file)
} catch (e: Exception) {
logger.warn("Failed convert media.", e)
throw MediaConvertException("Failed convert media.", e)
}
val thumbnailInputStream = try {
thumbnail?.let { mediaConverterRoot.convert(fileType, it) }
} catch (e: Exception) {
logger.warn("Failed convert thumbnail media.", e)
null
}
return fileInputStream to (thumbnailInputStream ?: thumbnailGenerateService.generate(
fileInputStream,
2048,
2048
))
}
companion object {
private val logger = LoggerFactory.getLogger(MediaProcessServiceImpl::class.java)
}
}

View File

@ -7,6 +7,18 @@ hideout:
key-id: a key-id: a
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==" 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" public-key: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyehkd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdgcKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbcmwIDAQAB"
storage:
use-s3: true
endpoint: ""
public-url: ""
bucket: ""
region: ""
access-key: ""
secret-key: ""
spring: spring:
jackson: jackson:
serialization: serialization: