feat: タイムラインを読めるように

This commit is contained in:
usbharu 2024-08-15 00:02:26 +09:00
parent 60d71e1eb2
commit 88a61ba97f
Signed by: usbharu
GPG Key ID: 6556747BF94EEBC8
13 changed files with 224 additions and 48 deletions

View File

@ -1,14 +1,12 @@
package dev.usbharu.hideout.core.application.post package dev.usbharu.hideout.core.application.post
import dev.usbharu.hideout.core.domain.model.actor.Actor import dev.usbharu.hideout.core.domain.model.actor.Actor
import dev.usbharu.hideout.core.domain.model.instance.Instance
import dev.usbharu.hideout.core.domain.model.media.Media import dev.usbharu.hideout.core.domain.model.media.Media
import java.net.URI import java.net.URI
data class ActorDetail( data class ActorDetail(
val actorId: Long, val actorId: Long,
val instanceId: Long, val instanceId: Long,
val instanceName: String,
val name: String, val name: String,
val domain: String, val domain: String,
val screenName: String, val screenName: String,
@ -17,11 +15,10 @@ data class ActorDetail(
val icon: URI?, val icon: URI?,
) { ) {
companion object { companion object {
fun of(actor: Actor, instance: Instance, iconMedia: Media?): ActorDetail { fun of(actor: Actor, iconMedia: Media?): ActorDetail {
return ActorDetail( return ActorDetail(
actor.id.id, actor.id.id,
actor.instance.instanceId, actor.instance.instanceId,
instance.name.name,
actor.name.name, actor.name.name,
actor.domain.domain, actor.domain.domain,
actor.screenName.screenName, actor.screenName.screenName,

View File

@ -5,8 +5,6 @@ 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.actor.Actor import dev.usbharu.hideout.core.domain.model.actor.Actor
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.domain.model.instance.Instance
import dev.usbharu.hideout.core.domain.model.instance.InstanceRepository
import dev.usbharu.hideout.core.domain.model.media.Media import dev.usbharu.hideout.core.domain.model.media.Media
import dev.usbharu.hideout.core.domain.model.media.MediaRepository import dev.usbharu.hideout.core.domain.model.media.MediaRepository
import dev.usbharu.hideout.core.domain.model.post.PostId import dev.usbharu.hideout.core.domain.model.post.PostId
@ -21,7 +19,6 @@ class GetPostDetailApplicationService(
transaction: Transaction, transaction: Transaction,
private val postRepository: PostRepository, private val postRepository: PostRepository,
private val actorRepository: ActorRepository, private val actorRepository: ActorRepository,
private val instanceRepository: InstanceRepository,
private val mediaRepository: MediaRepository, private val mediaRepository: MediaRepository,
private val iPostReadAccessControl: IPostReadAccessControl private val iPostReadAccessControl: IPostReadAccessControl
) : AbstractApplicationService<GetPostDetail, PostDetail>( ) : AbstractApplicationService<GetPostDetail, PostDetail>(
@ -36,8 +33,6 @@ class GetPostDetailApplicationService(
} }
val actor = val actor =
actorRepository.findById(post.actorId) ?: throw InternalServerException("Actor ${post.actorId} not found.") actorRepository.findById(post.actorId) ?: throw InternalServerException("Actor ${post.actorId} not found.")
val instance = instanceRepository.findById(post.instanceId)
?: throw InternalServerException("Instance ${post.instanceId} not found.")
val iconMedia = actor.icon?.let { mediaRepository.findById(it) } val iconMedia = actor.icon?.let { mediaRepository.findById(it) }
@ -46,19 +41,17 @@ class GetPostDetailApplicationService(
return PostDetail.of( return PostDetail.of(
post, post,
actor, actor,
instance,
iconMedia, iconMedia,
mediaList, mediaList,
post.replyId?.let { fetchChild(it, actor, instance, iconMedia, principal) }, post.replyId?.let { fetchChild(it, actor, iconMedia, principal) },
post.repostId?.let { fetchChild(it, actor, instance, iconMedia, principal) }, post.repostId?.let { fetchChild(it, actor, iconMedia, principal) },
post.moveTo?.let { fetchChild(it, actor, instance, iconMedia, principal) }, post.moveTo?.let { fetchChild(it, actor, iconMedia, principal) },
) )
} }
private suspend fun fetchChild( private suspend fun fetchChild(
postId: PostId, postId: PostId,
actor: Actor, actor: Actor,
instance: Instance,
iconMedia: Media?, iconMedia: Media?,
principal: Principal principal: Principal
): PostDetail? { ): PostDetail? {
@ -68,21 +61,16 @@ class GetPostDetailApplicationService(
return null return null
} }
val (first, second: Instance, third) = if (actor.id != post.actorId) { val (first, third) = if (actor.id != post.actorId) {
Triple( (actorRepository.findById(post.actorId) ?: return null) to actor.icon?.let { mediaRepository.findById(it) }
actorRepository.findById(post.actorId) ?: return null,
instanceRepository.findById(actor.instance) ?: return null,
actor.icon?.let { mediaRepository.findById(it) }
)
} else { } else {
Triple(actor, instance, iconMedia) actor to iconMedia
} }
val mediaList = mediaRepository.findByIds(post.mediaIds) val mediaList = mediaRepository.findByIds(post.mediaIds)
return PostDetail.of( return PostDetail.of(
post, post,
first, first,
second,
third, third,
mediaList mediaList
) )

View File

@ -1,7 +1,6 @@
package dev.usbharu.hideout.core.application.post package dev.usbharu.hideout.core.application.post
import dev.usbharu.hideout.core.domain.model.actor.Actor import dev.usbharu.hideout.core.domain.model.actor.Actor
import dev.usbharu.hideout.core.domain.model.instance.Instance
import dev.usbharu.hideout.core.domain.model.media.Media import dev.usbharu.hideout.core.domain.model.media.Media
import dev.usbharu.hideout.core.domain.model.post.Post import dev.usbharu.hideout.core.domain.model.post.Post
import dev.usbharu.hideout.core.domain.model.post.Visibility import dev.usbharu.hideout.core.domain.model.post.Visibility
@ -30,7 +29,6 @@ data class PostDetail(
fun of( fun of(
post: Post, post: Post,
actor: Actor, actor: Actor,
instance: Instance,
iconMedia: Media?, iconMedia: Media?,
mediaList: List<Media>, mediaList: List<Media>,
reply: PostDetail? = null, reply: PostDetail? = null,
@ -39,7 +37,7 @@ data class PostDetail(
): PostDetail { ): PostDetail {
return PostDetail( return PostDetail(
id = post.id.id, id = post.id.id,
actor = ActorDetail.of(actor, instance, iconMedia), actor = ActorDetail.of(actor, iconMedia),
overview = post.overview?.overview, overview = post.overview?.overview,
text = post.text, text = post.text,
content = post.content.content, content = post.content.content,

View File

@ -0,0 +1,11 @@
package dev.usbharu.hideout.core.application.timeline
import dev.usbharu.hideout.core.domain.model.support.page.Page
data class ReadTimeline(
val timelineId: Long,
val mediaOnly: Boolean,
val localOnly: Boolean,
val remoteOnly: Boolean,
val page: Page
)

View File

@ -0,0 +1,47 @@
package dev.usbharu.hideout.core.application.timeline
import dev.usbharu.hideout.core.application.shared.AbstractApplicationService
import dev.usbharu.hideout.core.application.shared.Transaction
import dev.usbharu.hideout.core.domain.model.post.PostId
import dev.usbharu.hideout.core.domain.model.support.page.PaginationList
import dev.usbharu.hideout.core.domain.model.support.principal.Principal
import dev.usbharu.hideout.core.domain.model.support.timelineobjectdetail.TimelineObjectDetail
import dev.usbharu.hideout.core.domain.model.timeline.TimelineId
import dev.usbharu.hideout.core.domain.model.timeline.TimelineRepository
import dev.usbharu.hideout.core.external.timeline.ReadTimelineOption
import dev.usbharu.hideout.core.external.timeline.TimelineStore
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
@Service
class ReadTimelineApplicationService(
private val timelineStore: TimelineStore,
private val timelineRepository: TimelineRepository,
transaction: Transaction
) :
AbstractApplicationService<ReadTimeline, PaginationList<TimelineObjectDetail, PostId>>(transaction, logger) {
override suspend fun internalExecute(
command: ReadTimeline,
principal: Principal
): PaginationList<TimelineObjectDetail, PostId> {
val findById = timelineRepository.findById(TimelineId(command.timelineId))
?: throw IllegalArgumentException("Timeline ${command.timelineId} not found.")
val readTimelineOption = ReadTimelineOption(
command.mediaOnly,
command.localOnly,
command.remoteOnly
)
return timelineStore.readTimeline(
findById,
readTimelineOption,
command.page,
principal,
)
}
companion object {
private val logger = LoggerFactory.getLogger(ReadTimelineApplicationService::class.java)
}
}

View File

@ -22,6 +22,18 @@ interface RelationshipRepository {
suspend fun save(relationship: Relationship): Relationship suspend fun save(relationship: Relationship): Relationship
suspend fun delete(relationship: Relationship) suspend fun delete(relationship: Relationship)
suspend fun findByActorIdAndTargetId(actorId: ActorId, targetId: ActorId): Relationship? suspend fun findByActorIdAndTargetId(actorId: ActorId, targetId: ActorId): Relationship?
suspend fun findByActorIdsAndTargetIdAndBlocking(
actorIds: List<ActorId>,
targetId: ActorId,
blocking: Boolean
): List<Relationship>
suspend fun findByActorIdAndTargetIdsAndFollowing(
actorId: ActorId,
targetIds: List<ActorId>,
following: Boolean
): List<Relationship>
suspend fun findByTargetId( suspend fun findByTargetId(
targetId: ActorId, targetId: ActorId,
option: FindRelationshipOption? = null, option: FindRelationshipOption? = null,

View File

@ -1,6 +1,7 @@
package dev.usbharu.hideout.core.domain.model.support.timelineobjectdetail package dev.usbharu.hideout.core.domain.model.support.timelineobjectdetail
import dev.usbharu.hideout.core.domain.model.actor.Actor 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 import dev.usbharu.hideout.core.domain.model.post.Post
import dev.usbharu.hideout.core.domain.model.post.PostId import dev.usbharu.hideout.core.domain.model.post.PostId
import dev.usbharu.hideout.core.domain.model.timelineobject.TimelineObject import dev.usbharu.hideout.core.domain.model.timelineobject.TimelineObject
@ -14,11 +15,17 @@ data class TimelineObjectDetail(
val postId: PostId, val postId: PostId,
val timelineUserDetail: UserDetail, val timelineUserDetail: UserDetail,
val post: Post, val post: Post,
val postMedias: List<Media>,
val postActor: Actor, val postActor: Actor,
val postActorIconMedia: Media?,
val replyPost: Post?, val replyPost: Post?,
val replyPostMedias: List<Media>?,
val replyPostActor: Actor?, val replyPostActor: Actor?,
val replyPostActorIconMedia: Media?,
val repostPost: Post?, val repostPost: Post?,
val repostPostMedias: List<Media>?,
val repostPostActor: Actor?, val repostPostActor: Actor?,
val repostPostActorIconMedia: Media?,
val isPureRepost: Boolean, val isPureRepost: Boolean,
val lastUpdateAt: Instant, val lastUpdateAt: Instant,
val hasMediaInRepost: Boolean, val hasMediaInRepost: Boolean,
@ -29,27 +36,39 @@ data class TimelineObjectDetail(
timelineObject: TimelineObject, timelineObject: TimelineObject,
timelineUserDetail: UserDetail, timelineUserDetail: UserDetail,
post: Post, post: Post,
postMedias: List<Media>,
postActor: Actor, postActor: Actor,
postActorIconMedia: Media?,
replyPost: Post?, replyPost: Post?,
replyPostMedias: List<Media>?,
replyPostActor: Actor?, replyPostActor: Actor?,
replyPostActorIconMedia: Media?,
repostPost: Post?, repostPost: Post?,
repostPostMedias: List<Media>?,
repostPostActor: Actor?, repostPostActor: Actor?,
repostPostActorIconMedia: Media?,
warnFilter: List<TimelineObjectWarnFilter> warnFilter: List<TimelineObjectWarnFilter>
): TimelineObjectDetail { ): TimelineObjectDetail {
return TimelineObjectDetail( return TimelineObjectDetail(
timelineObject.id, id = timelineObject.id,
post.id, postId = post.id,
timelineUserDetail, timelineUserDetail = timelineUserDetail,
post, post = post,
postActor, postMedias = postMedias,
replyPost, postActor = postActor,
replyPostActor, postActorIconMedia = postActorIconMedia,
repostPost, replyPost = replyPost,
repostPostActor, replyPostMedias = replyPostMedias,
timelineObject.isPureRepost, replyPostActor = replyPostActor,
timelineObject.lastUpdatedAt, replyPostActorIconMedia = replyPostActorIconMedia,
timelineObject.hasMediaInRepost, repostPost = repostPost,
warnFilter repostPostMedias = repostPostMedias,
repostPostActor = repostPostActor,
repostPostActorIconMedia = repostPostActorIconMedia,
isPureRepost = timelineObject.isPureRepost,
lastUpdateAt = timelineObject.lastUpdatedAt,
hasMediaInRepost = timelineObject.hasMediaInRepost,
warnFilter = warnFilter
) )
} }
} }

View File

@ -10,6 +10,7 @@ import org.springframework.stereotype.Component
interface IPostReadAccessControl { interface IPostReadAccessControl {
suspend fun isAllow(post: Post, principal: Principal): Boolean suspend fun isAllow(post: Post, principal: Principal): Boolean
suspend fun areAllows(postList: List<Post>, principal: Principal): List<Post>
} }
@Component @Component
@ -22,9 +23,9 @@ class DefaultPostReadAccessControl(private val relationshipRepository: Relations
} }
val relationship = ( val relationship = (
relationshipRepository.findByActorIdAndTargetId(post.actorId, principal.actorId) relationshipRepository.findByActorIdAndTargetId(post.actorId, principal.actorId)
?: Relationship.default(post.actorId, principal.actorId) ?: Relationship.default(post.actorId, principal.actorId)
) )
// ブロックされてたら見れない // ブロックされてたら見れない
if (relationship.blocking) { if (relationship.blocking) {
@ -57,4 +58,47 @@ class DefaultPostReadAccessControl(private val relationshipRepository: Relations
// その他の場合は見れない // その他の場合は見れない
return false return false
} }
override suspend fun areAllows(postList: List<Post>, principal: Principal): List<Post> {
val actorIds = postList.map { it.actorId }
val relationshipList =
relationshipRepository.findByActorIdsAndTargetIdAndBlocking(actorIds, principal.actorId, true)
.map { it.actorId }
val inverseRelationshipList =
relationshipRepository.findByActorIdAndTargetIdsAndFollowing(principal.actorId, actorIds, true)
.map { it.actorId }
fun internalAllow(post: Post): Boolean {
// ポスト主は無条件で見れる
if (post.actorId == principal.actorId) {
return true
}
if (relationshipList.contains(post.actorId)) {
return false
}
if (post.visibility == Visibility.PUBLIC || post.visibility == Visibility.UNLISTED) {
return true
}
if (principal is Anonymous) {
return false
}
if (post.visibility == Visibility.DIRECT && post.visibleActors.contains(principal.actorId)) {
return true
}
if (post.visibility == Visibility.FOLLOWERS && inverseRelationshipList.contains(principal.actorId)) {
return true
}
return false
}
return postList
.filter {
internalAllow(it)
}
}
} }

View File

@ -4,6 +4,7 @@ import dev.usbharu.hideout.core.domain.model.post.Post
import dev.usbharu.hideout.core.domain.model.post.PostId import dev.usbharu.hideout.core.domain.model.post.PostId
import dev.usbharu.hideout.core.domain.model.support.page.Page import dev.usbharu.hideout.core.domain.model.support.page.Page
import dev.usbharu.hideout.core.domain.model.support.page.PaginationList import dev.usbharu.hideout.core.domain.model.support.page.PaginationList
import dev.usbharu.hideout.core.domain.model.support.principal.Principal
import dev.usbharu.hideout.core.domain.model.support.timelineobjectdetail.TimelineObjectDetail import dev.usbharu.hideout.core.domain.model.support.timelineobjectdetail.TimelineObjectDetail
import dev.usbharu.hideout.core.domain.model.timeline.Timeline import dev.usbharu.hideout.core.domain.model.timeline.Timeline
import dev.usbharu.hideout.core.domain.model.timelinerelationship.TimelineRelationship import dev.usbharu.hideout.core.domain.model.timelinerelationship.TimelineRelationship
@ -22,6 +23,7 @@ interface TimelineStore {
suspend fun readTimeline( suspend fun readTimeline(
timeline: Timeline, timeline: Timeline,
option: ReadTimelineOption? = null, option: ReadTimelineOption? = null,
page: Page? = null page: Page? = null,
principal: Principal
): PaginationList<TimelineObjectDetail, PostId> ): PaginationList<TimelineObjectDetail, PostId>
} }

View File

@ -67,6 +67,26 @@ class ExposedRelationshipRepository(override val domainEventPublisher: DomainEve
}.singleOrNull()?.toRelationships() }.singleOrNull()?.toRelationships()
} }
override suspend fun findByActorIdsAndTargetIdAndBlocking(
actorIds: List<ActorId>,
targetId: ActorId,
blocking: Boolean
): List<Relationship> = query {
Relationships.selectAll().where {
Relationships.actorId inList actorIds.map { it.id } and (Relationships.targetActorId eq targetId.id)
}.map { it.toRelationships() }
}
override suspend fun findByActorIdAndTargetIdsAndFollowing(
actorId: ActorId,
targetIds: List<ActorId>,
following: Boolean
): List<Relationship> = query {
Relationships.selectAll().where {
Relationships.actorId eq actorId.id and (Relationships.targetActorId inList targetIds.map { it.id })
}.map { it.toRelationships() }
}
override suspend fun findByTargetId( override suspend fun findByTargetId(
targetId: ActorId, targetId: ActorId,
option: FindRelationshipOption?, option: FindRelationshipOption?,

View File

@ -4,11 +4,14 @@ 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.actor.ActorId
import dev.usbharu.hideout.core.domain.model.filter.Filter import dev.usbharu.hideout.core.domain.model.filter.Filter
import dev.usbharu.hideout.core.domain.model.filter.FilteredPost import dev.usbharu.hideout.core.domain.model.filter.FilteredPost
import dev.usbharu.hideout.core.domain.model.media.Media
import dev.usbharu.hideout.core.domain.model.media.MediaId
import dev.usbharu.hideout.core.domain.model.post.Post import dev.usbharu.hideout.core.domain.model.post.Post
import dev.usbharu.hideout.core.domain.model.post.PostId import dev.usbharu.hideout.core.domain.model.post.PostId
import dev.usbharu.hideout.core.domain.model.post.Visibility import dev.usbharu.hideout.core.domain.model.post.Visibility
import dev.usbharu.hideout.core.domain.model.support.page.Page import dev.usbharu.hideout.core.domain.model.support.page.Page
import dev.usbharu.hideout.core.domain.model.support.page.PaginationList import dev.usbharu.hideout.core.domain.model.support.page.PaginationList
import dev.usbharu.hideout.core.domain.model.support.principal.Principal
import dev.usbharu.hideout.core.domain.model.support.timelineobjectdetail.TimelineObjectDetail import dev.usbharu.hideout.core.domain.model.support.timelineobjectdetail.TimelineObjectDetail
import dev.usbharu.hideout.core.domain.model.timeline.Timeline import dev.usbharu.hideout.core.domain.model.timeline.Timeline
import dev.usbharu.hideout.core.domain.model.timeline.TimelineId import dev.usbharu.hideout.core.domain.model.timeline.TimelineId
@ -105,7 +108,7 @@ abstract class AbstractTimelineStore(private val idGenerateService: IdGenerateSe
protected abstract suspend fun getPostsByTimelineRelationshipList(timelineRelationshipList: List<TimelineRelationship>): List<Post> protected abstract suspend fun getPostsByTimelineRelationshipList(timelineRelationshipList: List<TimelineRelationship>): List<Post>
protected abstract suspend fun getPostsByPostId(postIds: List<PostId>): List<Post> protected abstract suspend fun getPostsByPostId(postIds: List<PostId>, principal: Principal): List<Post>
protected abstract suspend fun getTimelineObject( protected abstract suspend fun getTimelineObject(
timelineId: TimelineId, timelineId: TimelineId,
@ -205,7 +208,8 @@ abstract class AbstractTimelineStore(private val idGenerateService: IdGenerateSe
override suspend fun readTimeline( override suspend fun readTimeline(
timeline: Timeline, timeline: Timeline,
option: ReadTimelineOption?, option: ReadTimelineOption?,
page: Page? page: Page?,
principal: Principal
): PaginationList<TimelineObjectDetail, PostId> { ): PaginationList<TimelineObjectDetail, PostId> {
val timelineObjectList = getTimelineObject(timeline.id, option, page) val timelineObjectList = getTimelineObject(timeline.id, option, page)
val lastUpdatedAt = timelineObjectList.minBy { it.lastUpdatedAt }.lastUpdatedAt val lastUpdatedAt = timelineObjectList.minBy { it.lastUpdatedAt }.lastUpdatedAt
@ -216,7 +220,8 @@ abstract class AbstractTimelineStore(private val idGenerateService: IdGenerateSe
getPostsByPostId( getPostsByPostId(
timelineObjectList.map { timelineObjectList.map {
it.postId it.postId
} + timelineObjectList.mapNotNull { it.repostId } + timelineObjectList.mapNotNull { it.replyId } } + timelineObjectList.mapNotNull { it.repostId } + timelineObjectList.mapNotNull { it.replyId },
principal
) )
val userDetails = getUserDetails(timelineObjectList.map { it.userDetailId }) val userDetails = getUserDetails(timelineObjectList.map { it.userDetailId })
@ -232,24 +237,35 @@ abstract class AbstractTimelineStore(private val idGenerateService: IdGenerateSe
post.id to applyFilters(post, newerFilters) post.id to applyFilters(post, newerFilters)
} }
val mediaMap = getMedias(posts.flatMap { it.mediaIds } + actors.mapNotNull { it.value.icon })
return PaginationList( return PaginationList(
timelineObjectList.mapNotNull<TimelineObject, TimelineObjectDetail> { timelineObjectList.mapNotNull<TimelineObject, TimelineObjectDetail> {
val timelineUserDetail = userDetails[it.userDetailId] ?: return@mapNotNull null val timelineUserDetail = userDetails[it.userDetailId] ?: return@mapNotNull null
val actor = actors[it.postActorId] ?: return@mapNotNull null val actor = actors[it.postActorId] ?: return@mapNotNull null
val post = postMap[it.postId] ?: return@mapNotNull null val post = postMap[it.postId] ?: return@mapNotNull null
val postMedias = post.post.mediaIds.mapNotNull { mediaId -> mediaMap[mediaId] }
val reply = postMap[it.replyId] val reply = postMap[it.replyId]
val replyMedias = reply?.post?.mediaIds?.mapNotNull { mediaId -> mediaMap[mediaId] }
val replyActor = actors[it.replyActorId] val replyActor = actors[it.replyActorId]
val repost = postMap[it.repostId] val repost = postMap[it.repostId]
val repostMedias = repost?.post?.mediaIds?.mapNotNull { mediaId -> mediaMap[mediaId] }
val repostActor = actors[it.repostActorId] val repostActor = actors[it.repostActorId]
TimelineObjectDetail.of( TimelineObjectDetail.of(
timelineObject = it, timelineObject = it,
timelineUserDetail = timelineUserDetail, timelineUserDetail = timelineUserDetail,
post = post.post, post = post.post,
postMedias = postMedias,
postActor = actor, postActor = actor,
postActorIconMedia = mediaMap[actor.icon],
replyPost = reply?.post, replyPost = reply?.post,
replyPostMedias = replyMedias,
replyPostActor = replyActor, replyPostActor = replyActor,
replyPostActorIconMedia = mediaMap[replyActor?.icon],
repostPost = repost?.post, repostPost = repost?.post,
repostPostMedias = repostMedias,
repostPostActor = repostActor, repostPostActor = repostActor,
repostPostActorIconMedia = mediaMap[repostActor?.icon],
warnFilter = it.warnFilters + post.filterResults.map { warnFilter = it.warnFilters + post.filterResults.map {
TimelineObjectWarnFilter( TimelineObjectWarnFilter(
it.filter.id, it.filter.id,
@ -265,5 +281,7 @@ abstract class AbstractTimelineStore(private val idGenerateService: IdGenerateSe
protected abstract suspend fun getActors(actorIds: List<ActorId>): Map<ActorId, Actor> protected abstract suspend fun getActors(actorIds: List<ActorId>): Map<ActorId, Actor>
protected abstract suspend fun getMedias(mediaIds: List<MediaId>): Map<MediaId, Media>
protected abstract suspend fun getUserDetails(userDetailIdList: List<UserDetailId>): Map<UserDetailId, UserDetail> protected abstract suspend fun getUserDetails(userDetailIdList: List<UserDetailId>): Map<UserDetailId, UserDetail>
} }

View File

@ -8,12 +8,16 @@ import dev.usbharu.hideout.core.domain.model.filter.Filter
import dev.usbharu.hideout.core.domain.model.filter.FilterContext import dev.usbharu.hideout.core.domain.model.filter.FilterContext
import dev.usbharu.hideout.core.domain.model.filter.FilterRepository import dev.usbharu.hideout.core.domain.model.filter.FilterRepository
import dev.usbharu.hideout.core.domain.model.filter.FilteredPost import dev.usbharu.hideout.core.domain.model.filter.FilteredPost
import dev.usbharu.hideout.core.domain.model.media.Media
import dev.usbharu.hideout.core.domain.model.media.MediaId
import dev.usbharu.hideout.core.domain.model.media.MediaRepository
import dev.usbharu.hideout.core.domain.model.post.Post import dev.usbharu.hideout.core.domain.model.post.Post
import dev.usbharu.hideout.core.domain.model.post.PostId import dev.usbharu.hideout.core.domain.model.post.PostId
import dev.usbharu.hideout.core.domain.model.post.PostRepository import dev.usbharu.hideout.core.domain.model.post.PostRepository
import dev.usbharu.hideout.core.domain.model.post.Visibility import dev.usbharu.hideout.core.domain.model.post.Visibility
import dev.usbharu.hideout.core.domain.model.support.page.Page import dev.usbharu.hideout.core.domain.model.support.page.Page
import dev.usbharu.hideout.core.domain.model.support.page.PaginationList import dev.usbharu.hideout.core.domain.model.support.page.PaginationList
import dev.usbharu.hideout.core.domain.model.support.principal.Principal
import dev.usbharu.hideout.core.domain.model.timeline.Timeline import dev.usbharu.hideout.core.domain.model.timeline.Timeline
import dev.usbharu.hideout.core.domain.model.timeline.TimelineId import dev.usbharu.hideout.core.domain.model.timeline.TimelineId
import dev.usbharu.hideout.core.domain.model.timeline.TimelineRepository import dev.usbharu.hideout.core.domain.model.timeline.TimelineRepository
@ -24,6 +28,7 @@ import dev.usbharu.hideout.core.domain.model.userdetails.UserDetail
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailId import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailId
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailRepository import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailRepository
import dev.usbharu.hideout.core.domain.service.filter.FilterDomainService 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.domain.shared.id.IdGenerateService
import dev.usbharu.hideout.core.external.timeline.ReadTimelineOption import dev.usbharu.hideout.core.external.timeline.ReadTimelineOption
import org.springframework.stereotype.Component import org.springframework.stereotype.Component
@ -40,7 +45,9 @@ open class DefaultTimelineStore(
private val defaultTimelineStoreConfig: DefaultTimelineStoreConfig, private val defaultTimelineStoreConfig: DefaultTimelineStoreConfig,
private val internalTimelineObjectRepository: InternalTimelineObjectRepository, private val internalTimelineObjectRepository: InternalTimelineObjectRepository,
private val userDetailRepository: UserDetailRepository, private val userDetailRepository: UserDetailRepository,
private val actorRepository: ActorRepository private val actorRepository: ActorRepository,
private val mediaRepository: MediaRepository,
private val postIPostReadAccessControl: IPostReadAccessControl
) : AbstractTimelineStore(idGenerateService) { ) : AbstractTimelineStore(idGenerateService) {
override suspend fun getTimelines(actorId: ActorId): List<Timeline> { override suspend fun getTimelines(actorId: ActorId): List<Timeline> {
return timelineRepository.findByIds( return timelineRepository.findByIds(
@ -99,8 +106,9 @@ open class DefaultTimelineStore(
return timelineRelationshipList.flatMap { getActorPost(it.actorId, visibilities(it)) } return timelineRelationshipList.flatMap { getActorPost(it.actorId, visibilities(it)) }
} }
override suspend fun getPostsByPostId(postIds: List<PostId>): List<Post> { override suspend fun getPostsByPostId(postIds: List<PostId>, principal: Principal): List<Post> {
return postRepository.findAllById(postIds) val findAllById = postRepository.findAllById(postIds)
return postIPostReadAccessControl.areAllows(findAllById, principal)
} }
override suspend fun getTimelineObject( override suspend fun getTimelineObject(
@ -131,6 +139,10 @@ open class DefaultTimelineStore(
return actorRepository.findAllById(actorIds).associateBy { it.id } return actorRepository.findAllById(actorIds).associateBy { it.id }
} }
override suspend fun getMedias(mediaIds: List<MediaId>): Map<MediaId, Media> {
return mediaRepository.findByIds(mediaIds).associateBy { it.id }
}
override suspend fun getUserDetails(userDetailIdList: List<UserDetailId>): Map<UserDetailId, UserDetail> { override suspend fun getUserDetails(userDetailIdList: List<UserDetailId>): Map<UserDetailId, UserDetail> {
return userDetailRepository.findAllById(userDetailIdList).associateBy { it.id } return userDetailRepository.findAllById(userDetailIdList).associateBy { it.id }
} }

View File

@ -3,6 +3,7 @@ package dev.usbharu.hideout.core.infrastructure.timeline
import dev.usbharu.hideout.core.config.DefaultTimelineStoreConfig import dev.usbharu.hideout.core.config.DefaultTimelineStoreConfig
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.domain.model.filter.* import dev.usbharu.hideout.core.domain.model.filter.*
import dev.usbharu.hideout.core.domain.model.media.MediaRepository
import dev.usbharu.hideout.core.domain.model.post.PostRepository import dev.usbharu.hideout.core.domain.model.post.PostRepository
import dev.usbharu.hideout.core.domain.model.post.TestPostFactory import dev.usbharu.hideout.core.domain.model.post.TestPostFactory
import dev.usbharu.hideout.core.domain.model.post.Visibility import dev.usbharu.hideout.core.domain.model.post.Visibility
@ -16,6 +17,7 @@ import dev.usbharu.hideout.core.domain.model.timelinerelationship.Visible
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailId import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailId
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailRepository import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailRepository
import dev.usbharu.hideout.core.domain.service.filter.FilterDomainService import dev.usbharu.hideout.core.domain.service.filter.FilterDomainService
import dev.usbharu.hideout.core.domain.service.post.IPostReadAccessControl
import dev.usbharu.hideout.core.infrastructure.other.TwitterSnowflakeIdGenerateService import dev.usbharu.hideout.core.infrastructure.other.TwitterSnowflakeIdGenerateService
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
@ -56,6 +58,12 @@ class DefaultTimelineStoreTest {
@Mock @Mock
lateinit var actorRepository: ActorRepository lateinit var actorRepository: ActorRepository
@Mock
lateinit var mediaRepository: MediaRepository
@Mock
lateinit var iPostReadAccessControl: IPostReadAccessControl
@Spy @Spy
val defaultTimelineStoreConfig = DefaultTimelineStoreConfig(500) val defaultTimelineStoreConfig = DefaultTimelineStoreConfig(500)