mirror of https://github.com/usbharu/Hideout.git
fix: メディア付き投稿に失敗する問題を修正
This commit is contained in:
parent
42b9d4e64b
commit
ced41e64fd
|
@ -7,6 +7,7 @@ import org.slf4j.LoggerFactory
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
|
||||||
import org.springframework.stereotype.Component
|
import org.springframework.stereotype.Component
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
|
import java.nio.file.Files
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import kotlin.io.path.copyTo
|
import kotlin.io.path.copyTo
|
||||||
|
|
||||||
|
@ -19,9 +20,16 @@ class LocalFileSystemMediaStore(
|
||||||
MediaStore {
|
MediaStore {
|
||||||
|
|
||||||
private val publicUrl = localStorageConfig.publicUrl ?: "${applicationConfig.url}/files/"
|
private val publicUrl = localStorageConfig.publicUrl ?: "${applicationConfig.url}/files/"
|
||||||
|
|
||||||
|
private val savePath = Path.of(localStorageConfig.path)
|
||||||
|
|
||||||
|
init {
|
||||||
|
Files.createDirectories(savePath)
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun upload(path: Path, id: String): URI {
|
override suspend fun upload(path: Path, id: String): URI {
|
||||||
logger.info("START Media upload. {}", id)
|
logger.info("START Media upload. {}", id)
|
||||||
val fileSavePath = buildSavePath(path, id)
|
val fileSavePath = buildSavePath(savePath, id)
|
||||||
|
|
||||||
val fileSavePathString = fileSavePath.toAbsolutePath().toString()
|
val fileSavePathString = fileSavePath.toAbsolutePath().toString()
|
||||||
logger.info("MEDIA save. path: {}", fileSavePathString)
|
logger.info("MEDIA save. path: {}", fileSavePathString)
|
||||||
|
|
|
@ -97,6 +97,10 @@ tasks {
|
||||||
|
|
||||||
importMappings.put("org.springframework.core.io.Resource", "org.springframework.web.multipart.MultipartFile")
|
importMappings.put("org.springframework.core.io.Resource", "org.springframework.web.multipart.MultipartFile")
|
||||||
typeMappings.put("org.springframework.core.io.Resource", "org.springframework.web.multipart.MultipartFile")
|
typeMappings.put("org.springframework.core.io.Resource", "org.springframework.web.multipart.MultipartFile")
|
||||||
|
schemaMappings.put(
|
||||||
|
"StatusesRequest",
|
||||||
|
"dev.usbharu.hideout.mastodon.interfaces.api.StatusesRequest"
|
||||||
|
)
|
||||||
templateDir.set("$rootDir/templates")
|
templateDir.set("$rootDir/templates")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,9 +16,9 @@
|
||||||
|
|
||||||
package dev.usbharu.hideout.mastodon.application.status
|
package dev.usbharu.hideout.mastodon.application.status
|
||||||
|
|
||||||
import dev.usbharu.hideout.core.application.shared.LocalUserAbstractApplicationService
|
import dev.usbharu.hideout.core.application.shared.AbstractApplicationService
|
||||||
import dev.usbharu.hideout.core.application.shared.Transaction
|
import dev.usbharu.hideout.core.application.shared.Transaction
|
||||||
import dev.usbharu.hideout.core.domain.model.support.principal.FromApi
|
import dev.usbharu.hideout.core.domain.model.support.principal.Principal
|
||||||
import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.Status
|
import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.Status
|
||||||
import dev.usbharu.hideout.mastodon.query.StatusQueryService
|
import dev.usbharu.hideout.mastodon.query.StatusQueryService
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
|
@ -28,7 +28,7 @@ import org.springframework.stereotype.Service
|
||||||
class GetStatusApplicationService(
|
class GetStatusApplicationService(
|
||||||
private val statusQueryService: StatusQueryService,
|
private val statusQueryService: StatusQueryService,
|
||||||
transaction: Transaction,
|
transaction: Transaction,
|
||||||
) : LocalUserAbstractApplicationService<GetStatus, Status>(
|
) : AbstractApplicationService<GetStatus, Status>(
|
||||||
transaction,
|
transaction,
|
||||||
logger
|
logger
|
||||||
) {
|
) {
|
||||||
|
@ -36,7 +36,7 @@ class GetStatusApplicationService(
|
||||||
val logger = LoggerFactory.getLogger(GetStatusApplicationService::class.java)!!
|
val logger = LoggerFactory.getLogger(GetStatusApplicationService::class.java)!!
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun internalExecute(command: GetStatus, principal: FromApi): Status {
|
override suspend fun internalExecute(command: GetStatus, principal: Principal): Status {
|
||||||
return statusQueryService.findByPostId(command.id.toLong(), principal)
|
return statusQueryService.findByPostId(command.id.toLong(), principal)
|
||||||
?: throw IllegalArgumentException("Post ${command.id} not found.")
|
?: throw IllegalArgumentException("Post ${command.id} not found.")
|
||||||
|
|
||||||
|
|
|
@ -79,11 +79,11 @@ class StatusQueryServiceImpl : StatusQueryService {
|
||||||
emojiIdSet.addAll(statusQueries.flatMap { it.emojiIds })
|
emojiIdSet.addAll(statusQueries.flatMap { it.emojiIds })
|
||||||
|
|
||||||
val qa = authorizedQuery()
|
val qa = authorizedQuery()
|
||||||
|
val replyToAlias = Posts.alias("reply_to")
|
||||||
val postMap = Posts
|
val postMap = qa
|
||||||
.leftJoin(Actors)
|
.leftJoin(Actors)
|
||||||
.selectAll().where { Posts.id inList postIdSet }
|
.selectAll().where { Posts.id inList postIdSet }
|
||||||
.associate { it[Posts.id] to toStatus(it, qa) }
|
.associate { it[Posts.id] to toStatus(it, qa, replyToAlias) }
|
||||||
val mediaMap = Media.selectAll().where { Media.id inList mediaIdSet }
|
val mediaMap = Media.selectAll().where { Media.id inList mediaIdSet }
|
||||||
.associate {
|
.associate {
|
||||||
it[Media.id] to it.toMedia().toMediaAttachments()
|
it[Media.id] to it.toMedia().toMediaAttachments()
|
||||||
|
@ -112,6 +112,7 @@ class StatusQueryServiceImpl : StatusQueryService {
|
||||||
tagged: String?,
|
tagged: String?,
|
||||||
includeFollowers: Boolean,
|
includeFollowers: Boolean,
|
||||||
): List<Status> {
|
): List<Status> {
|
||||||
|
val inReplyToAlias = Posts.alias("reply_to")
|
||||||
val qa = authorizedQuery()
|
val qa = authorizedQuery()
|
||||||
val query = qa
|
val query = qa
|
||||||
.leftJoin(PostsMedia)
|
.leftJoin(PostsMedia)
|
||||||
|
@ -138,7 +139,7 @@ class StatusQueryServiceImpl : StatusQueryService {
|
||||||
.groupBy { it[Posts.id] }
|
.groupBy { it[Posts.id] }
|
||||||
.map { it.value }
|
.map { it.value }
|
||||||
.map {
|
.map {
|
||||||
toStatus(it.first(), qa).copy(
|
toStatus(it.first(), qa, inReplyToAlias).copy(
|
||||||
mediaAttachments = it.mapNotNull { resultRow ->
|
mediaAttachments = it.mapNotNull { resultRow ->
|
||||||
resultRow.toMediaOrNull()?.toMediaAttachments()
|
resultRow.toMediaOrNull()?.toMediaAttachments()
|
||||||
}
|
}
|
||||||
|
@ -151,16 +152,18 @@ class StatusQueryServiceImpl : StatusQueryService {
|
||||||
|
|
||||||
override suspend fun findByPostId(id: Long, principal: Principal?): Status? {
|
override suspend fun findByPostId(id: Long, principal: Principal?): Status? {
|
||||||
val aq = authorizedQuery(principal)
|
val aq = authorizedQuery(principal)
|
||||||
|
val inReplyTo = Posts.alias("reply_to")
|
||||||
val map = aq
|
val map = aq
|
||||||
.leftJoin(PostsMedia, { aq[Posts.id] }, { PostsMedia.postId })
|
.leftJoin(PostsMedia, { aq[Posts.id] }, { PostsMedia.postId })
|
||||||
.leftJoin(Actors, { aq[Posts.actorId] }, { Actors.id })
|
.leftJoin(Actors, { aq[Posts.actorId] }, { Actors.id })
|
||||||
.leftJoin(Media, { PostsMedia.mediaId }, { Media.id })
|
.leftJoin(Media, { PostsMedia.mediaId }, { Media.id })
|
||||||
|
.leftJoin(inReplyTo, { aq[Posts.replyId] }, { inReplyTo[Posts.id] })
|
||||||
.selectAll()
|
.selectAll()
|
||||||
.where { aq[Posts.id] eq id }
|
.where { aq[Posts.id] eq id }
|
||||||
.groupBy { it[aq[Posts.id]] }
|
.groupBy { it[aq[Posts.id]] }
|
||||||
.map { it.value }
|
.map { it.value }
|
||||||
.map {
|
.map {
|
||||||
toStatus(it.first(), aq).copy(
|
toStatus(it.first(), aq, inReplyTo).copy(
|
||||||
mediaAttachments = it.mapNotNull { resultRow ->
|
mediaAttachments = it.mapNotNull { resultRow ->
|
||||||
resultRow.toMediaOrNull()?.toMediaAttachments()
|
resultRow.toMediaOrNull()?.toMediaAttachments()
|
||||||
},
|
},
|
||||||
|
@ -180,18 +183,10 @@ class StatusQueryServiceImpl : StatusQueryService {
|
||||||
it.first
|
it.first
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.map {
|
|
||||||
if (it.inReplyToId != null) {
|
|
||||||
println("statuses trace: $statuses")
|
|
||||||
println("inReplyToId trace: ${it.inReplyToId}")
|
|
||||||
it.copy(inReplyToAccountId = statuses.find { (id) -> id == it.inReplyToId }?.account?.id)
|
|
||||||
} else {
|
|
||||||
it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun findByPostIdsWithMedia(ids: List<Long>): List<Status> {
|
private suspend fun findByPostIdsWithMedia(ids: List<Long>): List<Status> {
|
||||||
|
val inReplyToAlias = Posts.alias("reply_to")
|
||||||
val qa = authorizedQuery()
|
val qa = authorizedQuery()
|
||||||
val pairs = Posts
|
val pairs = Posts
|
||||||
.leftJoin(PostsMedia)
|
.leftJoin(PostsMedia)
|
||||||
|
@ -203,7 +198,7 @@ class StatusQueryServiceImpl : StatusQueryService {
|
||||||
.groupBy { it[Posts.id] }
|
.groupBy { it[Posts.id] }
|
||||||
.map { it.value }
|
.map { it.value }
|
||||||
.map {
|
.map {
|
||||||
toStatus(it.first(), qa).copy(
|
toStatus(it.first(), qa, inReplyToAlias).copy(
|
||||||
mediaAttachments = it.mapNotNull { resultRow ->
|
mediaAttachments = it.mapNotNull { resultRow ->
|
||||||
resultRow.toMediaOrNull()?.toMediaAttachments()
|
resultRow.toMediaOrNull()?.toMediaAttachments()
|
||||||
},
|
},
|
||||||
|
@ -222,7 +217,7 @@ private fun CustomEmoji.toMastodonEmoji(): MastodonEmoji = MastodonEmoji(
|
||||||
category = this.category.orEmpty()
|
category = this.category.orEmpty()
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun toStatus(it: ResultRow, queryAlias: QueryAlias) = Status(
|
private fun toStatus(it: ResultRow, queryAlias: QueryAlias, inReplyToAlias: Alias<Posts>) = Status(
|
||||||
id = it[queryAlias[Posts.id]].toString(),
|
id = it[queryAlias[Posts.id]].toString(),
|
||||||
uri = it[queryAlias[Posts.apId]],
|
uri = it[queryAlias[Posts.apId]],
|
||||||
createdAt = it[queryAlias[Posts.createdAt]].toString(),
|
createdAt = it[queryAlias[Posts.createdAt]].toString(),
|
||||||
|
@ -271,7 +266,7 @@ private fun toStatus(it: ResultRow, queryAlias: QueryAlias) = Status(
|
||||||
repliesCount = 0,
|
repliesCount = 0,
|
||||||
url = it[queryAlias[Posts.apId]],
|
url = it[queryAlias[Posts.apId]],
|
||||||
inReplyToId = it[queryAlias[Posts.replyId]]?.toString(),
|
inReplyToId = it[queryAlias[Posts.replyId]]?.toString(),
|
||||||
inReplyToAccountId = null,
|
inReplyToAccountId = it.getOrNull(inReplyToAlias[Posts.actorId])?.toString(),
|
||||||
language = null,
|
language = null,
|
||||||
text = it[queryAlias[Posts.text]],
|
text = it[queryAlias[Posts.text]],
|
||||||
editedAt = null
|
editedAt = null
|
||||||
|
@ -305,12 +300,12 @@ fun ResultRow.toMediaOrNull(): EntityMedia? {
|
||||||
type = FileType.valueOf(this[Media.type]),
|
type = FileType.valueOf(this[Media.type]),
|
||||||
blurHash = this[Media.blurhash]?.let { MediaBlurHash(it) },
|
blurHash = this[Media.blurhash]?.let { MediaBlurHash(it) },
|
||||||
mimeType = MimeType(mimeType.substringBefore("/"), mimeType.substringAfter("/"), fileType),
|
mimeType = MimeType(mimeType.substringBefore("/"), mimeType.substringAfter("/"), fileType),
|
||||||
description = MediaDescription(this[Media.description] ?: return null)
|
description = this[Media.description]?.let { MediaDescription(it) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun EntityMedia.toMediaAttachments(): MediaAttachment = MediaAttachment(
|
fun EntityMedia.toMediaAttachments(): MediaAttachment = MediaAttachment(
|
||||||
id = id.toString(),
|
id = id.id.toString(),
|
||||||
type = when (type) {
|
type = when (type) {
|
||||||
FileType.Image -> MediaAttachment.Type.image
|
FileType.Image -> MediaAttachment.Type.image
|
||||||
FileType.Video -> MediaAttachment.Type.video
|
FileType.Video -> MediaAttachment.Type.video
|
||||||
|
|
|
@ -22,10 +22,9 @@ import dev.usbharu.hideout.core.domain.model.post.Visibility
|
||||||
import dev.usbharu.hideout.core.domain.model.support.principal.PrincipalContextHolder
|
import dev.usbharu.hideout.core.domain.model.support.principal.PrincipalContextHolder
|
||||||
import dev.usbharu.hideout.mastodon.application.status.GetStatus
|
import dev.usbharu.hideout.mastodon.application.status.GetStatus
|
||||||
import dev.usbharu.hideout.mastodon.application.status.GetStatusApplicationService
|
import dev.usbharu.hideout.mastodon.application.status.GetStatusApplicationService
|
||||||
|
import dev.usbharu.hideout.mastodon.interfaces.api.StatusesRequest.Visibility.*
|
||||||
import dev.usbharu.hideout.mastodon.interfaces.api.generated.StatusApi
|
import dev.usbharu.hideout.mastodon.interfaces.api.generated.StatusApi
|
||||||
import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.Status
|
import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.Status
|
||||||
import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.StatusesRequest
|
|
||||||
import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.StatusesRequest.Visibility.*
|
|
||||||
import org.springframework.http.ResponseEntity
|
import org.springframework.http.ResponseEntity
|
||||||
import org.springframework.stereotype.Controller
|
import org.springframework.stereotype.Controller
|
||||||
|
|
||||||
|
@ -52,13 +51,13 @@ class SpringStatusApi(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun apiV1StatusesPost(statusesRequest: StatusesRequest): ResponseEntity<Status> {
|
override suspend fun apiV1StatusesPost(statusesRequest: dev.usbharu.hideout.mastodon.interfaces.api.StatusesRequest): ResponseEntity<Status> {
|
||||||
|
|
||||||
val principal = principalContextHolder.getPrincipal()
|
val principal = principalContextHolder.getPrincipal()
|
||||||
val execute = registerLocalPostApplicationService.execute(
|
val execute = registerLocalPostApplicationService.execute(
|
||||||
RegisterLocalPost(
|
RegisterLocalPost(
|
||||||
content = statusesRequest.status.orEmpty(),
|
content = statusesRequest.status.orEmpty(),
|
||||||
overview = statusesRequest.spoilerText,
|
overview = statusesRequest.spoiler_text,
|
||||||
visibility = when (statusesRequest.visibility) {
|
visibility = when (statusesRequest.visibility) {
|
||||||
public -> Visibility.PUBLIC
|
public -> Visibility.PUBLIC
|
||||||
unlisted -> Visibility.UNLISTED
|
unlisted -> Visibility.UNLISTED
|
||||||
|
@ -67,9 +66,9 @@ class SpringStatusApi(
|
||||||
null -> Visibility.PUBLIC
|
null -> Visibility.PUBLIC
|
||||||
},
|
},
|
||||||
repostId = null,
|
repostId = null,
|
||||||
replyId = statusesRequest.inReplyToId?.toLong(),
|
replyId = statusesRequest.in_reply_to_id?.toLong(),
|
||||||
sensitive = statusesRequest.sensitive == true,
|
sensitive = statusesRequest.sensitive == true,
|
||||||
mediaIds = statusesRequest.mediaIds.orEmpty().map { it.toLong() }
|
mediaIds = statusesRequest.media_ids.map { it.toLong() }
|
||||||
), principal
|
), principal
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
package dev.usbharu.hideout.mastodon.interfaces.api
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
import dev.usbharu.hideout.core.domain.model.post.Visibility
|
||||||
|
import dev.usbharu.hideout.mastodon.interfaces.api.StatusesRequest.Visibility.*
|
||||||
|
import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.Status
|
||||||
|
import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.StatusesRequestPoll
|
||||||
|
|
||||||
|
@Suppress("VariableNaming", "EnumEntryName")
|
||||||
|
class StatusesRequest {
|
||||||
|
@JsonProperty("status")
|
||||||
|
var status: String? = null
|
||||||
|
|
||||||
|
@JsonProperty("media_ids")
|
||||||
|
var media_ids: List<String> = emptyList()
|
||||||
|
|
||||||
|
@JsonProperty("poll")
|
||||||
|
var poll: StatusesRequestPoll? = null
|
||||||
|
|
||||||
|
@JsonProperty("in_reply_to_id")
|
||||||
|
var in_reply_to_id: String? = null
|
||||||
|
|
||||||
|
@JsonProperty("sensitive")
|
||||||
|
var sensitive: Boolean? = null
|
||||||
|
|
||||||
|
@JsonProperty("spoiler_text")
|
||||||
|
var spoiler_text: String? = null
|
||||||
|
|
||||||
|
@JsonProperty("visibility")
|
||||||
|
var visibility: Visibility? = null
|
||||||
|
|
||||||
|
@JsonProperty("language")
|
||||||
|
var language: String? = null
|
||||||
|
|
||||||
|
@JsonProperty("scheduled_at")
|
||||||
|
var scheduled_at: String? = null
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other !is StatusesRequest) return false
|
||||||
|
|
||||||
|
if (status != other.status) return false
|
||||||
|
if (media_ids != other.media_ids) return false
|
||||||
|
if (poll != other.poll) return false
|
||||||
|
if (in_reply_to_id != other.in_reply_to_id) return false
|
||||||
|
if (sensitive != other.sensitive) return false
|
||||||
|
if (spoiler_text != other.spoiler_text) return false
|
||||||
|
if (visibility != other.visibility) return false
|
||||||
|
if (language != other.language) return false
|
||||||
|
if (scheduled_at != other.scheduled_at) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = status?.hashCode() ?: 0
|
||||||
|
result = 31 * result + media_ids.hashCode()
|
||||||
|
result = 31 * result + (poll?.hashCode() ?: 0)
|
||||||
|
result = 31 * result + (in_reply_to_id?.hashCode() ?: 0)
|
||||||
|
result = 31 * result + (sensitive?.hashCode() ?: 0)
|
||||||
|
result = 31 * result + (spoiler_text?.hashCode() ?: 0)
|
||||||
|
result = 31 * result + (visibility?.hashCode() ?: 0)
|
||||||
|
result = 31 * result + (language?.hashCode() ?: 0)
|
||||||
|
result = 31 * result + (scheduled_at?.hashCode() ?: 0)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "StatusesRequest(status=$status, mediaIds=$media_ids, poll=$poll, inReplyToId=$in_reply_to_id, " +
|
||||||
|
"sensitive=$sensitive, spoilerText=$spoiler_text, visibility=$visibility, language=$language," +
|
||||||
|
" scheduledAt=$scheduled_at)"
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("EnumNaming", "EnumEntryNameCase")
|
||||||
|
enum class Visibility {
|
||||||
|
`public`,
|
||||||
|
unlisted,
|
||||||
|
private,
|
||||||
|
direct
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun StatusesRequest.Visibility?.toPostVisibility(): Visibility {
|
||||||
|
return when (this) {
|
||||||
|
public -> Visibility.PUBLIC
|
||||||
|
unlisted -> Visibility.UNLISTED
|
||||||
|
private -> Visibility.FOLLOWERS
|
||||||
|
direct -> Visibility.DIRECT
|
||||||
|
null -> Visibility.PUBLIC
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun StatusesRequest.Visibility?.toStatusVisibility(): Status.Visibility {
|
||||||
|
return when (this) {
|
||||||
|
public -> Status.Visibility.public
|
||||||
|
unlisted -> Status.Visibility.unlisted
|
||||||
|
private -> Status.Visibility.private
|
||||||
|
direct -> Status.Visibility.direct
|
||||||
|
null -> Status.Visibility.public
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue