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