feat: メディア関係のクラスを追加

This commit is contained in:
usbharu 2023-10-04 14:48:12 +09:00
parent 9e570dc1bf
commit 7fa2e97368
17 changed files with 319 additions and 2 deletions

View File

@ -2,18 +2,29 @@ package dev.usbharu.hideout.controller.mastodon
import dev.usbharu.hideout.controller.mastodon.generated.MediaApi import dev.usbharu.hideout.controller.mastodon.generated.MediaApi
import dev.usbharu.hideout.domain.mastodon.model.generated.MediaAttachment import dev.usbharu.hideout.domain.mastodon.model.generated.MediaAttachment
import dev.usbharu.hideout.domain.model.hideout.form.Media
import dev.usbharu.hideout.service.api.mastodon.MediaApiService
import org.springframework.http.ResponseEntity import org.springframework.http.ResponseEntity
import org.springframework.stereotype.Controller import org.springframework.stereotype.Controller
import org.springframework.web.multipart.MultipartFile import org.springframework.web.multipart.MultipartFile
@Controller @Controller
class MastodonMediaApiController : MediaApi { class MastodonMediaApiController(private val mediaApiService: MediaApiService) : MediaApi {
override fun apiV1MediaPost( override fun apiV1MediaPost(
file: MultipartFile, file: MultipartFile,
thumbnail: MultipartFile?, thumbnail: MultipartFile?,
description: String?, description: String?,
focus: String? focus: String?
): ResponseEntity<MediaAttachment> { ): ResponseEntity<MediaAttachment> {
return ResponseEntity.ok(
mediaApiService.postMedia(
Media(
file,
thumbnail,
description,
focus
)
)
)
} }
} }

View File

@ -0,0 +1,10 @@
package dev.usbharu.hideout.domain.model
import java.io.InputStream
data class MediaSave(
val name: String,
val prefix: String,
val fileInputStream: InputStream,
val thumbnailInputStream: InputStream
)

View File

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

View File

@ -0,0 +1,10 @@
package dev.usbharu.hideout.domain.model.hideout.form
import org.springframework.web.multipart.MultipartFile
data class Media(
val file: MultipartFile,
val thumbnail: MultipartFile?,
val description: String?,
val focus: String?
)

View File

@ -0,0 +1,14 @@
package dev.usbharu.hideout.exception.media
abstract class MediaException : RuntimeException {
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 MediaUploadException : 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,10 @@
package dev.usbharu.hideout.repository
import dev.usbharu.hideout.domain.model.hideout.entity.Media
interface MediaRepository {
suspend fun generateId(): Long
suspend fun save(media: Media): Media
suspend fun findById(id: Long): Media
suspend fun delete(id: Long)
}

View File

@ -0,0 +1,80 @@
package dev.usbharu.hideout.repository
import dev.usbharu.hideout.exception.FailedToGetResourcesException
import dev.usbharu.hideout.service.core.IdGenerateService
import dev.usbharu.hideout.service.media.FileTypeDeterminationService
import dev.usbharu.hideout.util.singleOr
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.springframework.stereotype.Repository
import dev.usbharu.hideout.domain.model.hideout.entity.Media as EntityMedia
@Repository
class MediaRepositoryImpl(private val idGenerateService: IdGenerateService) : MediaRepository {
override suspend fun generateId(): Long = idGenerateService.generateId()
override suspend fun save(media: EntityMedia): EntityMedia {
if (Media.select {
Media.id eq media.id
}.singleOrNull() != null) {
Media.update({ Media.id eq media.id }) {
it[Media.name] = media.name
it[Media.url] = media.url
it[Media.remoteUrl] = media.remoteUrl
it[Media.thumbnailUrl] = media.thumbnailUrl
it[Media.type] = media.type.ordinal
it[Media.blurhash] = media.blurHash
}
} else {
Media.insert {
it[Media.id] = media.id
it[Media.name] = media.name
it[Media.url] = media.url
it[Media.remoteUrl] = media.remoteUrl
it[Media.thumbnailUrl] = media.thumbnailUrl
it[Media.type] = media.type.ordinal
it[Media.blurhash] = media.blurHash
}
}
return media
}
override suspend fun findById(id: Long): EntityMedia {
return Media
.select {
Media.id eq id
}
.singleOr {
FailedToGetResourcesException("id: $id was not found.")
}.toMedia()
}
override suspend fun delete(id: Long) {
Media.deleteWhere {
Media.id eq id
}
}
fun ResultRow.toMedia(): EntityMedia {
return EntityMedia(
this[Media.id],
this[Media.name],
this[Media.url],
this[Media.remoteUrl],
this[Media.thumbnailUrl],
FileTypeDeterminationService.FileType.values().first { it.ordinal == this[Media.type] },
this[Media.blurhash],
)
}
}
object Media : Table("media") {
val id = long("id")
val name = varchar("name", 255)
val url = varchar("url", 255)
val remoteUrl = varchar("remote_url", 255).nullable()
val thumbnailUrl = varchar("thumbnail_url", 255).nullable()
val type = integer("type")
val blurhash = varchar("blurhash", 255).nullable()
}

View File

@ -0,0 +1,10 @@
package dev.usbharu.hideout.service.api.mastodon
import dev.usbharu.hideout.domain.mastodon.model.generated.MediaAttachment
import dev.usbharu.hideout.domain.model.hideout.form.Media
import org.springframework.stereotype.Service
@Service
interface MediaApiService {
suspend fun postMedia(media: Media): MediaAttachment
}

View File

@ -0,0 +1,11 @@
package dev.usbharu.hideout.service.api.mastodon
import dev.usbharu.hideout.domain.mastodon.model.generated.MediaAttachment
import dev.usbharu.hideout.domain.model.hideout.form.Media
import dev.usbharu.hideout.service.media.MediaService
class MediaApiServiceImpl(private val mediaService: MediaService) : MediaApiService {
override suspend fun postMedia(media: Media): MediaAttachment {
mediaService.uploadLocalMedia(media)
}
}

View File

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

View File

@ -0,0 +1,26 @@
package dev.usbharu.hideout.service.media
import org.springframework.stereotype.Component
@Component
class FileTypeDeterminationServiceImpl : FileTypeDeterminationService {
override fun fileType(
byteArray: ByteArray,
filename: String,
contentType: String?
): FileTypeDeterminationService.FileType {
if (contentType == null) {
return FileTypeDeterminationService.FileType.Unknown
}
if (contentType.startsWith("image")) {
return FileTypeDeterminationService.FileType.Image
}
if (contentType.startsWith("video")) {
return FileTypeDeterminationService.FileType.Video
}
if (contentType.startsWith("audio")) {
return FileTypeDeterminationService.FileType.Audio
}
return FileTypeDeterminationService.FileType.Unknown
}
}

View File

@ -0,0 +1,7 @@
package dev.usbharu.hideout.service.media
import java.awt.image.BufferedImage
interface MediaBlurhashService {
fun generateBlurhash(bufferedImage: BufferedImage): String
}

View File

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

View File

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

View File

@ -0,0 +1,56 @@
package dev.usbharu.hideout.service.media
import dev.usbharu.hideout.domain.model.MediaSave
import dev.usbharu.hideout.domain.model.hideout.form.Media
import dev.usbharu.hideout.exception.media.MediaException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.springframework.stereotype.Service
import javax.imageio.ImageIO
@Service
class MediaServiceImpl(
private val mediaDataStore: MediaDataStore,
private val fileTypeDeterminationService: FileTypeDeterminationService,
private val mediaBlurhashService: MediaBlurhashService
) : MediaService {
override suspend fun uploadLocalMedia(media: Media): SavedMedia {
if (media.file.size == 0L) {
return FaildSavedMedia(
"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)
if (fileType != FileTypeDeterminationService.FileType.Image) {
return FaildSavedMedia("Unsupported file type.", "FileType: $fileType is not supported.")
}
try {
mediaDataStore.save(
MediaSave(
media.file.name,
"",
media.file.inputStream,
media.thumbnail.inputStream
)
)
} catch (e: MediaException) {
return FaildSavedMedia(
"Faild to upload.",
e.localizedMessage,
e
)
}
val withContext = withContext(Dispatchers.IO) {
mediaBlurhashService.generateBlurhash(ImageIO.read(media.file.inputStream))
}
return SuccessSavedMedia(
media.file.name, "", "",
withContext
)
}
}

View File

@ -0,0 +1,18 @@
package dev.usbharu.hideout.service.media
sealed class SavedMedia(val success: Boolean)
class SuccessSavedMedia(
val name: String,
val url: String,
val thumbnailUrl: String,
val blurhash: String
) :
SavedMedia(true)
class FaildSavedMedia(
val reason: String,
val description: String,
val trace: Throwable? = null
) : SavedMedia(false)