mirror of https://github.com/usbharu/Hideout.git
feat: メディア関係のクラスを追加
This commit is contained in:
parent
9e570dc1bf
commit
7fa2e97368
|
@ -2,18 +2,29 @@ package dev.usbharu.hideout.controller.mastodon
|
|||
|
||||
import dev.usbharu.hideout.controller.mastodon.generated.MediaApi
|
||||
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.stereotype.Controller
|
||||
import org.springframework.web.multipart.MultipartFile
|
||||
|
||||
@Controller
|
||||
class MastodonMediaApiController : MediaApi {
|
||||
class MastodonMediaApiController(private val mediaApiService: MediaApiService) : MediaApi {
|
||||
override fun apiV1MediaPost(
|
||||
file: MultipartFile,
|
||||
thumbnail: MultipartFile?,
|
||||
description: String?,
|
||||
focus: String?
|
||||
): ResponseEntity<MediaAttachment> {
|
||||
|
||||
return ResponseEntity.ok(
|
||||
mediaApiService.postMedia(
|
||||
Media(
|
||||
file,
|
||||
thumbnail,
|
||||
description,
|
||||
focus
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
|
@ -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?
|
||||
)
|
|
@ -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?
|
||||
)
|
|
@ -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
|
||||
)
|
||||
}
|
|
@ -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
|
||||
)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package dev.usbharu.hideout.service.media
|
||||
|
||||
import java.awt.image.BufferedImage
|
||||
|
||||
interface MediaBlurhashService {
|
||||
fun generateBlurhash(bufferedImage: BufferedImage): String
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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)
|
Loading…
Reference in New Issue