feat: タイムラインにリアクションを表示するように

This commit is contained in:
usbharu 2024-09-08 16:40:51 +09:00
parent 2b567bb1d5
commit e42919ce3c
Signed by: usbharu
GPG Key ID: 6556747BF94EEBC8
6 changed files with 67 additions and 13 deletions

View File

@ -50,6 +50,10 @@ data class CustomEmoji(
data class UnicodeEmoji(
override val name: String
) : Emoji() {
override val domain: Domain = Domain("unicode.org")
override val domain: Domain = Companion.domain
override fun id(): String = name
companion object {
val domain = Domain("unicode.org")
}
}

View File

@ -1,5 +1,6 @@
package dev.usbharu.hideout.core.domain.model.support.timelineobjectdetail
import dev.usbharu.hideout.core.application.model.Reactions
import dev.usbharu.hideout.core.domain.model.actor.Actor
import dev.usbharu.hideout.core.domain.model.media.Media
import dev.usbharu.hideout.core.domain.model.post.Post
@ -29,7 +30,8 @@ data class TimelineObjectDetail(
val isPureRepost: Boolean,
val lastUpdateAt: Instant,
val hasMediaInRepost: Boolean,
val warnFilter: List<TimelineObjectWarnFilter>
val warnFilter: List<TimelineObjectWarnFilter>,
val reactionsList: List<Reactions>
) {
companion object {
@Suppress("LongParameterList")
@ -48,7 +50,8 @@ data class TimelineObjectDetail(
repostPostMedias: List<Media>?,
repostPostActor: Actor?,
repostPostActorIconMedia: Media?,
warnFilter: List<TimelineObjectWarnFilter>
warnFilter: List<TimelineObjectWarnFilter>,
reactionsList: List<Reactions>
): TimelineObjectDetail {
return TimelineObjectDetail(
id = timelineObject.id,
@ -69,7 +72,8 @@ data class TimelineObjectDetail(
isPureRepost = timelineObject.isPureRepost,
lastUpdateAt = timelineObject.lastUpdatedAt,
hasMediaInRepost = timelineObject.hasMediaInRepost,
warnFilter = warnFilter
warnFilter = warnFilter,
reactionsList = reactionsList
)
}
}

View File

@ -1,16 +1,18 @@
package dev.usbharu.hideout.core.infrastructure.exposedquery
import dev.usbharu.hideout.core.application.model.Reactions
import dev.usbharu.hideout.core.domain.model.emoji.UnicodeEmoji
import dev.usbharu.hideout.core.domain.model.post.PostId
import dev.usbharu.hideout.core.infrastructure.exposedrepository.AbstractRepository
import dev.usbharu.hideout.core.infrastructure.exposedrepository.CustomEmojis
import dev.usbharu.hideout.core.infrastructure.exposedrepository.toCustomEmojiOrNull
import dev.usbharu.hideout.core.infrastructure.exposedrepository.toReaction
import dev.usbharu.hideout.core.query.reactions.ReactionsQueryService
import org.jetbrains.exposed.sql.selectAll
import org.jetbrains.exposed.sql.*
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Repository
import java.net.URI
import dev.usbharu.hideout.core.infrastructure.exposedrepository.Reactions as ExposedrepositoryReactions
@Repository
@ -33,6 +35,35 @@ class ExposedReactionsQueryService : ReactionsQueryService, AbstractRepository()
}
}
override suspend fun findAllByPostIdIn(postIds: List<PostId>): List<Reactions> {
return query {
val actorIdsQuery =
ExposedrepositoryReactions.actorId.castTo<String>(VarCharColumnType()).groupConcat(",", true)
ExposedrepositoryReactions.leftJoin(CustomEmojis)
.select(
ExposedrepositoryReactions.postId,
ExposedrepositoryReactions.postId.count(),
ExposedrepositoryReactions.customEmojiId.max(),
ExposedrepositoryReactions.unicodeEmoji.max(),
actorIdsQuery
)
.where { ExposedrepositoryReactions.postId inList postIds.map { it.id } }
.groupBy(ExposedrepositoryReactions.postId)
.map {
Reactions(
it[ExposedrepositoryReactions.postId],
it[ExposedrepositoryReactions.postId.count()].toInt(),
it.getOrNull(CustomEmojis.name) ?: it[ExposedrepositoryReactions.unicodeEmoji],
it.getOrNull(CustomEmojis.domain) ?: UnicodeEmoji.domain.domain,
it.getOrNull(CustomEmojis.url)?.let { it1 -> URI.create(it1) },
it[actorIdsQuery].split(",").mapNotNull { it.toLongOrNull() }
)
}
}
}
override val logger: Logger
get() = Companion.logger

View File

@ -1,5 +1,6 @@
package dev.usbharu.hideout.core.infrastructure.timeline
import dev.usbharu.hideout.core.application.model.Reactions
import dev.usbharu.hideout.core.domain.model.actor.Actor
import dev.usbharu.hideout.core.domain.model.actor.ActorId
import dev.usbharu.hideout.core.domain.model.filter.Filter
@ -256,6 +257,8 @@ abstract class AbstractTimelineStore(private val idGenerateService: IdGenerateSe
actors.mapNotNull { it.value.icon }
)
val reactions = getReactions(posts.map { it.id })
return PaginationList(
timelineObjectList.mapNotNull<TimelineObject, TimelineObjectDetail> {
val timelineUserDetail = userDetails[it.userDetailId] ?: return@mapNotNull null
@ -268,6 +271,7 @@ abstract class AbstractTimelineStore(private val idGenerateService: IdGenerateSe
val repost = postMap[it.repostId]
val repostMedias = repost?.post?.mediaIds?.mapNotNull { mediaId -> mediaMap[mediaId] }
val repostActor = actors[it.repostActorId]
val reactionsList = reactions[it.postId].orEmpty()
TimelineObjectDetail.of(
timelineObject = it,
timelineUserDetail = timelineUserDetail,
@ -288,7 +292,8 @@ abstract class AbstractTimelineStore(private val idGenerateService: IdGenerateSe
filterResult.filter.id,
filterResult.matchedKeyword
)
}
},
reactionsList = reactionsList
)
},
timelineObjectList.lastOrNull()?.postId,
@ -300,5 +305,7 @@ abstract class AbstractTimelineStore(private val idGenerateService: IdGenerateSe
protected abstract suspend fun getMedias(mediaIds: List<MediaId>): Map<MediaId, Media>
protected abstract suspend fun getReactions(postIds: List<PostId>): Map<PostId, List<Reactions>>
protected abstract suspend fun getUserDetails(userDetailIdList: List<UserDetailId>): Map<UserDetailId, UserDetail>
}

View File

@ -1,5 +1,6 @@
package dev.usbharu.hideout.core.infrastructure.timeline
import dev.usbharu.hideout.core.application.model.Reactions
import dev.usbharu.hideout.core.config.DefaultTimelineStoreConfig
import dev.usbharu.hideout.core.domain.model.actor.Actor
import dev.usbharu.hideout.core.domain.model.actor.ActorId
@ -32,6 +33,7 @@ import dev.usbharu.hideout.core.domain.service.filter.FilterDomainService
import dev.usbharu.hideout.core.domain.service.post.IPostReadAccessControl
import dev.usbharu.hideout.core.domain.shared.id.IdGenerateService
import dev.usbharu.hideout.core.external.timeline.ReadTimelineOption
import dev.usbharu.hideout.core.query.reactions.ReactionsQueryService
import org.springframework.stereotype.Component
import java.time.Instant
@ -49,7 +51,8 @@ open class DefaultTimelineStore(
private val userDetailRepository: UserDetailRepository,
private val actorRepository: ActorRepository,
private val mediaRepository: MediaRepository,
private val postIPostReadAccessControl: IPostReadAccessControl
private val postIPostReadAccessControl: IPostReadAccessControl,
private val reactionsQueryService: ReactionsQueryService,
) : AbstractTimelineStore(idGenerateService) {
override suspend fun getTimelines(actorId: ActorId): List<Timeline> {
return timelineRepository.findByIds(
@ -157,6 +160,10 @@ open class DefaultTimelineStore(
override suspend fun getMedias(mediaIds: List<MediaId>): Map<MediaId, Media> =
mediaRepository.findByIds(mediaIds).associateBy { it.id }
override suspend fun getReactions(postIds: List<PostId>): Map<PostId, List<Reactions>> {
return reactionsQueryService.findAllByPostIdIn(postIds).groupBy { PostId(it.postId) }
}
override suspend fun getUserDetails(userDetailIdList: List<UserDetailId>): Map<UserDetailId, UserDetail> =
userDetailRepository.findAllById(userDetailIdList).associateBy { it.id }
}

View File

@ -5,4 +5,5 @@ import dev.usbharu.hideout.core.domain.model.post.PostId
interface ReactionsQueryService {
suspend fun findAllByPostId(postId: PostId): List<Reactions>
suspend fun findAllByPostIdIn(postIds: List<PostId>): List<Reactions>
}