mirror of https://github.com/usbharu/Hideout.git
commit
65d8502a18
|
@ -0,0 +1,5 @@
|
||||||
|
package dev.usbharu.hideout.core.application.domainevent.subscribers
|
||||||
|
|
||||||
|
data class RegisterHomeTimeline(
|
||||||
|
val userDetailId: Long
|
||||||
|
)
|
|
@ -0,0 +1,24 @@
|
||||||
|
package dev.usbharu.hideout.core.application.domainevent.subscribers
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.domain.event.userdetail.UserDetailEvent
|
||||||
|
import dev.usbharu.hideout.core.domain.event.userdetail.UserDetailEventBody
|
||||||
|
import dev.usbharu.hideout.core.domain.model.support.principal.Anonymous
|
||||||
|
import org.springframework.stereotype.Component
|
||||||
|
|
||||||
|
@Component
|
||||||
|
class RegisterLocalUserSetHomeTimelineSubscriber(
|
||||||
|
domainEventSubscriber: DomainEventSubscriber,
|
||||||
|
private val userRegisterHomeTimelineApplicationService: UserRegisterHomeTimelineApplicationService
|
||||||
|
) :
|
||||||
|
Subscriber {
|
||||||
|
init {
|
||||||
|
domainEventSubscriber.subscribe<UserDetailEventBody>(UserDetailEvent.CREATE.eventName) {
|
||||||
|
userRegisterHomeTimelineApplicationService.execute(
|
||||||
|
RegisterHomeTimeline(it.body.getUserDetail().id),
|
||||||
|
Anonymous
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo userdetailにdomain event付けて createのイベントで反応させる タイムラインを新しく一つ作って userdetailのhometimelineに紐づけて自分自身をtimleine relationshipに入れる
|
|
@ -0,0 +1,21 @@
|
||||||
|
package dev.usbharu.hideout.core.application.domainevent.subscribers
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.application.timeline.SetTimelineToTimelineStoreApplicationService
|
||||||
|
import dev.usbharu.hideout.core.application.timeline.SetTimleineStore
|
||||||
|
import dev.usbharu.hideout.core.domain.event.timeline.TimelineEvent
|
||||||
|
import dev.usbharu.hideout.core.domain.event.timeline.TimelineEventBody
|
||||||
|
import dev.usbharu.hideout.core.domain.model.support.principal.Anonymous
|
||||||
|
import org.springframework.stereotype.Component
|
||||||
|
|
||||||
|
@Component
|
||||||
|
class RegisterTimelineSetTimelineStoreSubscriber(
|
||||||
|
domainEventSubscriber: DomainEventSubscriber,
|
||||||
|
private val setTimelineToTimelineStoreApplicationService: SetTimelineToTimelineStoreApplicationService
|
||||||
|
) :
|
||||||
|
Subscriber {
|
||||||
|
init {
|
||||||
|
domainEventSubscriber.subscribe<TimelineEventBody>(TimelineEvent.CREATE.eventName) {
|
||||||
|
setTimelineToTimelineStoreApplicationService.execute(SetTimleineStore(it.body.getTimelineId()), Anonymous)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,18 +1,21 @@
|
||||||
package dev.usbharu.hideout.core.application.domainevent.subscribers
|
package dev.usbharu.hideout.core.application.domainevent.subscribers
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.application.timeline.AddPost
|
||||||
|
import dev.usbharu.hideout.core.application.timeline.TimelineAddPostApplicationService
|
||||||
import dev.usbharu.hideout.core.domain.event.post.PostEvent
|
import dev.usbharu.hideout.core.domain.event.post.PostEvent
|
||||||
import dev.usbharu.hideout.core.domain.event.post.PostEventBody
|
import dev.usbharu.hideout.core.domain.event.post.PostEventBody
|
||||||
|
import dev.usbharu.hideout.core.domain.model.support.principal.Anonymous
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import org.springframework.stereotype.Component
|
import org.springframework.stereotype.Component
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
class TimelinePostCreateSubscriber(domainEventSubscriber: DomainEventSubscriber) : Subscriber {
|
class TimelinePostCreateSubscriber(
|
||||||
|
private val timelineAddPostApplicationService: TimelineAddPostApplicationService,
|
||||||
|
domainEventSubscriber: DomainEventSubscriber,
|
||||||
|
) : Subscriber {
|
||||||
init {
|
init {
|
||||||
domainEventSubscriber.subscribe<PostEventBody>(PostEvent.CREATE.eventName) {
|
domainEventSubscriber.subscribe<PostEventBody>(PostEvent.CREATE.eventName) {
|
||||||
val post = it.body.getPost()
|
timelineAddPostApplicationService.execute(AddPost(it.body.getPostId()), Anonymous)
|
||||||
val actor = it.body.getActor()
|
|
||||||
|
|
||||||
logger.info("New Post! : {}", post)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,7 @@ class TimelineRelationshipFollowSubscriber(
|
||||||
AddTimelineRelationship(
|
AddTimelineRelationship(
|
||||||
TimelineRelationship(
|
TimelineRelationship(
|
||||||
TimelineRelationshipId(idGenerateService.generateId()),
|
TimelineRelationshipId(idGenerateService.generateId()),
|
||||||
userDetail.homeTimelineId,
|
userDetail.homeTimelineId!!,
|
||||||
relationship.targetActorId,
|
relationship.targetActorId,
|
||||||
Visible.FOLLOWERS
|
Visible.FOLLOWERS
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
package dev.usbharu.hideout.core.application.domainevent.subscribers
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.application.shared.AbstractApplicationService
|
||||||
|
import dev.usbharu.hideout.core.application.shared.Transaction
|
||||||
|
import dev.usbharu.hideout.core.domain.model.support.principal.Principal
|
||||||
|
import dev.usbharu.hideout.core.domain.model.timeline.*
|
||||||
|
import dev.usbharu.hideout.core.domain.model.timelinerelationship.TimelineRelationship
|
||||||
|
import dev.usbharu.hideout.core.domain.model.timelinerelationship.TimelineRelationshipId
|
||||||
|
import dev.usbharu.hideout.core.domain.model.timelinerelationship.TimelineRelationshipRepository
|
||||||
|
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.UserDetailRepository
|
||||||
|
import dev.usbharu.hideout.core.domain.shared.id.IdGenerateService
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import org.springframework.stereotype.Component
|
||||||
|
|
||||||
|
@Component
|
||||||
|
class UserRegisterHomeTimelineApplicationService(
|
||||||
|
private val userDetailRepository: UserDetailRepository,
|
||||||
|
private val timelineRepository: TimelineRepository,
|
||||||
|
private val idGenerateService: IdGenerateService,
|
||||||
|
transaction: Transaction,
|
||||||
|
private val timelineRelationshipRepository: TimelineRelationshipRepository
|
||||||
|
) : AbstractApplicationService<RegisterHomeTimeline, Unit>(transaction, logger) {
|
||||||
|
override suspend fun internalExecute(command: RegisterHomeTimeline, principal: Principal) {
|
||||||
|
val userDetail = (
|
||||||
|
userDetailRepository.findById(UserDetailId(command.userDetailId))
|
||||||
|
?: throw IllegalArgumentException("UserDetail ${command.userDetailId} not found.")
|
||||||
|
)
|
||||||
|
|
||||||
|
val timeline = Timeline.create(
|
||||||
|
TimelineId(idGenerateService.generateId()),
|
||||||
|
UserDetailId(command.userDetailId),
|
||||||
|
TimelineName("System-LocalUser-HomeTimeline-${command.userDetailId}"),
|
||||||
|
TimelineVisibility.PRIVATE,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
timelineRepository.save(timeline)
|
||||||
|
userDetail.homeTimelineId = timeline.id
|
||||||
|
|
||||||
|
val timelineRelationship = TimelineRelationship(
|
||||||
|
TimelineRelationshipId(idGenerateService.generateId()),
|
||||||
|
timeline.id,
|
||||||
|
userDetail.actorId,
|
||||||
|
Visible.DIRECT
|
||||||
|
)
|
||||||
|
|
||||||
|
timelineRelationshipRepository.save(timelineRelationship)
|
||||||
|
|
||||||
|
userDetailRepository.save(userDetail)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val logger = LoggerFactory.getLogger(UserRegisterHomeTimelineApplicationService::class.java)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
package dev.usbharu.hideout.core.application.timeline
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.domain.model.post.PostId
|
||||||
|
|
||||||
|
data class AddPost(val postId: PostId)
|
|
@ -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
|
||||||
|
)
|
|
@ -0,0 +1,82 @@
|
||||||
|
package dev.usbharu.hideout.core.application.timeline
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.application.post.PostDetail
|
||||||
|
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.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<PostDetail, PostId>>(transaction, logger) {
|
||||||
|
override suspend fun internalExecute(
|
||||||
|
command: ReadTimeline,
|
||||||
|
principal: Principal
|
||||||
|
): PaginationList<PostDetail, PostId> {
|
||||||
|
val findById = timelineRepository.findById(TimelineId(command.timelineId))
|
||||||
|
?: throw IllegalArgumentException("Timeline ${command.timelineId} not found.")
|
||||||
|
|
||||||
|
val readTimelineOption = ReadTimelineOption(
|
||||||
|
command.mediaOnly,
|
||||||
|
command.localOnly,
|
||||||
|
command.remoteOnly
|
||||||
|
)
|
||||||
|
|
||||||
|
val timeline = timelineStore.readTimeline(
|
||||||
|
findById,
|
||||||
|
readTimelineOption,
|
||||||
|
command.page,
|
||||||
|
principal,
|
||||||
|
)
|
||||||
|
|
||||||
|
val postDetailList = timeline.map {
|
||||||
|
val reply = if (it.replyPost != null) {
|
||||||
|
PostDetail.of(
|
||||||
|
it.replyPost,
|
||||||
|
it.replyPostActor!!,
|
||||||
|
it.replyPostActorIconMedia,
|
||||||
|
it.replyPostMedias.orEmpty()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
val repost = if (it.repostPost != null) {
|
||||||
|
PostDetail.of(
|
||||||
|
it.repostPost,
|
||||||
|
it.repostPostActor!!,
|
||||||
|
it.repostPostActorIconMedia,
|
||||||
|
it.repostPostMedias.orEmpty()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
PostDetail.of(
|
||||||
|
it.post,
|
||||||
|
it.postActor,
|
||||||
|
it.postActorIconMedia,
|
||||||
|
it.postMedias,
|
||||||
|
reply,
|
||||||
|
repost
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return PaginationList(postDetailList, timeline.next, timeline.prev)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val logger = LoggerFactory.getLogger(ReadTimelineApplicationService::class.java)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package dev.usbharu.hideout.core.application.timeline
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.domain.model.timeline.TimelineVisibility
|
||||||
|
|
||||||
|
data class RegisterTimeline(
|
||||||
|
val timelineName: String,
|
||||||
|
val visibility: TimelineVisibility
|
||||||
|
)
|
|
@ -0,0 +1,30 @@
|
||||||
|
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.support.principal.Principal
|
||||||
|
import dev.usbharu.hideout.core.domain.model.timeline.TimelineRepository
|
||||||
|
import dev.usbharu.hideout.core.external.timeline.TimelineStore
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import org.springframework.stereotype.Component
|
||||||
|
|
||||||
|
@Component
|
||||||
|
class SetTimelineToTimelineStoreApplicationService(
|
||||||
|
transaction: Transaction,
|
||||||
|
private val timelineStore: TimelineStore,
|
||||||
|
private val timelineRepository: TimelineRepository
|
||||||
|
) :
|
||||||
|
AbstractApplicationService<SetTimleineStore, Unit>(
|
||||||
|
transaction,
|
||||||
|
logger
|
||||||
|
) {
|
||||||
|
override suspend fun internalExecute(command: SetTimleineStore, principal: Principal) {
|
||||||
|
val findById = timelineRepository.findById(command.timelineId)
|
||||||
|
?: throw IllegalArgumentException("Timeline ${command.timelineId} not found")
|
||||||
|
timelineStore.addTimeline(findById, emptyList())
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val logger = LoggerFactory.getLogger(SetTimelineToTimelineStoreApplicationService::class.java)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
package dev.usbharu.hideout.core.application.timeline
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.domain.model.timeline.TimelineId
|
||||||
|
|
||||||
|
data class SetTimleineStore(val timelineId: TimelineId)
|
|
@ -0,0 +1,29 @@
|
||||||
|
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.PostRepository
|
||||||
|
import dev.usbharu.hideout.core.domain.model.support.principal.Principal
|
||||||
|
import dev.usbharu.hideout.core.external.timeline.TimelineStore
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import org.springframework.stereotype.Component
|
||||||
|
|
||||||
|
@Component
|
||||||
|
class TimelineAddPostApplicationService(
|
||||||
|
private val timelineStore: TimelineStore,
|
||||||
|
private val postRepository: PostRepository,
|
||||||
|
transaction: Transaction
|
||||||
|
) : AbstractApplicationService<AddPost, Unit>(
|
||||||
|
transaction,
|
||||||
|
logger
|
||||||
|
) {
|
||||||
|
override suspend fun internalExecute(command: AddPost, principal: Principal) {
|
||||||
|
val findById = postRepository.findById(command.postId)
|
||||||
|
?: throw IllegalArgumentException("Post ${command.postId} not found.")
|
||||||
|
timelineStore.addPost(findById)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val logger = LoggerFactory.getLogger(TimelineAddPostApplicationService::class.java)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package dev.usbharu.hideout.core.application.timeline
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.application.shared.LocalUserAbstractApplicationService
|
||||||
|
import dev.usbharu.hideout.core.application.shared.Transaction
|
||||||
|
import dev.usbharu.hideout.core.domain.model.support.principal.LocalUser
|
||||||
|
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.TimelineName
|
||||||
|
import dev.usbharu.hideout.core.domain.model.timeline.TimelineRepository
|
||||||
|
import dev.usbharu.hideout.core.domain.shared.id.IdGenerateService
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import org.springframework.stereotype.Component
|
||||||
|
|
||||||
|
@Component
|
||||||
|
class UserRegisterTimelineApplicationService(
|
||||||
|
private val idGenerateService: IdGenerateService,
|
||||||
|
private val timelineRepository: TimelineRepository,
|
||||||
|
transaction: Transaction
|
||||||
|
) :
|
||||||
|
LocalUserAbstractApplicationService<RegisterTimeline, TimelineId>(transaction, logger) {
|
||||||
|
override suspend fun internalExecute(command: RegisterTimeline, principal: LocalUser): TimelineId {
|
||||||
|
val timeline = Timeline.create(
|
||||||
|
id = TimelineId(idGenerateService.generateId()),
|
||||||
|
userDetailId = principal.userDetailId,
|
||||||
|
name = TimelineName(command.timelineName),
|
||||||
|
visibility = command.visibility,
|
||||||
|
isSystem = false
|
||||||
|
)
|
||||||
|
|
||||||
|
timelineRepository.save(timeline)
|
||||||
|
return timeline.id
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val logger = LoggerFactory.getLogger(UserRegisterTimelineApplicationService::class.java)
|
||||||
|
}
|
||||||
|
}
|
|
@ -52,7 +52,7 @@ import org.springframework.security.web.SecurityFilterChain
|
||||||
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint
|
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableWebSecurity(debug = true)
|
@EnableWebSecurity(debug = false)
|
||||||
class SecurityConfig {
|
class SecurityConfig {
|
||||||
@Bean
|
@Bean
|
||||||
fun passwordEncoder(): PasswordEncoder = BCryptPasswordEncoder()
|
fun passwordEncoder(): PasswordEncoder = BCryptPasswordEncoder()
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
package dev.usbharu.hideout.core.domain.event.actor
|
package dev.usbharu.hideout.core.domain.event.actor
|
||||||
|
|
||||||
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.ActorId
|
||||||
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEvent
|
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEvent
|
||||||
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventBody
|
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventBody
|
||||||
|
|
||||||
|
@ -24,13 +25,13 @@ class ActorDomainEventFactory(private val actor: Actor) {
|
||||||
fun createEvent(actorEvent: ActorEvent): DomainEvent<ActorEventBody> {
|
fun createEvent(actorEvent: ActorEvent): DomainEvent<ActorEventBody> {
|
||||||
return DomainEvent.create(
|
return DomainEvent.create(
|
||||||
actorEvent.eventName,
|
actorEvent.eventName,
|
||||||
ActorEventBody(actor),
|
ActorEventBody(actor.id),
|
||||||
actorEvent.collectable
|
actorEvent.collectable
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ActorEventBody(actor: Actor) : DomainEventBody(
|
class ActorEventBody(actor: ActorId) : DomainEventBody(
|
||||||
mapOf(
|
mapOf(
|
||||||
"actor" to actor
|
"actor" to actor
|
||||||
),
|
),
|
||||||
|
|
|
@ -17,7 +17,9 @@
|
||||||
package dev.usbharu.hideout.core.domain.event.post
|
package dev.usbharu.hideout.core.domain.event.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.actor.ActorId
|
||||||
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.shared.domainevent.DomainEvent
|
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEvent
|
||||||
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventBody
|
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventBody
|
||||||
|
|
||||||
|
@ -25,14 +27,14 @@ class PostDomainEventFactory(private val post: Post, private val actor: Actor? =
|
||||||
fun createEvent(postEvent: PostEvent): DomainEvent<PostEventBody> {
|
fun createEvent(postEvent: PostEvent): DomainEvent<PostEventBody> {
|
||||||
return DomainEvent.create(
|
return DomainEvent.create(
|
||||||
postEvent.eventName,
|
postEvent.eventName,
|
||||||
PostEventBody(post, actor)
|
PostEventBody(post.id, actor?.id)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PostEventBody(post: Post, actor: Actor?) : DomainEventBody(mapOf("post" to post, "actor" to actor)) {
|
class PostEventBody(post: PostId, actor: ActorId?) : DomainEventBody(mapOf("post" to post, "actor" to actor)) {
|
||||||
fun getPost(): Post = toMap()["post"] as Post
|
fun getPostId(): PostId = toMap()["post"] as PostId
|
||||||
fun getActor(): Actor? = toMap()["actor"] as Actor?
|
fun getActorId(): ActorId? = toMap()["actor"] as ActorId?
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class PostEvent(val eventName: String) {
|
enum class PostEvent(val eventName: String) {
|
||||||
|
|
|
@ -1,16 +1,22 @@
|
||||||
package dev.usbharu.hideout.core.domain.event.timeline
|
package dev.usbharu.hideout.core.domain.event.timeline
|
||||||
|
|
||||||
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.shared.domainevent.DomainEvent
|
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEvent
|
||||||
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventBody
|
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventBody
|
||||||
|
|
||||||
class TimelineEventFactory(private val timeline: Timeline) {
|
class TimelineEventFactory(private val timeline: Timeline) {
|
||||||
fun createEvent(timelineEvent: TimelineEvent): DomainEvent<TimelineEventBody> =
|
fun createEvent(timelineEvent: TimelineEvent): DomainEvent<TimelineEventBody> =
|
||||||
DomainEvent.create(timelineEvent.eventName, TimelineEventBody(timeline))
|
DomainEvent.create(timelineEvent.eventName, TimelineEventBody(timeline.id))
|
||||||
}
|
}
|
||||||
|
|
||||||
class TimelineEventBody(timeline: Timeline) : DomainEventBody(mapOf("timeline" to timeline))
|
class TimelineEventBody(timelineId: TimelineId) : DomainEventBody(mapOf("timeline" to timelineId)) {
|
||||||
|
fun getTimelineId(): TimelineId {
|
||||||
|
return toMap()["timeline"] as TimelineId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
enum class TimelineEvent(val eventName: String) {
|
enum class TimelineEvent(val eventName: String) {
|
||||||
CHANGE_VISIBILITY("ChangeVisibility")
|
CHANGE_VISIBILITY("ChangeVisibility"),
|
||||||
|
CREATE("TimelineCreate")
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
package dev.usbharu.hideout.core.domain.event.userdetail
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetail
|
||||||
|
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailId
|
||||||
|
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEvent
|
||||||
|
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventBody
|
||||||
|
|
||||||
|
class UserDetailDomainEventFactory(private val userDetail: UserDetail) {
|
||||||
|
fun createEvent(userDetailEvent: UserDetailEvent): DomainEvent<UserDetailEventBody> {
|
||||||
|
return DomainEvent.create(
|
||||||
|
userDetailEvent.eventName,
|
||||||
|
UserDetailEventBody(userDetail.id)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UserDetailEventBody(userDetail: UserDetailId) : DomainEventBody(
|
||||||
|
mapOf(
|
||||||
|
"userDetail" to userDetail
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
fun getUserDetail(): UserDetailId {
|
||||||
|
return toMap()["userDetail"] as UserDetailId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class UserDetailEvent(val eventName: String) {
|
||||||
|
CREATE("UserDetailCreate"),
|
||||||
|
}
|
|
@ -8,6 +8,5 @@ interface FilterRepository {
|
||||||
|
|
||||||
suspend fun findByFilterKeywordId(filterKeywordId: FilterKeywordId): Filter?
|
suspend fun findByFilterKeywordId(filterKeywordId: FilterKeywordId): Filter?
|
||||||
suspend fun findByFilterId(filterId: FilterId): Filter?
|
suspend fun findByFilterId(filterId: FilterId): Filter?
|
||||||
|
|
||||||
suspend fun findByUserDetailId(userDetailId: UserDetailId): List<Filter>
|
suspend fun findByUserDetailId(userDetailId: UserDetailId): List<Filter>
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,4 +25,24 @@ class Timeline(
|
||||||
|
|
||||||
var name = name
|
var name = name
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun create(
|
||||||
|
id: TimelineId,
|
||||||
|
userDetailId: UserDetailId,
|
||||||
|
name: TimelineName,
|
||||||
|
visibility: TimelineVisibility,
|
||||||
|
isSystem: Boolean
|
||||||
|
): Timeline {
|
||||||
|
val timeline = Timeline(
|
||||||
|
id = id,
|
||||||
|
userDetailId = userDetailId,
|
||||||
|
name = name,
|
||||||
|
visibility = visibility,
|
||||||
|
isSystem = isSystem
|
||||||
|
)
|
||||||
|
timeline.addDomainEvent(TimelineEventFactory(timeline).createEvent(TimelineEvent.CREATE))
|
||||||
|
return timeline
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,18 +16,21 @@
|
||||||
|
|
||||||
package dev.usbharu.hideout.core.domain.model.userdetails
|
package dev.usbharu.hideout.core.domain.model.userdetails
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.domain.event.userdetail.UserDetailDomainEventFactory
|
||||||
|
import dev.usbharu.hideout.core.domain.event.userdetail.UserDetailEvent
|
||||||
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.timeline.TimelineId
|
import dev.usbharu.hideout.core.domain.model.timeline.TimelineId
|
||||||
|
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventStorable
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
class UserDetail private constructor(
|
class UserDetail(
|
||||||
val id: UserDetailId,
|
val id: UserDetailId,
|
||||||
val actorId: ActorId,
|
val actorId: ActorId,
|
||||||
var password: UserDetailHashedPassword,
|
var password: UserDetailHashedPassword,
|
||||||
var autoAcceptFolloweeFollowRequest: Boolean,
|
var autoAcceptFolloweeFollowRequest: Boolean,
|
||||||
var lastMigration: Instant? = null,
|
var lastMigration: Instant? = null,
|
||||||
val homeTimelineId: TimelineId?
|
var homeTimelineId: TimelineId?
|
||||||
) {
|
) : DomainEventStorable() {
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
|
@ -49,7 +52,7 @@ class UserDetail private constructor(
|
||||||
lastMigration: Instant? = null,
|
lastMigration: Instant? = null,
|
||||||
homeTimelineId: TimelineId? = null
|
homeTimelineId: TimelineId? = null
|
||||||
): UserDetail {
|
): UserDetail {
|
||||||
return UserDetail(
|
val userDetail = UserDetail(
|
||||||
id,
|
id,
|
||||||
actorId,
|
actorId,
|
||||||
password,
|
password,
|
||||||
|
@ -57,6 +60,8 @@ class UserDetail private constructor(
|
||||||
lastMigration,
|
lastMigration,
|
||||||
homeTimelineId
|
homeTimelineId
|
||||||
)
|
)
|
||||||
|
userDetail.addDomainEvent(UserDetailDomainEventFactory(userDetail).createEvent(UserDetailEvent.CREATE))
|
||||||
|
return userDetail
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,10 @@
|
||||||
package dev.usbharu.hideout.core.infrastructure.exposedrepository
|
package dev.usbharu.hideout.core.infrastructure.exposedrepository
|
||||||
|
|
||||||
import dev.usbharu.hideout.core.domain.exception.SpringDataAccessExceptionSQLExceptionTranslator
|
import dev.usbharu.hideout.core.domain.exception.SpringDataAccessExceptionSQLExceptionTranslator
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import kotlinx.coroutines.slf4j.MDCContext
|
||||||
|
import org.jetbrains.exposed.sql.Transaction
|
||||||
|
import org.jetbrains.exposed.sql.statements.StatementInterceptor
|
||||||
import org.jetbrains.exposed.sql.transactions.TransactionManager
|
import org.jetbrains.exposed.sql.transactions.TransactionManager
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import org.springframework.beans.factory.annotation.Value
|
import org.springframework.beans.factory.annotation.Value
|
||||||
|
@ -35,7 +39,20 @@ abstract class AbstractRepository {
|
||||||
@Value("\${hideout.debug.trace-query-call:false}")
|
@Value("\${hideout.debug.trace-query-call:false}")
|
||||||
private var traceQueryCall: Boolean = false
|
private var traceQueryCall: Boolean = false
|
||||||
|
|
||||||
protected suspend fun <T> query(block: () -> T): T = try {
|
class TransactionInterceptor(private val transaction: Transaction) {
|
||||||
|
|
||||||
|
fun onComplete(block: suspend (transaction: Transaction) -> Unit) {
|
||||||
|
transaction.registerInterceptor(object : StatementInterceptor {
|
||||||
|
override fun afterCommit(transaction: Transaction) {
|
||||||
|
runBlocking(MDCContext()) {
|
||||||
|
block(transaction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected suspend fun <T> query(block: TransactionInterceptor.() -> T): T = try {
|
||||||
if (traceQueryCall) {
|
if (traceQueryCall) {
|
||||||
@Suppress("ThrowingExceptionsWithoutMessageOrCause")
|
@Suppress("ThrowingExceptionsWithoutMessageOrCause")
|
||||||
logger.trace(
|
logger.trace(
|
||||||
|
@ -49,7 +66,7 @@ ${Throwable().stackTrace.joinToString("\n")}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
block.invoke()
|
block.invoke(TransactionInterceptor(TransactionManager.current()))
|
||||||
} catch (e: SQLException) {
|
} catch (e: SQLException) {
|
||||||
if (traceQueryException) {
|
if (traceQueryException) {
|
||||||
logger.trace("FAILED EXECUTE SQL", e)
|
logger.trace("FAILED EXECUTE SQL", e)
|
||||||
|
|
|
@ -45,8 +45,11 @@ class ExposedActorInstanceRelationshipRepository(override val domainEventPublish
|
||||||
it[muting] = actorInstanceRelationship.muting
|
it[muting] = actorInstanceRelationship.muting
|
||||||
it[doNotSendPrivate] = actorInstanceRelationship.doNotSendPrivate
|
it[doNotSendPrivate] = actorInstanceRelationship.doNotSendPrivate
|
||||||
}
|
}
|
||||||
|
onComplete {
|
||||||
|
update(actorInstanceRelationship)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
update(actorInstanceRelationship)
|
|
||||||
return actorInstanceRelationship
|
return actorInstanceRelationship
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,8 +59,10 @@ class ExposedActorInstanceRelationshipRepository(override val domainEventPublish
|
||||||
actorId eq actorInstanceRelationship.actorId.id and
|
actorId eq actorInstanceRelationship.actorId.id and
|
||||||
(instanceId eq actorInstanceRelationship.instanceId.instanceId)
|
(instanceId eq actorInstanceRelationship.instanceId.instanceId)
|
||||||
}
|
}
|
||||||
|
onComplete {
|
||||||
|
update(actorInstanceRelationship)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
update(actorInstanceRelationship)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun findByActorIdAndInstanceId(
|
override suspend fun findByActorIdAndInstanceId(
|
||||||
|
|
|
@ -59,8 +59,11 @@ class ExposedActorRepository(
|
||||||
this[ActorsAlsoKnownAs.actorId] = actor.id.id
|
this[ActorsAlsoKnownAs.actorId] = actor.id.id
|
||||||
this[ActorsAlsoKnownAs.alsoKnownAs] = it.id
|
this[ActorsAlsoKnownAs.alsoKnownAs] = it.id
|
||||||
}
|
}
|
||||||
|
onComplete {
|
||||||
|
update(actor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
update(actor)
|
|
||||||
return actor
|
return actor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,8 +71,10 @@ class ExposedActorRepository(
|
||||||
query {
|
query {
|
||||||
Actors.deleteWhere { id eq actor.id.id }
|
Actors.deleteWhere { id eq actor.id.id }
|
||||||
ActorsAlsoKnownAs.deleteWhere { actorId eq actor.id.id }
|
ActorsAlsoKnownAs.deleteWhere { actorId eq actor.id.id }
|
||||||
|
onComplete {
|
||||||
|
update(actor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
update(actor)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun findById(id: ActorId): Actor? {
|
override suspend fun findById(id: ActorId): Actor? {
|
||||||
|
|
|
@ -59,22 +59,22 @@ class ExposedFilterRepository(private val filterQueryMapper: QueryMapper<Filter>
|
||||||
Filters.deleteWhere { id eq filter.id.id }
|
Filters.deleteWhere { id eq filter.id.id }
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun findByFilterKeywordId(filterKeywordId: FilterKeywordId): Filter? {
|
override suspend fun findByFilterKeywordId(filterKeywordId: FilterKeywordId): Filter? = query {
|
||||||
val filterId = FilterKeywords
|
val filterId = FilterKeywords
|
||||||
.selectAll()
|
.selectAll()
|
||||||
.where { FilterKeywords.id eq filterKeywordId.id }
|
.where { FilterKeywords.id eq filterKeywordId.id }
|
||||||
.firstOrNull()?.get(FilterKeywords.filterId) ?: return null
|
.firstOrNull()?.get(FilterKeywords.filterId) ?: return@query null
|
||||||
val where = Filters.selectAll().where { Filters.id eq filterId }
|
val where = Filters.selectAll().where { Filters.id eq filterId }
|
||||||
return filterQueryMapper.map(where).firstOrNull()
|
return@query filterQueryMapper.map(where).firstOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun findByFilterId(filterId: FilterId): Filter? {
|
override suspend fun findByFilterId(filterId: FilterId): Filter? = query {
|
||||||
val where = Filters.selectAll().where { Filters.id eq filterId.id }
|
val where = Filters.selectAll().where { Filters.id eq filterId.id }
|
||||||
return filterQueryMapper.map(where).firstOrNull()
|
return@query filterQueryMapper.map(where).firstOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun findByUserDetailId(userDetailId: UserDetailId): List<Filter> {
|
override suspend fun findByUserDetailId(userDetailId: UserDetailId): List<Filter> = query {
|
||||||
return Filters.selectAll().where { Filters.userId eq userDetailId.id }.let(filterQueryMapper::map)
|
return@query Filters.selectAll().where { Filters.userId eq userDetailId.id }.let(filterQueryMapper::map)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
@ -98,8 +98,11 @@ class ExposedPostRepository(
|
||||||
this[PostsVisibleActors.postId] = post.id.id
|
this[PostsVisibleActors.postId] = post.id.id
|
||||||
this[PostsVisibleActors.actorId] = it.id
|
this[PostsVisibleActors.actorId] = it.id
|
||||||
}
|
}
|
||||||
|
onComplete {
|
||||||
|
update(post)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
update(post)
|
|
||||||
return post
|
return post
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,9 +151,11 @@ class ExposedPostRepository(
|
||||||
this[PostsVisibleActors.postId] = it.first
|
this[PostsVisibleActors.postId] = it.first
|
||||||
this[PostsVisibleActors.actorId] = it.second
|
this[PostsVisibleActors.actorId] = it.second
|
||||||
}
|
}
|
||||||
}
|
onComplete {
|
||||||
posts.forEach {
|
posts.forEach {
|
||||||
update(it)
|
update(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return posts
|
return posts
|
||||||
}
|
}
|
||||||
|
@ -195,8 +200,10 @@ class ExposedPostRepository(
|
||||||
Posts.deleteWhere {
|
Posts.deleteWhere {
|
||||||
id eq post.id.id
|
id eq post.id.id
|
||||||
}
|
}
|
||||||
|
onComplete {
|
||||||
|
update(post)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
update(post)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun findByActorIdAndVisibilityInList(
|
override suspend fun findByActorIdAndVisibilityInList(
|
||||||
|
|
|
@ -47,8 +47,11 @@ class ExposedRelationshipRepository(override val domainEventPublisher: DomainEve
|
||||||
it[followRequesting] = relationship.followRequesting
|
it[followRequesting] = relationship.followRequesting
|
||||||
it[mutingFollowRequest] = relationship.mutingFollowRequest
|
it[mutingFollowRequest] = relationship.mutingFollowRequest
|
||||||
}
|
}
|
||||||
|
onComplete {
|
||||||
|
update(relationship)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
update(relationship)
|
|
||||||
return relationship
|
return relationship
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,8 +60,10 @@ class ExposedRelationshipRepository(override val domainEventPublisher: DomainEve
|
||||||
Relationships.deleteWhere {
|
Relationships.deleteWhere {
|
||||||
actorId eq relationship.actorId.id and (targetActorId eq relationship.targetActorId.id)
|
actorId eq relationship.actorId.id and (targetActorId eq relationship.targetActorId.id)
|
||||||
}
|
}
|
||||||
|
onComplete {
|
||||||
|
update(relationship)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
update(relationship)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun findByActorIdAndTargetId(actorId: ActorId, targetId: ActorId): Relationship? = query {
|
override suspend fun findByActorIdAndTargetId(actorId: ActorId, targetId: ActorId): Relationship? = query {
|
||||||
|
@ -67,6 +72,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?,
|
||||||
|
|
|
@ -24,8 +24,11 @@ class ExposedTimelineRepository(override val domainEventPublisher: DomainEventPu
|
||||||
it[visibility] = timeline.visibility.name
|
it[visibility] = timeline.visibility.name
|
||||||
it[isSystem] = timeline.isSystem
|
it[isSystem] = timeline.isSystem
|
||||||
}
|
}
|
||||||
|
onComplete {
|
||||||
|
update(timeline)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
update(timeline)
|
|
||||||
return timeline
|
return timeline
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,8 +37,10 @@ class ExposedTimelineRepository(override val domainEventPublisher: DomainEventPu
|
||||||
Timelines.deleteWhere {
|
Timelines.deleteWhere {
|
||||||
Timelines.id eq timeline.id.value
|
Timelines.id eq timeline.id.value
|
||||||
}
|
}
|
||||||
|
onComplete {
|
||||||
|
update(timeline)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
update(timeline)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun findByIds(ids: List<TimelineId>): List<Timeline> {
|
override suspend fun findByIds(ids: List<TimelineId>): List<Timeline> {
|
||||||
|
|
|
@ -22,6 +22,8 @@ import dev.usbharu.hideout.core.domain.model.userdetails.UserDetail
|
||||||
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailHashedPassword
|
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailHashedPassword
|
||||||
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.shared.domainevent.DomainEventPublisher
|
||||||
|
import dev.usbharu.hideout.core.domain.shared.repository.DomainEventPublishableRepository
|
||||||
import org.jetbrains.exposed.sql.*
|
import org.jetbrains.exposed.sql.*
|
||||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||||
import org.jetbrains.exposed.sql.javatime.timestamp
|
import org.jetbrains.exposed.sql.javatime.timestamp
|
||||||
|
@ -30,34 +32,51 @@ import org.slf4j.LoggerFactory
|
||||||
import org.springframework.stereotype.Repository
|
import org.springframework.stereotype.Repository
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
class UserDetailRepositoryImpl : UserDetailRepository, AbstractRepository() {
|
class UserDetailRepositoryImpl(override val domainEventPublisher: DomainEventPublisher) :
|
||||||
|
UserDetailRepository,
|
||||||
|
AbstractRepository(),
|
||||||
|
DomainEventPublishableRepository<UserDetail> {
|
||||||
override val logger: Logger
|
override val logger: Logger
|
||||||
get() = Companion.logger
|
get() = Companion.logger
|
||||||
|
|
||||||
override suspend fun save(userDetail: UserDetail): UserDetail = query {
|
override suspend fun save(userDetail: UserDetail): UserDetail {
|
||||||
val singleOrNull =
|
val userDetail1 = query {
|
||||||
UserDetails.selectAll().where { UserDetails.id eq userDetail.id.id }.forUpdate().singleOrNull()
|
val singleOrNull =
|
||||||
if (singleOrNull == null) {
|
UserDetails.selectAll().where { UserDetails.id eq userDetail.id.id }.forUpdate().singleOrNull()
|
||||||
UserDetails.insert {
|
if (singleOrNull == null) {
|
||||||
it[id] = userDetail.id.id
|
UserDetails.insert {
|
||||||
it[actorId] = userDetail.actorId.id
|
it[id] = userDetail.id.id
|
||||||
it[password] = userDetail.password.password
|
it[actorId] = userDetail.actorId.id
|
||||||
it[autoAcceptFolloweeFollowRequest] = userDetail.autoAcceptFolloweeFollowRequest
|
it[password] = userDetail.password.password
|
||||||
it[lastMigration] = userDetail.lastMigration
|
it[autoAcceptFolloweeFollowRequest] = userDetail.autoAcceptFolloweeFollowRequest
|
||||||
|
it[lastMigration] = userDetail.lastMigration
|
||||||
|
it[homeTimelineId] = userDetail.homeTimelineId?.value
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
UserDetails.update({ UserDetails.id eq userDetail.id.id }) {
|
||||||
|
it[actorId] = userDetail.actorId.id
|
||||||
|
it[password] = userDetail.password.password
|
||||||
|
it[autoAcceptFolloweeFollowRequest] = userDetail.autoAcceptFolloweeFollowRequest
|
||||||
|
it[lastMigration] = userDetail.lastMigration
|
||||||
|
it[homeTimelineId] = userDetail.homeTimelineId?.value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
onComplete {
|
||||||
UserDetails.update({ UserDetails.id eq userDetail.id.id }) {
|
update(userDetail)
|
||||||
it[actorId] = userDetail.actorId.id
|
|
||||||
it[password] = userDetail.password.password
|
|
||||||
it[autoAcceptFolloweeFollowRequest] = userDetail.autoAcceptFolloweeFollowRequest
|
|
||||||
it[lastMigration] = userDetail.lastMigration
|
|
||||||
}
|
}
|
||||||
|
userDetail
|
||||||
}
|
}
|
||||||
return@query userDetail
|
|
||||||
|
return userDetail1
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun delete(userDetail: UserDetail): Unit = query {
|
override suspend fun delete(userDetail: UserDetail) {
|
||||||
UserDetails.deleteWhere { id eq userDetail.id.id }
|
query {
|
||||||
|
UserDetails.deleteWhere { id eq userDetail.id.id }
|
||||||
|
onComplete {
|
||||||
|
update(userDetail)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun findByActorId(actorId: Long): UserDetail? = query {
|
override suspend fun findByActorId(actorId: Long): UserDetail? = query {
|
||||||
|
@ -89,7 +108,7 @@ class UserDetailRepositoryImpl : UserDetailRepository, AbstractRepository() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun userDetail(it: ResultRow) = UserDetail.create(
|
private fun userDetail(it: ResultRow) = UserDetail(
|
||||||
UserDetailId(it[UserDetails.id]),
|
UserDetailId(it[UserDetails.id]),
|
||||||
ActorId(it[UserDetails.actorId]),
|
ActorId(it[UserDetails.actorId]),
|
||||||
UserDetailHashedPassword(it[UserDetails.password]),
|
UserDetailHashedPassword(it[UserDetails.password]),
|
||||||
|
|
|
@ -19,6 +19,7 @@ import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.toList
|
import kotlinx.coroutines.flow.toList
|
||||||
|
import org.springframework.data.annotation.Id
|
||||||
import org.springframework.data.domain.Sort
|
import org.springframework.data.domain.Sort
|
||||||
import org.springframework.data.mongodb.core.MongoTemplate
|
import org.springframework.data.mongodb.core.MongoTemplate
|
||||||
import org.springframework.data.mongodb.core.mapping.Document
|
import org.springframework.data.mongodb.core.mapping.Document
|
||||||
|
@ -61,6 +62,22 @@ class MongoInternalTimelineObjectRepository(
|
||||||
springDataMongoTimelineObjectRepository.deleteByTimelineId(timelineId.value)
|
springDataMongoTimelineObjectRepository.deleteByTimelineId(timelineId.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun findByTimelineIdAndPostIdGT(timelineId: TimelineId, postId: PostId): TimelineObject? {
|
||||||
|
return springDataMongoTimelineObjectRepository.findFirstByTimelineIdAndPostIdGreaterThanOrderByIdAsc(
|
||||||
|
timelineId.value,
|
||||||
|
postId.id
|
||||||
|
)
|
||||||
|
?.toTimelineObject()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun findByTimelineIdAndPostIdLT(timelineId: TimelineId, postId: PostId): TimelineObject? {
|
||||||
|
return springDataMongoTimelineObjectRepository.findFirstByTimelineIdAndPostIdLessThanOrderByIdDesc(
|
||||||
|
timelineId.value,
|
||||||
|
postId.id
|
||||||
|
)
|
||||||
|
?.toTimelineObject()
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun findByTimelineId(
|
override suspend fun findByTimelineId(
|
||||||
timelineId: TimelineId,
|
timelineId: TimelineId,
|
||||||
internalTimelineObjectOption: InternalTimelineObjectOption?,
|
internalTimelineObjectOption: InternalTimelineObjectOption?,
|
||||||
|
@ -70,12 +87,12 @@ class MongoInternalTimelineObjectRepository(
|
||||||
|
|
||||||
if (page?.minId != null) {
|
if (page?.minId != null) {
|
||||||
query.with(Sort.by(Sort.Direction.ASC, "postCreatedAt"))
|
query.with(Sort.by(Sort.Direction.ASC, "postCreatedAt"))
|
||||||
page.minId?.let { query.addCriteria(Criteria.where("id").gt(it)) }
|
page.minId?.let { query.addCriteria(Criteria.where("postId").gt(it)) }
|
||||||
page.maxId?.let { query.addCriteria(Criteria.where("id").lt(it)) }
|
page.maxId?.let { query.addCriteria(Criteria.where("postId").lt(it)) }
|
||||||
} else {
|
} else {
|
||||||
query.with(Sort.by(Sort.Direction.DESC, "postCreatedAt"))
|
query.with(Sort.by(Sort.Direction.DESC, "postCreatedAt"))
|
||||||
page?.sinceId?.let { query.addCriteria(Criteria.where("id").gt(it)) }
|
page?.sinceId?.let { query.addCriteria(Criteria.where("postId").gt(it)) }
|
||||||
page?.maxId?.let { query.addCriteria(Criteria.where("id").lt(it)) }
|
page?.maxId?.let { query.addCriteria(Criteria.where("postId").lt(it)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
page?.limit?.let { query.limit(it) }
|
page?.limit?.let { query.limit(it) }
|
||||||
|
@ -83,16 +100,23 @@ class MongoInternalTimelineObjectRepository(
|
||||||
val timelineObjects =
|
val timelineObjects =
|
||||||
mongoTemplate.find(query, SpringDataMongoTimelineObject::class.java).map { it.toTimelineObject() }
|
mongoTemplate.find(query, SpringDataMongoTimelineObject::class.java).map { it.toTimelineObject() }
|
||||||
|
|
||||||
|
val objectList = if (page?.minId != null) {
|
||||||
|
timelineObjects.reversed()
|
||||||
|
} else {
|
||||||
|
timelineObjects
|
||||||
|
}
|
||||||
|
|
||||||
return PaginationList(
|
return PaginationList(
|
||||||
timelineObjects,
|
objectList,
|
||||||
timelineObjects.lastOrNull()?.postId,
|
objectList.lastOrNull()?.postId,
|
||||||
timelineObjects.firstOrNull()?.postId
|
objectList.firstOrNull()?.postId
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Document
|
@Document
|
||||||
data class SpringDataMongoTimelineObject(
|
data class SpringDataMongoTimelineObject(
|
||||||
|
@Id
|
||||||
val id: Long,
|
val id: Long,
|
||||||
val userDetailId: Long,
|
val userDetailId: Long,
|
||||||
val timelineId: Long,
|
val timelineId: Long,
|
||||||
|
@ -194,4 +218,14 @@ interface SpringDataMongoTimelineObjectRepository : CoroutineCrudRepository<Spri
|
||||||
suspend fun deleteByTimelineId(timelineId: Long)
|
suspend fun deleteByTimelineId(timelineId: Long)
|
||||||
|
|
||||||
suspend fun findByTimelineId(timelineId: TimelineId): Flow<SpringDataMongoTimelineObject>
|
suspend fun findByTimelineId(timelineId: TimelineId): Flow<SpringDataMongoTimelineObject>
|
||||||
|
|
||||||
|
suspend fun findFirstByTimelineIdAndPostIdGreaterThanOrderByIdAsc(
|
||||||
|
timelineId: Long,
|
||||||
|
postId: Long
|
||||||
|
): SpringDataMongoTimelineObject?
|
||||||
|
|
||||||
|
suspend fun findFirstByTimelineIdAndPostIdLessThanOrderByIdDesc(
|
||||||
|
timelineId: Long,
|
||||||
|
postId: Long
|
||||||
|
): SpringDataMongoTimelineObject?
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ package dev.usbharu.hideout.core.infrastructure.springframework.domainevent
|
||||||
|
|
||||||
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEvent
|
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEvent
|
||||||
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventPublisher
|
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventPublisher
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
import org.springframework.context.ApplicationEventPublisher
|
import org.springframework.context.ApplicationEventPublisher
|
||||||
import org.springframework.stereotype.Component
|
import org.springframework.stereotype.Component
|
||||||
|
|
||||||
|
@ -25,6 +26,11 @@ import org.springframework.stereotype.Component
|
||||||
class SpringFrameworkDomainEventPublisher(private val applicationEventPublisher: ApplicationEventPublisher) :
|
class SpringFrameworkDomainEventPublisher(private val applicationEventPublisher: ApplicationEventPublisher) :
|
||||||
DomainEventPublisher {
|
DomainEventPublisher {
|
||||||
override suspend fun publishEvent(domainEvent: DomainEvent<*>) {
|
override suspend fun publishEvent(domainEvent: DomainEvent<*>) {
|
||||||
|
logger.trace("Publish ${domainEvent.id} ${domainEvent.name}")
|
||||||
applicationEventPublisher.publishEvent(domainEvent)
|
applicationEventPublisher.publishEvent(domainEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val logger = LoggerFactory.getLogger(SpringFrameworkDomainEventPublisher::class.java)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import dev.usbharu.hideout.core.application.domainevent.subscribers.DomainEventC
|
||||||
import dev.usbharu.hideout.core.application.domainevent.subscribers.DomainEventSubscriber
|
import dev.usbharu.hideout.core.application.domainevent.subscribers.DomainEventSubscriber
|
||||||
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEvent
|
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEvent
|
||||||
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventBody
|
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventBody
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
import org.springframework.context.event.EventListener
|
import org.springframework.context.event.EventListener
|
||||||
import org.springframework.stereotype.Component
|
import org.springframework.stereotype.Component
|
||||||
|
|
||||||
|
@ -18,11 +19,17 @@ class SpringFrameworkDomainEventSubscriber : DomainEventSubscriber {
|
||||||
|
|
||||||
@EventListener
|
@EventListener
|
||||||
suspend fun onDomainEventPublished(domainEvent: DomainEvent<*>) {
|
suspend fun onDomainEventPublished(domainEvent: DomainEvent<*>) {
|
||||||
|
logger.trace("Domain Event Published: $domainEvent")
|
||||||
map[domainEvent.name]?.forEach {
|
map[domainEvent.name]?.forEach {
|
||||||
try {
|
try {
|
||||||
it.invoke(domainEvent)
|
it.invoke(domainEvent)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
logger.error("", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val logger = LoggerFactory.getLogger(SpringFrameworkDomainEventSubscriber::class.java)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
@ -202,12 +205,21 @@ abstract class AbstractTimelineStore(private val idGenerateService: IdGenerateSe
|
||||||
removeTimelineObject(timeline.id)
|
removeTimelineObject(timeline.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected abstract suspend fun getNextPaging(
|
||||||
|
timelineId: TimelineId,
|
||||||
|
page: Page?
|
||||||
|
): PaginationList<TimelineObjectDetail, PostId>
|
||||||
|
|
||||||
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)
|
||||||
|
if (timelineObjectList.isEmpty()) {
|
||||||
|
return getNextPaging(timeline.id, page)
|
||||||
|
}
|
||||||
val lastUpdatedAt = timelineObjectList.minBy { it.lastUpdatedAt }.lastUpdatedAt
|
val lastUpdatedAt = timelineObjectList.minBy { it.lastUpdatedAt }.lastUpdatedAt
|
||||||
|
|
||||||
val newerFilters = getNewerFilters(timeline.userDetailId, lastUpdatedAt)
|
val newerFilters = getNewerFilters(timeline.userDetailId, lastUpdatedAt)
|
||||||
|
@ -216,7 +228,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 +245,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 +289,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>
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,12 +8,17 @@ 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.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
|
||||||
import dev.usbharu.hideout.core.domain.model.timeline.TimelineRepository
|
import dev.usbharu.hideout.core.domain.model.timeline.TimelineRepository
|
||||||
|
@ -24,6 +29,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 +46,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(
|
||||||
|
@ -60,7 +68,7 @@ open class DefaultTimelineStore(
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getNewerFilters(userDetailId: UserDetailId, lastUpdateAt: Instant): List<Filter> {
|
override suspend fun getNewerFilters(userDetailId: UserDetailId, lastUpdateAt: Instant): List<Filter> {
|
||||||
TODO("Not yet implemented")
|
return filterRepository.findByUserDetailId(userDetailId)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun applyFilters(post: Post, filters: List<Filter>): FilteredPost {
|
override suspend fun applyFilters(post: Post, filters: List<Filter>): FilteredPost {
|
||||||
|
@ -99,8 +107,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(
|
||||||
|
@ -127,10 +136,36 @@ open class DefaultTimelineStore(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getNextPaging(
|
||||||
|
timelineId: TimelineId,
|
||||||
|
page: Page?
|
||||||
|
): PaginationList<TimelineObjectDetail, PostId> {
|
||||||
|
if (page?.maxId != null) {
|
||||||
|
return PaginationList(
|
||||||
|
emptyList(),
|
||||||
|
null,
|
||||||
|
internalTimelineObjectRepository.findByTimelineIdAndPostIdLT(timelineId, PostId(page.maxId!!))?.postId
|
||||||
|
?: PostId(0)
|
||||||
|
)
|
||||||
|
} else if (page?.minId != null) {
|
||||||
|
return PaginationList(
|
||||||
|
emptyList(),
|
||||||
|
internalTimelineObjectRepository.findByTimelineIdAndPostIdGT(timelineId, PostId(page.minId!!))?.postId
|
||||||
|
?: PostId(Long.MAX_VALUE),
|
||||||
|
null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return PaginationList(emptyList(), page?.maxId?.let { PostId(it) }, page?.minId?.let { PostId(it) })
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun getActors(actorIds: List<ActorId>): Map<ActorId, Actor> {
|
override suspend fun getActors(actorIds: List<ActorId>): Map<ActorId, Actor> {
|
||||||
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 }
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,17 @@ interface InternalTimelineObjectRepository {
|
||||||
suspend fun deleteByTimelineIdAndActorId(timelineId: TimelineId, actorId: ActorId)
|
suspend fun deleteByTimelineIdAndActorId(timelineId: TimelineId, actorId: ActorId)
|
||||||
|
|
||||||
suspend fun deleteByTimelineId(timelineId: TimelineId)
|
suspend fun deleteByTimelineId(timelineId: TimelineId)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指定したTimelineIdより大きく、近いものを返す
|
||||||
|
*/
|
||||||
|
suspend fun findByTimelineIdAndPostIdGT(timelineId: TimelineId, postId: PostId): TimelineObject?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指定したTimelineIdより小さく、近いものを返す
|
||||||
|
*/
|
||||||
|
suspend fun findByTimelineIdAndPostIdLT(timelineId: TimelineId, postId: PostId): TimelineObject?
|
||||||
|
|
||||||
suspend fun findByTimelineId(
|
suspend fun findByTimelineId(
|
||||||
timelineId: TimelineId,
|
timelineId: TimelineId,
|
||||||
internalTimelineObjectOption: InternalTimelineObjectOption? = null,
|
internalTimelineObjectOption: InternalTimelineObjectOption? = null,
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
package dev.usbharu.hideout.core.interfaces.web.timeline
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.application.exception.InternalServerException
|
||||||
|
import dev.usbharu.hideout.core.application.shared.Transaction
|
||||||
|
import dev.usbharu.hideout.core.application.timeline.ReadTimeline
|
||||||
|
import dev.usbharu.hideout.core.application.timeline.ReadTimelineApplicationService
|
||||||
|
import dev.usbharu.hideout.core.domain.model.support.page.Page
|
||||||
|
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailRepository
|
||||||
|
import dev.usbharu.hideout.core.infrastructure.springframework.SpringSecurityFormLoginPrincipalContextHolder
|
||||||
|
import org.springframework.stereotype.Controller
|
||||||
|
import org.springframework.ui.Model
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
class TimelineController(
|
||||||
|
private val readTimelineApplicationService: ReadTimelineApplicationService,
|
||||||
|
private val userDetailRepository: UserDetailRepository,
|
||||||
|
private val springSecurityFormLoginPrincipalContextHolder: SpringSecurityFormLoginPrincipalContextHolder,
|
||||||
|
private val transaction: Transaction
|
||||||
|
) {
|
||||||
|
@GetMapping("/home")
|
||||||
|
suspend fun homeTimeline(
|
||||||
|
model: Model,
|
||||||
|
@RequestParam sinceId: String?,
|
||||||
|
@RequestParam maxId: String?,
|
||||||
|
@RequestParam minId: String?
|
||||||
|
): String {
|
||||||
|
val principal = springSecurityFormLoginPrincipalContextHolder.getPrincipal()
|
||||||
|
val userDetail = transaction.transaction {
|
||||||
|
userDetailRepository.findByActorId(principal.actorId.id)
|
||||||
|
?: throw InternalServerException("UserDetail not found.")
|
||||||
|
}
|
||||||
|
|
||||||
|
val homeTimelineId = userDetail.homeTimelineId!!
|
||||||
|
val execute = readTimelineApplicationService.execute(
|
||||||
|
ReadTimeline(
|
||||||
|
timelineId = homeTimelineId.value,
|
||||||
|
mediaOnly = false,
|
||||||
|
localOnly = false,
|
||||||
|
remoteOnly = false,
|
||||||
|
page = Page.of(
|
||||||
|
maxId = maxId?.toLongOrNull(),
|
||||||
|
sinceId = sinceId?.toLongOrNull(),
|
||||||
|
minId = minId?.toLongOrNull(),
|
||||||
|
limit = 20
|
||||||
|
)
|
||||||
|
),
|
||||||
|
principal
|
||||||
|
)
|
||||||
|
|
||||||
|
model.addAttribute("timeline", execute)
|
||||||
|
|
||||||
|
return "homeTimeline"
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,6 +20,12 @@ hideout:
|
||||||
|
|
||||||
|
|
||||||
spring:
|
spring:
|
||||||
|
data:
|
||||||
|
mongodb:
|
||||||
|
auto-index-creation: true
|
||||||
|
host: localhost
|
||||||
|
port: 27017
|
||||||
|
database: hideout
|
||||||
jmx:
|
jmx:
|
||||||
enabled: false
|
enabled: false
|
||||||
jackson:
|
jackson:
|
||||||
|
@ -31,12 +37,6 @@ spring:
|
||||||
url: "jdbc:postgresql:hideout"
|
url: "jdbc:postgresql:hideout"
|
||||||
username: "postgres"
|
username: "postgres"
|
||||||
password: "password"
|
password: "password"
|
||||||
data:
|
|
||||||
mongodb:
|
|
||||||
auto-index-creation: true
|
|
||||||
host: localhost
|
|
||||||
port: 27017
|
|
||||||
database: hideout
|
|
||||||
servlet:
|
servlet:
|
||||||
multipart:
|
multipart:
|
||||||
max-file-size: 40MB
|
max-file-size: 40MB
|
||||||
|
|
|
@ -81,7 +81,7 @@ create table timelines
|
||||||
);
|
);
|
||||||
create table if not exists user_details
|
create table if not exists user_details
|
||||||
(
|
(
|
||||||
id bigserial primary key,
|
id bigint primary key,
|
||||||
actor_id bigint not null unique,
|
actor_id bigint not null unique,
|
||||||
password varchar(255) not null,
|
password varchar(255) not null,
|
||||||
auto_accept_followee_follow_request boolean not null,
|
auto_accept_followee_follow_request boolean not null,
|
||||||
|
@ -286,4 +286,34 @@ create table if not exists actor_instance_relationships
|
||||||
PRIMARY KEY (actor_id, instance_id),
|
PRIMARY KEY (actor_id, instance_id),
|
||||||
constraint fk_actor_instance_relationships_actor_id__id foreign key (actor_id) references actors (id) on delete cascade on update cascade,
|
constraint fk_actor_instance_relationships_actor_id__id foreign key (actor_id) references actors (id) on delete cascade on update cascade,
|
||||||
constraint fk_actor_instance_relationships_instance_id__id foreign key (instance_id) references instance (id) on delete cascade on update cascade
|
constraint fk_actor_instance_relationships_instance_id__id foreign key (instance_id) references instance (id) on delete cascade on update cascade
|
||||||
);
|
);
|
||||||
|
|
||||||
|
create table if not exists timeline_relationships
|
||||||
|
(
|
||||||
|
id bigint primary key,
|
||||||
|
timeline_id bigint not null,
|
||||||
|
actor_id bigint not null,
|
||||||
|
visible varchar(100) not null,
|
||||||
|
constraint fk_timeline_relationships_timeline_id__id foreign key (timeline_id) references timelines (id) on delete cascade on update cascade,
|
||||||
|
constraint fk_timeline_relationships_actor_id__id foreign key (actor_id) references actors (id) on delete cascade on update cascade
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
|
create table if not exists filters
|
||||||
|
(
|
||||||
|
id bigint primary key,
|
||||||
|
user_id bigint not null,
|
||||||
|
name varchar(255) not null,
|
||||||
|
context varchar(500) not null,
|
||||||
|
action varchar(255) not null,
|
||||||
|
constraint fk_filters_user_id__id foreign key (user_id) references user_details (id) on delete cascade on update cascade
|
||||||
|
);
|
||||||
|
|
||||||
|
create table if not exists filter_keywords
|
||||||
|
(
|
||||||
|
id bigint primary key,
|
||||||
|
filter_id bigint not null,
|
||||||
|
keyword varchar(1000) not null,
|
||||||
|
mode varchar(100) not null,
|
||||||
|
constraint fk_filter_keywords_filter_id__id foreign key (filter_id) references filters (id) on delete cascade on update cascade
|
||||||
|
)
|
|
@ -6,12 +6,14 @@
|
||||||
</Console>
|
</Console>
|
||||||
</Appenders>
|
</Appenders>
|
||||||
<Loggers>
|
<Loggers>
|
||||||
<Root level="INFO">
|
<Root level="DEBUG">
|
||||||
<AppenderRef ref="Console"/>
|
<AppenderRef ref="Console"/>
|
||||||
</Root>
|
</Root>
|
||||||
<Logger name="dev.usbharu.owl.broker.service.QueuedTaskAssignerImpl" level="TRACE"/>
|
<Logger name="dev.usbharu.owl.broker.service.QueuedTaskAssignerImpl" level="TRACE"/>
|
||||||
<Logger name="org.mongodb.driver.cluster" level="WARN"/>
|
<!-- <Logger name="org.mongodb.driver.cluster" level=""/>-->
|
||||||
<Logger name="org.apache.tomcat.util.net.NioEndpoint" level="INFO"/>
|
<Logger name="org.apache.tomcat.util.net.NioEndpoint" level="INFO"/>
|
||||||
<Logger name="Exposed" level="DEBUG"/>
|
<Logger name="Exposed" level="DEBUG"/>
|
||||||
|
<Logger name="sun.rmi" level="INFO"/>
|
||||||
|
<Logger name="javax.management.remote.rmi" level="INFO"/>
|
||||||
</Loggers>
|
</Loggers>
|
||||||
</Configuration>
|
</Configuration>
|
|
@ -1,6 +1,8 @@
|
||||||
common.audio=\u30AA\u30FC\u30C7\u30A3\u30AA
|
common.audio=\u30AA\u30FC\u30C7\u30A3\u30AA
|
||||||
common.audio-download-link=\u97F3\u58F0\u30D5\u30A1\u30A4\u30EB\u3092\u30C0\u30A6\u30F3\u30ED\u30FC\u30C9
|
common.audio-download-link=\u97F3\u58F0\u30D5\u30A1\u30A4\u30EB\u3092\u30C0\u30A6\u30F3\u30ED\u30FC\u30C9
|
||||||
|
common.empty=\u8868\u793A\u3059\u308B\u3082\u306E\u304C\u3042\u308A\u307E\u305B\u3093
|
||||||
common.media-original-link=\u30AA\u30EA\u30B8\u30CA\u30EB
|
common.media-original-link=\u30AA\u30EA\u30B8\u30CA\u30EB
|
||||||
|
common.paging-load=\u3082\u3063\u3068\u898B\u308B
|
||||||
common.thumbnail=\u30B5\u30E0\u30CD\u30A4\u30EB
|
common.thumbnail=\u30B5\u30E0\u30CD\u30A4\u30EB
|
||||||
common.unknwon-file-type=\u4E0D\u660E\u306A\u30D5\u30A1\u30A4\u30EB\u5F62\u5F0F
|
common.unknwon-file-type=\u4E0D\u660E\u306A\u30D5\u30A1\u30A4\u30EB\u5F62\u5F0F
|
||||||
common.video=\u52D5\u753B
|
common.video=\u52D5\u753B
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
common.audio=Audio
|
common.audio=Audio
|
||||||
common.audio-download-link=Download the audio.
|
common.audio-download-link=Download the audio.
|
||||||
|
common.empty=Empty
|
||||||
common.media-original-link=original
|
common.media-original-link=original
|
||||||
|
common.paging-load=Show more
|
||||||
common.thumbnail=thumbnail
|
common.thumbnail=thumbnail
|
||||||
common.unknwon-file-type=Unknown filetype
|
common.unknwon-file-type=Unknown filetype
|
||||||
common.video=Video
|
common.video=Video
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
common.audio=\u30AA\u30FC\u30C7\u30A3\u30AA
|
common.audio=\u30AA\u30FC\u30C7\u30A3\u30AA
|
||||||
common.audio-download-link=\u97F3\u58F0\u30D5\u30A1\u30A4\u30EB\u3092\u30C0\u30A6\u30F3\u30ED\u30FC\u30C9
|
common.audio-download-link=\u97F3\u58F0\u30D5\u30A1\u30A4\u30EB\u3092\u30C0\u30A6\u30F3\u30ED\u30FC\u30C9
|
||||||
|
common.empty=\u8868\u793A\u3059\u308B\u3082\u306E\u304C\u3042\u308A\u307E\u305B\u3093
|
||||||
common.media-original-link=\u30AA\u30EA\u30B8\u30CA\u30EB
|
common.media-original-link=\u30AA\u30EA\u30B8\u30CA\u30EB
|
||||||
|
common.paging-load=\u3082\u3063\u3068\u898B\u308B
|
||||||
common.thumbnail=\u30B5\u30E0\u30CD\u30A4\u30EB
|
common.thumbnail=\u30B5\u30E0\u30CD\u30A4\u30EB
|
||||||
common.unknwon-file-type=\u4E0D\u660E\u306A\u30D5\u30A1\u30A4\u30EB\u5F62\u5F0F
|
common.unknwon-file-type=\u4E0D\u660E\u306A\u30D5\u30A1\u30A4\u30EB\u5F62\u5F0F
|
||||||
common.video=\u52D5\u753B
|
common.video=\u52D5\u753B
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Title</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<th:block th:fragment="simple-timline(timelineObject,href)">
|
||||||
|
<!--/*@thymesVar id="timelineObject" type="dev.usbharu.hideout.core.domain.model.support.page.PaginationList<dev.usbharu.hideout.core.application.post.PostDetail,dev.usbharu.hideout.core.domain.model.post.PostId>"*/-->
|
||||||
|
<div th:if="${timelineObject.prev != null}">
|
||||||
|
<a th:href="${href + '?minId=' + timelineObject.prev.id}" th:text="#{common.paging-load}">Show more</a>
|
||||||
|
</div>
|
||||||
|
<div th:if="${timelineObject.isEmpty()}" th:text="#{common.empty}"></div>
|
||||||
|
<div th:each="postDetail : ${timelineObject}">
|
||||||
|
<th:block th:replace="~{fragments-post :: single-simple-post(${postDetail})}"></th:block>
|
||||||
|
<th:block th:replace="~{fragments-post :: single-post-controller(${postDetail})}"></th:block>
|
||||||
|
</div>
|
||||||
|
<div th:if="${timelineObject.next != null}">
|
||||||
|
<a th:href="${href + '?maxId=' + timelineObject.next.id}" th:text="#{common.paging-load}">Show more</a>
|
||||||
|
</div>
|
||||||
|
</th:block>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,12 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Title</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>
|
||||||
|
<div th:replace="fragments-timeline :: simple-timline(${timeline},'/home')"></div>
|
||||||
|
</noscript>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -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)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue