mirror of https://github.com/usbharu/Hideout.git
feat: メディア付き投稿をできるように
This commit is contained in:
parent
82d51e2660
commit
6b01927133
|
@ -35,7 +35,7 @@ import java.security.interfaces.RSAPrivateKey
|
||||||
import java.security.interfaces.RSAPublicKey
|
import java.security.interfaces.RSAPublicKey
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@EnableWebSecurity(debug = true)
|
@EnableWebSecurity(debug = false)
|
||||||
@Configuration
|
@Configuration
|
||||||
class SecurityConfig {
|
class SecurityConfig {
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,12 @@ package dev.usbharu.hideout.config
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties
|
import org.springframework.boot.context.properties.ConfigurationProperties
|
||||||
|
import org.springframework.context.annotation.Bean
|
||||||
import org.springframework.context.annotation.Configuration
|
import org.springframework.context.annotation.Configuration
|
||||||
|
import org.springframework.web.filter.CommonsRequestLoggingFilter
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
class SpringConfig {
|
class SpringConfig {
|
||||||
|
|
||||||
|
@ -13,6 +16,17 @@ class SpringConfig {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
lateinit var storageConfig: StorageConfig
|
lateinit var storageConfig: StorageConfig
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
fun requestLoggingFilter(): CommonsRequestLoggingFilter {
|
||||||
|
val loggingFilter = CommonsRequestLoggingFilter()
|
||||||
|
loggingFilter.setIncludeHeaders(true)
|
||||||
|
loggingFilter.setIncludeClientInfo(true)
|
||||||
|
loggingFilter.setIncludeQueryString(true)
|
||||||
|
loggingFilter.setIncludePayload(true)
|
||||||
|
loggingFilter.setMaxPayloadLength(64000)
|
||||||
|
return loggingFilter
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ConfigurationProperties("hideout")
|
@ConfigurationProperties("hideout")
|
||||||
|
|
|
@ -8,5 +8,6 @@ data class PostCreateDto(
|
||||||
val visibility: Visibility = Visibility.PUBLIC,
|
val visibility: Visibility = Visibility.PUBLIC,
|
||||||
val repostId: Long? = null,
|
val repostId: Long? = null,
|
||||||
val repolyId: Long? = null,
|
val repolyId: Long? = null,
|
||||||
val userId: Long
|
val userId: Long,
|
||||||
|
val mediaIds: List<Long> = emptyList()
|
||||||
)
|
)
|
||||||
|
|
|
@ -13,7 +13,8 @@ data class Post private constructor(
|
||||||
val repostId: Long? = null,
|
val repostId: Long? = null,
|
||||||
val replyId: Long? = null,
|
val replyId: Long? = null,
|
||||||
val sensitive: Boolean = false,
|
val sensitive: Boolean = false,
|
||||||
val apId: String = url
|
val apId: String = url,
|
||||||
|
val mediaIds: List<Long> = emptyList()
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
@Suppress("FunctionMinLength", "LongParameterList")
|
@Suppress("FunctionMinLength", "LongParameterList")
|
||||||
|
@ -28,7 +29,8 @@ data class Post private constructor(
|
||||||
repostId: Long? = null,
|
repostId: Long? = null,
|
||||||
replyId: Long? = null,
|
replyId: Long? = null,
|
||||||
sensitive: Boolean = false,
|
sensitive: Boolean = false,
|
||||||
apId: String = url
|
apId: String = url,
|
||||||
|
mediaIds: List<Long> = emptyList()
|
||||||
): Post {
|
): Post {
|
||||||
val characterLimit = Config.configData.characterLimit
|
val characterLimit = Config.configData.characterLimit
|
||||||
|
|
||||||
|
@ -67,7 +69,8 @@ data class Post private constructor(
|
||||||
repostId = repostId,
|
repostId = repostId,
|
||||||
replyId = replyId,
|
replyId = replyId,
|
||||||
sensitive = sensitive,
|
sensitive = sensitive,
|
||||||
apId = apId
|
apId = apId,
|
||||||
|
mediaIds = mediaIds
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,20 +3,29 @@ package dev.usbharu.hideout.query
|
||||||
import dev.usbharu.hideout.domain.model.hideout.entity.Post
|
import dev.usbharu.hideout.domain.model.hideout.entity.Post
|
||||||
import dev.usbharu.hideout.exception.FailedToGetResourcesException
|
import dev.usbharu.hideout.exception.FailedToGetResourcesException
|
||||||
import dev.usbharu.hideout.repository.Posts
|
import dev.usbharu.hideout.repository.Posts
|
||||||
|
import dev.usbharu.hideout.repository.PostsMedia
|
||||||
import dev.usbharu.hideout.repository.toPost
|
import dev.usbharu.hideout.repository.toPost
|
||||||
import dev.usbharu.hideout.util.singleOr
|
import dev.usbharu.hideout.util.singleOr
|
||||||
|
import org.jetbrains.exposed.sql.innerJoin
|
||||||
import org.jetbrains.exposed.sql.select
|
import org.jetbrains.exposed.sql.select
|
||||||
import org.springframework.stereotype.Repository
|
import org.springframework.stereotype.Repository
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
class PostQueryServiceImpl : PostQueryService {
|
class PostQueryServiceImpl : PostQueryService {
|
||||||
override suspend fun findById(id: Long): Post =
|
override suspend fun findById(id: Long): Post =
|
||||||
Posts.select { Posts.id eq id }
|
Posts.innerJoin(PostsMedia, onColumn = { Posts.id }, otherColumn = { PostsMedia.postId })
|
||||||
|
.select { Posts.id eq id }
|
||||||
.singleOr { FailedToGetResourcesException("id: $id is duplicate or does not exist.", it) }.toPost()
|
.singleOr { FailedToGetResourcesException("id: $id is duplicate or does not exist.", it) }.toPost()
|
||||||
|
|
||||||
override suspend fun findByUrl(url: String): Post = Posts.select { Posts.url eq url }
|
override suspend fun findByUrl(url: String): Post =
|
||||||
.singleOr { FailedToGetResourcesException("url: $url is duplicate or does not exist.", it) }.toPost()
|
Posts.innerJoin(PostsMedia, onColumn = { Posts.id }, otherColumn = { PostsMedia.postId })
|
||||||
|
.select { Posts.url eq url }
|
||||||
|
.toPost()
|
||||||
|
.singleOr { FailedToGetResourcesException("url: $url is duplicate or does not exist.", it) }
|
||||||
|
|
||||||
override suspend fun findByApId(string: String): Post = Posts.select { Posts.apId eq string }
|
override suspend fun findByApId(string: String): Post =
|
||||||
.singleOr { FailedToGetResourcesException("apId: $string is duplicate or does not exist.", it) }.toPost()
|
Posts.innerJoin(PostsMedia, onColumn = { Posts.id }, otherColumn = { PostsMedia.postId })
|
||||||
|
.select { Posts.apId eq string }
|
||||||
|
.toPost()
|
||||||
|
.singleOr { FailedToGetResourcesException("apId: $string is duplicate or does not exist.", it) }
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
package dev.usbharu.hideout.query
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.domain.model.hideout.dto.Account
|
||||||
|
import dev.usbharu.hideout.domain.model.hideout.dto.ReactionResponse
|
||||||
|
import dev.usbharu.hideout.domain.model.hideout.entity.Reaction
|
||||||
|
import dev.usbharu.hideout.exception.FailedToGetResourcesException
|
||||||
|
import dev.usbharu.hideout.repository.Reactions
|
||||||
|
import dev.usbharu.hideout.repository.Users
|
||||||
|
import dev.usbharu.hideout.repository.toReaction
|
||||||
|
import dev.usbharu.hideout.util.singleOr
|
||||||
|
import org.jetbrains.exposed.sql.*
|
||||||
|
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||||
|
import org.springframework.stereotype.Repository
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
class ReactionQueryServiceImpl : ReactionQueryService {
|
||||||
|
override suspend fun findByPostId(postId: Long, userId: Long?): List<Reaction> {
|
||||||
|
return Reactions.select {
|
||||||
|
Reactions.postId.eq(postId)
|
||||||
|
}.map { it.toReaction() }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("FunctionMaxLength")
|
||||||
|
override suspend fun findByPostIdAndUserIdAndEmojiId(postId: Long, userId: Long, emojiId: Long): Reaction {
|
||||||
|
return Reactions
|
||||||
|
.select {
|
||||||
|
Reactions.postId.eq(postId).and(Reactions.userId.eq(userId)).and(
|
||||||
|
Reactions.emojiId.eq(emojiId)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.singleOr {
|
||||||
|
FailedToGetResourcesException(
|
||||||
|
"postId: $postId,userId: $userId,emojiId: $emojiId is duplicate or does not exist.",
|
||||||
|
it
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.toReaction()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun reactionAlreadyExist(postId: Long, userId: Long, emojiId: Long): Boolean {
|
||||||
|
return Reactions.select {
|
||||||
|
Reactions.postId.eq(postId).and(Reactions.userId.eq(userId)).and(
|
||||||
|
Reactions.emojiId.eq(emojiId)
|
||||||
|
)
|
||||||
|
}.empty().not()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteByPostIdAndUserId(postId: Long, userId: Long) {
|
||||||
|
Reactions.deleteWhere { Reactions.postId.eq(postId).and(Reactions.userId.eq(userId)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun findByPostIdWithUsers(postId: Long, userId: Long?): List<ReactionResponse> {
|
||||||
|
return Reactions
|
||||||
|
.leftJoin(Users, onColumn = { Reactions.userId }, otherColumn = { id })
|
||||||
|
.select { Reactions.postId.eq(postId) }
|
||||||
|
.groupBy { _: ResultRow -> ReactionResponse("❤", true, "", emptyList()) }
|
||||||
|
.map { entry: Map.Entry<ReactionResponse, List<ResultRow>> ->
|
||||||
|
entry.key.copy(accounts = entry.value.map { Account(it[Users.screenName], "", it[Users.url]) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,11 @@
|
||||||
package dev.usbharu.hideout.query.mastodon
|
package dev.usbharu.hideout.query.mastodon
|
||||||
|
|
||||||
import dev.usbharu.hideout.domain.mastodon.model.generated.Account
|
import dev.usbharu.hideout.domain.mastodon.model.generated.Account
|
||||||
|
import dev.usbharu.hideout.domain.mastodon.model.generated.MediaAttachment
|
||||||
import dev.usbharu.hideout.domain.mastodon.model.generated.Status
|
import dev.usbharu.hideout.domain.mastodon.model.generated.Status
|
||||||
import dev.usbharu.hideout.repository.Posts
|
import dev.usbharu.hideout.domain.model.hideout.dto.FileType
|
||||||
import dev.usbharu.hideout.repository.Users
|
import dev.usbharu.hideout.repository.*
|
||||||
|
import org.jetbrains.exposed.sql.ResultRow
|
||||||
import org.jetbrains.exposed.sql.innerJoin
|
import org.jetbrains.exposed.sql.innerJoin
|
||||||
import org.jetbrains.exposed.sql.select
|
import org.jetbrains.exposed.sql.select
|
||||||
import org.springframework.stereotype.Repository
|
import org.springframework.stereotype.Repository
|
||||||
|
@ -12,67 +14,21 @@ import java.time.Instant
|
||||||
@Repository
|
@Repository
|
||||||
class StatusQueryServiceImpl : StatusQueryService {
|
class StatusQueryServiceImpl : StatusQueryService {
|
||||||
@Suppress("LongMethod")
|
@Suppress("LongMethod")
|
||||||
override suspend fun findByPostIds(ids: List<Long>): List<Status> {
|
override suspend fun findByPostIds(ids: List<Long>): List<Status> = findByPostIdsWithMediaAttachments(ids)
|
||||||
val pairs = Posts.innerJoin(Users, onColumn = { userId }, otherColumn = { id })
|
|
||||||
|
private suspend fun internalFindByPostIds(ids: List<Long>): List<Status> {
|
||||||
|
val pairs = Posts
|
||||||
|
.innerJoin(Users, onColumn = { Posts.userId }, otherColumn = { id })
|
||||||
.select { Posts.id inList ids }
|
.select { Posts.id inList ids }
|
||||||
.map {
|
.map {
|
||||||
Status(
|
toStatus(it) to it[Posts.repostId]
|
||||||
id = it[Posts.id].toString(),
|
|
||||||
uri = it[Posts.apId],
|
|
||||||
createdAt = Instant.ofEpochMilli(it[Posts.createdAt]).toString(),
|
|
||||||
account = Account(
|
|
||||||
id = it[Users.id].toString(),
|
|
||||||
username = it[Users.name],
|
|
||||||
acct = "${it[Users.name]}@${it[Users.domain]}",
|
|
||||||
url = it[Users.url],
|
|
||||||
displayName = it[Users.screenName],
|
|
||||||
note = it[Users.description],
|
|
||||||
avatar = it[Users.url] + "/icon.jpg",
|
|
||||||
avatarStatic = it[Users.url] + "/icon.jpg",
|
|
||||||
header = it[Users.url] + "/header.jpg",
|
|
||||||
headerStatic = it[Users.url] + "/header.jpg",
|
|
||||||
locked = false,
|
|
||||||
fields = emptyList(),
|
|
||||||
emojis = emptyList(),
|
|
||||||
bot = false,
|
|
||||||
group = false,
|
|
||||||
discoverable = true,
|
|
||||||
createdAt = Instant.ofEpochMilli(it[Users.createdAt]).toString(),
|
|
||||||
lastStatusAt = Instant.ofEpochMilli(it[Users.createdAt]).toString(),
|
|
||||||
statusesCount = 0,
|
|
||||||
followersCount = 0,
|
|
||||||
followingCount = 0,
|
|
||||||
noindex = false,
|
|
||||||
moved = false,
|
|
||||||
suspendex = false,
|
|
||||||
limited = false
|
|
||||||
),
|
|
||||||
content = it[Posts.text],
|
|
||||||
visibility = when (it[Posts.visibility]) {
|
|
||||||
0 -> Status.Visibility.public
|
|
||||||
1 -> Status.Visibility.unlisted
|
|
||||||
2 -> Status.Visibility.private
|
|
||||||
3 -> Status.Visibility.direct
|
|
||||||
else -> Status.Visibility.public
|
|
||||||
},
|
|
||||||
sensitive = it[Posts.sensitive],
|
|
||||||
spoilerText = it[Posts.overview].orEmpty(),
|
|
||||||
mediaAttachments = emptyList(),
|
|
||||||
mentions = emptyList(),
|
|
||||||
tags = emptyList(),
|
|
||||||
emojis = emptyList(),
|
|
||||||
reblogsCount = 0,
|
|
||||||
favouritesCount = 0,
|
|
||||||
repliesCount = 0,
|
|
||||||
url = it[Posts.apId],
|
|
||||||
inReplyToId = it[Posts.replyId].toString(),
|
|
||||||
inReplyToAccountId = null,
|
|
||||||
language = null,
|
|
||||||
text = it[Posts.text],
|
|
||||||
editedAt = null
|
|
||||||
) to it[Posts.repostId]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return resolveReplyAndRepost(pairs)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun resolveReplyAndRepost(pairs: List<Pair<Status, Long?>>): List<Status> {
|
||||||
val statuses = pairs.map { it.first }
|
val statuses = pairs.map { it.first }
|
||||||
return pairs
|
return pairs
|
||||||
.map {
|
.map {
|
||||||
|
@ -90,4 +46,92 @@ class StatusQueryServiceImpl : StatusQueryService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun findByPostIdsWithMediaAttachments(ids: List<Long>): List<Status> {
|
||||||
|
val pairs = Posts
|
||||||
|
.innerJoin(PostsMedia, onColumn = { Posts.id }, otherColumn = { PostsMedia.postId })
|
||||||
|
.innerJoin(Users, onColumn = { Posts.userId }, otherColumn = { id })
|
||||||
|
.innerJoin(Media, onColumn = { PostsMedia.mediaId }, otherColumn = { id })
|
||||||
|
.select { Posts.id inList ids }
|
||||||
|
.groupBy { it[Posts.id] }
|
||||||
|
.map { it.value }
|
||||||
|
.map {
|
||||||
|
toStatus(it.first()).copy(mediaAttachments = it.map {
|
||||||
|
it.toMedia().let {
|
||||||
|
MediaAttachment(
|
||||||
|
it.id.toString(),
|
||||||
|
when (it.type) {
|
||||||
|
FileType.Image -> MediaAttachment.Type.image
|
||||||
|
FileType.Video -> MediaAttachment.Type.video
|
||||||
|
FileType.Audio -> MediaAttachment.Type.audio
|
||||||
|
FileType.Unknown -> MediaAttachment.Type.unknown
|
||||||
|
},
|
||||||
|
it.url,
|
||||||
|
it.thumbnailUrl,
|
||||||
|
it.remoteUrl,
|
||||||
|
"",
|
||||||
|
it.blurHash,
|
||||||
|
it.url
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}) to it.first()[Posts.repostId]
|
||||||
|
}
|
||||||
|
return resolveReplyAndRepost(pairs)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun toStatus(it: ResultRow) = Status(
|
||||||
|
id = it[Posts.id].toString(),
|
||||||
|
uri = it[Posts.apId],
|
||||||
|
createdAt = Instant.ofEpochMilli(it[Posts.createdAt]).toString(),
|
||||||
|
account = Account(
|
||||||
|
id = it[Users.id].toString(),
|
||||||
|
username = it[Users.name],
|
||||||
|
acct = "${it[Users.name]}@${it[Users.domain]}",
|
||||||
|
url = it[Users.url],
|
||||||
|
displayName = it[Users.screenName],
|
||||||
|
note = it[Users.description],
|
||||||
|
avatar = it[Users.url] + "/icon.jpg",
|
||||||
|
avatarStatic = it[Users.url] + "/icon.jpg",
|
||||||
|
header = it[Users.url] + "/header.jpg",
|
||||||
|
headerStatic = it[Users.url] + "/header.jpg",
|
||||||
|
locked = false,
|
||||||
|
fields = emptyList(),
|
||||||
|
emojis = emptyList(),
|
||||||
|
bot = false,
|
||||||
|
group = false,
|
||||||
|
discoverable = true,
|
||||||
|
createdAt = Instant.ofEpochMilli(it[Users.createdAt]).toString(),
|
||||||
|
lastStatusAt = Instant.ofEpochMilli(it[Users.createdAt]).toString(),
|
||||||
|
statusesCount = 0,
|
||||||
|
followersCount = 0,
|
||||||
|
followingCount = 0,
|
||||||
|
noindex = false,
|
||||||
|
moved = false,
|
||||||
|
suspendex = false,
|
||||||
|
limited = false
|
||||||
|
),
|
||||||
|
content = it[Posts.text],
|
||||||
|
visibility = when (it[Posts.visibility]) {
|
||||||
|
0 -> Status.Visibility.public
|
||||||
|
1 -> Status.Visibility.unlisted
|
||||||
|
2 -> Status.Visibility.private
|
||||||
|
3 -> Status.Visibility.direct
|
||||||
|
else -> Status.Visibility.public
|
||||||
|
},
|
||||||
|
sensitive = it[Posts.sensitive],
|
||||||
|
spoilerText = it[Posts.overview].orEmpty(),
|
||||||
|
mediaAttachments = emptyList(),
|
||||||
|
mentions = emptyList(),
|
||||||
|
tags = emptyList(),
|
||||||
|
emojis = emptyList(),
|
||||||
|
reblogsCount = 0,
|
||||||
|
favouritesCount = 0,
|
||||||
|
repliesCount = 0,
|
||||||
|
url = it[Posts.apId],
|
||||||
|
inReplyToId = it[Posts.replyId].toString(),
|
||||||
|
inReplyToAccountId = null,
|
||||||
|
language = null,
|
||||||
|
text = it[Posts.text],
|
||||||
|
editedAt = null
|
||||||
|
)
|
||||||
|
|
|
@ -55,18 +55,18 @@ class MediaRepositoryImpl(private val idGenerateService: IdGenerateService) : Me
|
||||||
Media.id eq id
|
Media.id eq id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun ResultRow.toMedia(): EntityMedia {
|
fun ResultRow.toMedia(): EntityMedia {
|
||||||
return EntityMedia(
|
return EntityMedia(
|
||||||
id = this[Media.id],
|
id = this[Media.id],
|
||||||
name = this[Media.name],
|
name = this[Media.name],
|
||||||
url = this[Media.url],
|
url = this[Media.url],
|
||||||
remoteUrl = this[Media.remoteUrl],
|
remoteUrl = this[Media.remoteUrl],
|
||||||
thumbnailUrl = this[Media.thumbnailUrl],
|
thumbnailUrl = this[Media.thumbnailUrl],
|
||||||
type = FileType.values().first { it.ordinal == this[Media.type] },
|
type = FileType.values().first { it.ordinal == this[Media.type] },
|
||||||
blurHash = this[Media.blurhash],
|
blurHash = this[Media.blurhash],
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
object Media : Table("media") {
|
object Media : Table("media") {
|
||||||
|
@ -77,4 +77,5 @@ object Media : Table("media") {
|
||||||
val thumbnailUrl = varchar("thumbnail_url", 255).nullable()
|
val thumbnailUrl = varchar("thumbnail_url", 255).nullable()
|
||||||
val type = integer("type")
|
val type = integer("type")
|
||||||
val blurhash = varchar("blurhash", 255).nullable()
|
val blurhash = varchar("blurhash", 255).nullable()
|
||||||
|
override val primaryKey = PrimaryKey(id)
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,18 @@ class PostRepositoryImpl(private val idGenerateService: IdGenerateService) : Pos
|
||||||
it[sensitive] = post.sensitive
|
it[sensitive] = post.sensitive
|
||||||
it[apId] = post.apId
|
it[apId] = post.apId
|
||||||
}
|
}
|
||||||
|
PostsMedia.batchInsert(post.mediaIds) {
|
||||||
|
this[PostsMedia.postId] = post.id
|
||||||
|
this[PostsMedia.mediaId] = it
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
PostsMedia.deleteWhere {
|
||||||
|
PostsMedia.postId eq post.id
|
||||||
|
}
|
||||||
|
PostsMedia.batchInsert(post.mediaIds) {
|
||||||
|
this[PostsMedia.postId] = post.id
|
||||||
|
this[PostsMedia.mediaId] = it
|
||||||
|
}
|
||||||
Posts.update({ Posts.id eq post.id }) {
|
Posts.update({ Posts.id eq post.id }) {
|
||||||
it[userId] = post.userId
|
it[userId] = post.userId
|
||||||
it[overview] = post.overview
|
it[overview] = post.overview
|
||||||
|
@ -46,8 +57,12 @@ class PostRepositoryImpl(private val idGenerateService: IdGenerateService) : Pos
|
||||||
return post
|
return post
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun findById(id: Long): Post = Posts.select { Posts.id eq id }.singleOrNull()?.toPost()
|
override suspend fun findById(id: Long): Post =
|
||||||
?: throw FailedToGetResourcesException("id: $id was not found.")
|
Posts.innerJoin(PostsMedia, onColumn = { Posts.id }, otherColumn = { PostsMedia.postId })
|
||||||
|
.select { Posts.id eq id }
|
||||||
|
.toPost()
|
||||||
|
.singleOrNull()
|
||||||
|
?: throw FailedToGetResourcesException("id: $id was not found.")
|
||||||
|
|
||||||
override suspend fun delete(id: Long) {
|
override suspend fun delete(id: Long) {
|
||||||
Posts.deleteWhere { Posts.id eq id }
|
Posts.deleteWhere { Posts.id eq id }
|
||||||
|
@ -69,6 +84,13 @@ object Posts : Table() {
|
||||||
override val primaryKey: PrimaryKey = PrimaryKey(id)
|
override val primaryKey: PrimaryKey = PrimaryKey(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object PostsMedia : Table() {
|
||||||
|
val postId = long("post_id").references(Posts.id, ReferenceOption.CASCADE, ReferenceOption.CASCADE)
|
||||||
|
val mediaId = long("media_id").references(Media.id, ReferenceOption.CASCADE, ReferenceOption.CASCADE)
|
||||||
|
override val primaryKey = PrimaryKey(postId, mediaId)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fun ResultRow.toPost(): Post {
|
fun ResultRow.toPost(): Post {
|
||||||
return Post.of(
|
return Post.of(
|
||||||
id = this[Posts.id],
|
id = this[Posts.id],
|
||||||
|
@ -81,6 +103,12 @@ fun ResultRow.toPost(): Post {
|
||||||
repostId = this[Posts.repostId],
|
repostId = this[Posts.repostId],
|
||||||
replyId = this[Posts.replyId],
|
replyId = this[Posts.replyId],
|
||||||
sensitive = this[Posts.sensitive],
|
sensitive = this[Posts.sensitive],
|
||||||
apId = this[Posts.apId]
|
apId = this[Posts.apId],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Query.toPost(): List<Post> {
|
||||||
|
return this.groupBy { it[Posts.id] }
|
||||||
|
.map { it.value }
|
||||||
|
.map { it.first().toPost().copy(mediaIds = it.map { it[PostsMedia.mediaId] }) }
|
||||||
|
}
|
||||||
|
|
|
@ -168,6 +168,7 @@ class APNoteServiceImpl(
|
||||||
postQueryService.findByUrl(it)
|
postQueryService.findByUrl(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: リモートのメディア処理を追加
|
||||||
postService.createRemote(
|
postService.createRemote(
|
||||||
Post.of(
|
Post.of(
|
||||||
id = postRepository.generateId(),
|
id = postRepository.generateId(),
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
package dev.usbharu.hideout.service.api.mastodon
|
package dev.usbharu.hideout.service.api.mastodon
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.domain.mastodon.model.generated.MediaAttachment
|
||||||
import dev.usbharu.hideout.domain.mastodon.model.generated.Status
|
import dev.usbharu.hideout.domain.mastodon.model.generated.Status
|
||||||
import dev.usbharu.hideout.domain.mastodon.model.generated.StatusesRequest
|
import dev.usbharu.hideout.domain.mastodon.model.generated.StatusesRequest
|
||||||
|
import dev.usbharu.hideout.domain.model.hideout.dto.FileType
|
||||||
import dev.usbharu.hideout.domain.model.hideout.dto.PostCreateDto
|
import dev.usbharu.hideout.domain.model.hideout.dto.PostCreateDto
|
||||||
import dev.usbharu.hideout.domain.model.hideout.entity.Visibility
|
import dev.usbharu.hideout.domain.model.hideout.entity.Visibility
|
||||||
import dev.usbharu.hideout.exception.FailedToGetResourcesException
|
import dev.usbharu.hideout.exception.FailedToGetResourcesException
|
||||||
import dev.usbharu.hideout.query.PostQueryService
|
import dev.usbharu.hideout.query.PostQueryService
|
||||||
import dev.usbharu.hideout.query.UserQueryService
|
import dev.usbharu.hideout.query.UserQueryService
|
||||||
|
import dev.usbharu.hideout.repository.MediaRepository
|
||||||
import dev.usbharu.hideout.service.core.Transaction
|
import dev.usbharu.hideout.service.core.Transaction
|
||||||
import dev.usbharu.hideout.service.mastodon.AccountService
|
import dev.usbharu.hideout.service.mastodon.AccountService
|
||||||
import dev.usbharu.hideout.service.post.PostService
|
import dev.usbharu.hideout.service.post.PostService
|
||||||
|
@ -24,11 +27,13 @@ class StatsesApiServiceImpl(
|
||||||
private val accountService: AccountService,
|
private val accountService: AccountService,
|
||||||
private val postQueryService: PostQueryService,
|
private val postQueryService: PostQueryService,
|
||||||
private val userQueryService: UserQueryService,
|
private val userQueryService: UserQueryService,
|
||||||
|
private val mediaRepository: MediaRepository,
|
||||||
private val transaction: Transaction
|
private val transaction: Transaction
|
||||||
) :
|
) :
|
||||||
StatusesApiService {
|
StatusesApiService {
|
||||||
@Suppress("LongMethod")
|
@Suppress("LongMethod")
|
||||||
override suspend fun postStatus(statusesRequest: StatusesRequest, userId: Long): Status = transaction.transaction {
|
override suspend fun postStatus(statusesRequest: StatusesRequest, userId: Long): Status = transaction.transaction {
|
||||||
|
println("Post status media ids " + statusesRequest.mediaIds)
|
||||||
val visibility = when (statusesRequest.visibility) {
|
val visibility = when (statusesRequest.visibility) {
|
||||||
StatusesRequest.Visibility.public -> Visibility.PUBLIC
|
StatusesRequest.Visibility.public -> Visibility.PUBLIC
|
||||||
StatusesRequest.Visibility.unlisted -> Visibility.UNLISTED
|
StatusesRequest.Visibility.unlisted -> Visibility.UNLISTED
|
||||||
|
@ -43,7 +48,8 @@ class StatsesApiServiceImpl(
|
||||||
overview = statusesRequest.spoilerText,
|
overview = statusesRequest.spoilerText,
|
||||||
visibility = visibility,
|
visibility = visibility,
|
||||||
repolyId = statusesRequest.inReplyToId?.toLongOrNull(),
|
repolyId = statusesRequest.inReplyToId?.toLongOrNull(),
|
||||||
userId = userId
|
userId = userId,
|
||||||
|
mediaIds = statusesRequest.mediaIds.orEmpty().map { it.toLong() }
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
val account = accountService.findById(userId)
|
val account = accountService.findById(userId)
|
||||||
|
@ -66,6 +72,27 @@ class StatsesApiServiceImpl(
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: n+1解消
|
||||||
|
val mediaAttachment = post.mediaIds.map { mediaId ->
|
||||||
|
mediaRepository.findById(mediaId)
|
||||||
|
}.map {
|
||||||
|
MediaAttachment(
|
||||||
|
it.id.toString(),
|
||||||
|
when (it.type) {
|
||||||
|
FileType.Image -> MediaAttachment.Type.image
|
||||||
|
FileType.Video -> MediaAttachment.Type.video
|
||||||
|
FileType.Audio -> MediaAttachment.Type.audio
|
||||||
|
FileType.Unknown -> MediaAttachment.Type.unknown
|
||||||
|
},
|
||||||
|
it.url,
|
||||||
|
it.thumbnailUrl,
|
||||||
|
it.remoteUrl,
|
||||||
|
"",
|
||||||
|
it.blurHash,
|
||||||
|
it.url
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
Status(
|
Status(
|
||||||
id = post.id.toString(),
|
id = post.id.toString(),
|
||||||
uri = post.apId,
|
uri = post.apId,
|
||||||
|
@ -75,7 +102,7 @@ class StatsesApiServiceImpl(
|
||||||
visibility = postVisibility,
|
visibility = postVisibility,
|
||||||
sensitive = post.sensitive,
|
sensitive = post.sensitive,
|
||||||
spoilerText = post.overview.orEmpty(),
|
spoilerText = post.overview.orEmpty(),
|
||||||
mediaAttachments = emptyList(),
|
mediaAttachments = mediaAttachment,
|
||||||
mentions = emptyList(),
|
mentions = emptyList(),
|
||||||
tags = emptyList(),
|
tags = emptyList(),
|
||||||
emojis = emptyList(),
|
emojis = emptyList(),
|
||||||
|
@ -87,7 +114,7 @@ class StatsesApiServiceImpl(
|
||||||
inReplyToAccountId = replyUser?.toString(),
|
inReplyToAccountId = replyUser?.toString(),
|
||||||
language = null,
|
language = null,
|
||||||
text = post.text,
|
text = post.text,
|
||||||
editedAt = null
|
editedAt = null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,8 @@ class PostServiceImpl(
|
||||||
text = post.text,
|
text = post.text,
|
||||||
createdAt = Instant.now().toEpochMilli(),
|
createdAt = Instant.now().toEpochMilli(),
|
||||||
visibility = post.visibility,
|
visibility = post.visibility,
|
||||||
url = "${user.url}/posts/$id"
|
url = "${user.url}/posts/$id",
|
||||||
|
mediaIds = post.mediaIds
|
||||||
)
|
)
|
||||||
return internalCreate(createPost, isLocal)
|
return internalCreate(createPost, isLocal)
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,4 +13,5 @@
|
||||||
<logger name="Exposed" level="INFO"/>
|
<logger name="Exposed" level="INFO"/>
|
||||||
<logger name="io.ktor.server.plugins.contentnegotiation" level="INFO"/>
|
<logger name="io.ktor.server.plugins.contentnegotiation" level="INFO"/>
|
||||||
<logger name="org.springframework.security" level="DEBUG"/>
|
<logger name="org.springframework.security" level="DEBUG"/>
|
||||||
|
<logger name="org.springframework.web.filter.CommonsRequestLoggingFilter" level="DEBUG"/>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|
Loading…
Reference in New Issue