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
|
||||
|
||||
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.PostEventBody
|
||||
import dev.usbharu.hideout.core.domain.model.support.principal.Anonymous
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.stereotype.Component
|
||||
|
||||
@Component
|
||||
class TimelinePostCreateSubscriber(domainEventSubscriber: DomainEventSubscriber) : Subscriber {
|
||||
class TimelinePostCreateSubscriber(
|
||||
private val timelineAddPostApplicationService: TimelineAddPostApplicationService,
|
||||
domainEventSubscriber: DomainEventSubscriber,
|
||||
) : Subscriber {
|
||||
init {
|
||||
domainEventSubscriber.subscribe<PostEventBody>(PostEvent.CREATE.eventName) {
|
||||
val post = it.body.getPost()
|
||||
val actor = it.body.getActor()
|
||||
|
||||
logger.info("New Post! : {}", post)
|
||||
timelineAddPostApplicationService.execute(AddPost(it.body.getPostId()), Anonymous)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ class TimelineRelationshipFollowSubscriber(
|
|||
AddTimelineRelationship(
|
||||
TimelineRelationship(
|
||||
TimelineRelationshipId(idGenerateService.generateId()),
|
||||
userDetail.homeTimelineId,
|
||||
userDetail.homeTimelineId!!,
|
||||
relationship.targetActorId,
|
||||
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
|
||||
|
||||
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 java.net.URI
|
||||
|
||||
data class ActorDetail(
|
||||
val actorId: Long,
|
||||
val instanceId: Long,
|
||||
val instanceName: String,
|
||||
val name: String,
|
||||
val domain: String,
|
||||
val screenName: String,
|
||||
|
@ -17,11 +15,10 @@ data class ActorDetail(
|
|||
val icon: URI?,
|
||||
) {
|
||||
companion object {
|
||||
fun of(actor: Actor, instance: Instance, iconMedia: Media?): ActorDetail {
|
||||
fun of(actor: Actor, iconMedia: Media?): ActorDetail {
|
||||
return ActorDetail(
|
||||
actor.id.id,
|
||||
actor.instance.instanceId,
|
||||
instance.name.name,
|
||||
actor.name.name,
|
||||
actor.domain.domain,
|
||||
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.domain.model.actor.Actor
|
||||
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.MediaRepository
|
||||
import dev.usbharu.hideout.core.domain.model.post.PostId
|
||||
|
@ -21,7 +19,6 @@ class GetPostDetailApplicationService(
|
|||
transaction: Transaction,
|
||||
private val postRepository: PostRepository,
|
||||
private val actorRepository: ActorRepository,
|
||||
private val instanceRepository: InstanceRepository,
|
||||
private val mediaRepository: MediaRepository,
|
||||
private val iPostReadAccessControl: IPostReadAccessControl
|
||||
) : AbstractApplicationService<GetPostDetail, PostDetail>(
|
||||
|
@ -36,8 +33,6 @@ class GetPostDetailApplicationService(
|
|||
}
|
||||
val actor =
|
||||
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) }
|
||||
|
||||
|
@ -46,19 +41,17 @@ class GetPostDetailApplicationService(
|
|||
return PostDetail.of(
|
||||
post,
|
||||
actor,
|
||||
instance,
|
||||
iconMedia,
|
||||
mediaList,
|
||||
post.replyId?.let { fetchChild(it, actor, instance, iconMedia, principal) },
|
||||
post.repostId?.let { fetchChild(it, actor, instance, iconMedia, principal) },
|
||||
post.moveTo?.let { fetchChild(it, actor, instance, iconMedia, principal) },
|
||||
post.replyId?.let { fetchChild(it, actor, iconMedia, principal) },
|
||||
post.repostId?.let { fetchChild(it, actor, iconMedia, principal) },
|
||||
post.moveTo?.let { fetchChild(it, actor, iconMedia, principal) },
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun fetchChild(
|
||||
postId: PostId,
|
||||
actor: Actor,
|
||||
instance: Instance,
|
||||
iconMedia: Media?,
|
||||
principal: Principal
|
||||
): PostDetail? {
|
||||
|
@ -68,21 +61,16 @@ class GetPostDetailApplicationService(
|
|||
return null
|
||||
}
|
||||
|
||||
val (first, second: Instance, third) = if (actor.id != post.actorId) {
|
||||
Triple(
|
||||
actorRepository.findById(post.actorId) ?: return null,
|
||||
instanceRepository.findById(actor.instance) ?: return null,
|
||||
actor.icon?.let { mediaRepository.findById(it) }
|
||||
)
|
||||
val (first, third) = if (actor.id != post.actorId) {
|
||||
(actorRepository.findById(post.actorId) ?: return null) to actor.icon?.let { mediaRepository.findById(it) }
|
||||
} else {
|
||||
Triple(actor, instance, iconMedia)
|
||||
actor to iconMedia
|
||||
}
|
||||
|
||||
val mediaList = mediaRepository.findByIds(post.mediaIds)
|
||||
return PostDetail.of(
|
||||
post,
|
||||
first,
|
||||
second,
|
||||
third,
|
||||
mediaList
|
||||
)
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package dev.usbharu.hideout.core.application.post
|
||||
|
||||
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.post.Post
|
||||
import dev.usbharu.hideout.core.domain.model.post.Visibility
|
||||
|
@ -30,7 +29,6 @@ data class PostDetail(
|
|||
fun of(
|
||||
post: Post,
|
||||
actor: Actor,
|
||||
instance: Instance,
|
||||
iconMedia: Media?,
|
||||
mediaList: List<Media>,
|
||||
reply: PostDetail? = null,
|
||||
|
@ -39,7 +37,7 @@ data class PostDetail(
|
|||
): PostDetail {
|
||||
return PostDetail(
|
||||
id = post.id.id,
|
||||
actor = ActorDetail.of(actor, instance, iconMedia),
|
||||
actor = ActorDetail.of(actor, iconMedia),
|
||||
overview = post.overview?.overview,
|
||||
text = post.text,
|
||||
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
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity(debug = true)
|
||||
@EnableWebSecurity(debug = false)
|
||||
class SecurityConfig {
|
||||
@Bean
|
||||
fun passwordEncoder(): PasswordEncoder = BCryptPasswordEncoder()
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
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.ActorId
|
||||
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEvent
|
||||
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> {
|
||||
return DomainEvent.create(
|
||||
actorEvent.eventName,
|
||||
ActorEventBody(actor),
|
||||
ActorEventBody(actor.id),
|
||||
actorEvent.collectable
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class ActorEventBody(actor: Actor) : DomainEventBody(
|
||||
class ActorEventBody(actor: ActorId) : DomainEventBody(
|
||||
mapOf(
|
||||
"actor" to actor
|
||||
),
|
||||
|
|
|
@ -17,7 +17,9 @@
|
|||
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.ActorId
|
||||
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.DomainEventBody
|
||||
|
||||
|
@ -25,14 +27,14 @@ class PostDomainEventFactory(private val post: Post, private val actor: Actor? =
|
|||
fun createEvent(postEvent: PostEvent): DomainEvent<PostEventBody> {
|
||||
return DomainEvent.create(
|
||||
postEvent.eventName,
|
||||
PostEventBody(post, actor)
|
||||
PostEventBody(post.id, actor?.id)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class PostEventBody(post: Post, actor: Actor?) : DomainEventBody(mapOf("post" to post, "actor" to actor)) {
|
||||
fun getPost(): Post = toMap()["post"] as Post
|
||||
fun getActor(): Actor? = toMap()["actor"] as Actor?
|
||||
class PostEventBody(post: PostId, actor: ActorId?) : DomainEventBody(mapOf("post" to post, "actor" to actor)) {
|
||||
fun getPostId(): PostId = toMap()["post"] as PostId
|
||||
fun getActorId(): ActorId? = toMap()["actor"] as ActorId?
|
||||
}
|
||||
|
||||
enum class PostEvent(val eventName: String) {
|
||||
|
|
|
@ -1,16 +1,22 @@
|
|||
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.TimelineId
|
||||
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEvent
|
||||
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventBody
|
||||
|
||||
class TimelineEventFactory(private val timeline: Timeline) {
|
||||
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) {
|
||||
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 findByFilterId(filterId: FilterId): Filter?
|
||||
|
||||
suspend fun findByUserDetailId(userDetailId: UserDetailId): List<Filter>
|
||||
}
|
||||
|
|
|
@ -22,6 +22,18 @@ interface RelationshipRepository {
|
|||
suspend fun save(relationship: Relationship): Relationship
|
||||
suspend fun delete(relationship: 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(
|
||||
targetId: ActorId,
|
||||
option: FindRelationshipOption? = null,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
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.media.Media
|
||||
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.timelineobject.TimelineObject
|
||||
|
@ -14,11 +15,17 @@ data class TimelineObjectDetail(
|
|||
val postId: PostId,
|
||||
val timelineUserDetail: UserDetail,
|
||||
val post: Post,
|
||||
val postMedias: List<Media>,
|
||||
val postActor: Actor,
|
||||
val postActorIconMedia: Media?,
|
||||
val replyPost: Post?,
|
||||
val replyPostMedias: List<Media>?,
|
||||
val replyPostActor: Actor?,
|
||||
val replyPostActorIconMedia: Media?,
|
||||
val repostPost: Post?,
|
||||
val repostPostMedias: List<Media>?,
|
||||
val repostPostActor: Actor?,
|
||||
val repostPostActorIconMedia: Media?,
|
||||
val isPureRepost: Boolean,
|
||||
val lastUpdateAt: Instant,
|
||||
val hasMediaInRepost: Boolean,
|
||||
|
@ -29,27 +36,39 @@ data class TimelineObjectDetail(
|
|||
timelineObject: TimelineObject,
|
||||
timelineUserDetail: UserDetail,
|
||||
post: Post,
|
||||
postMedias: List<Media>,
|
||||
postActor: Actor,
|
||||
postActorIconMedia: Media?,
|
||||
replyPost: Post?,
|
||||
replyPostMedias: List<Media>?,
|
||||
replyPostActor: Actor?,
|
||||
replyPostActorIconMedia: Media?,
|
||||
repostPost: Post?,
|
||||
repostPostMedias: List<Media>?,
|
||||
repostPostActor: Actor?,
|
||||
repostPostActorIconMedia: Media?,
|
||||
warnFilter: List<TimelineObjectWarnFilter>
|
||||
): TimelineObjectDetail {
|
||||
return TimelineObjectDetail(
|
||||
timelineObject.id,
|
||||
post.id,
|
||||
timelineUserDetail,
|
||||
post,
|
||||
postActor,
|
||||
replyPost,
|
||||
replyPostActor,
|
||||
repostPost,
|
||||
repostPostActor,
|
||||
timelineObject.isPureRepost,
|
||||
timelineObject.lastUpdatedAt,
|
||||
timelineObject.hasMediaInRepost,
|
||||
warnFilter
|
||||
id = timelineObject.id,
|
||||
postId = post.id,
|
||||
timelineUserDetail = timelineUserDetail,
|
||||
post = post,
|
||||
postMedias = postMedias,
|
||||
postActor = postActor,
|
||||
postActorIconMedia = postActorIconMedia,
|
||||
replyPost = replyPost,
|
||||
replyPostMedias = replyPostMedias,
|
||||
replyPostActor = replyPostActor,
|
||||
replyPostActorIconMedia = replyPostActorIconMedia,
|
||||
repostPost = repostPost,
|
||||
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
|
||||
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
|
||||
|
||||
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.timeline.TimelineId
|
||||
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventStorable
|
||||
import java.time.Instant
|
||||
|
||||
class UserDetail private constructor(
|
||||
class UserDetail(
|
||||
val id: UserDetailId,
|
||||
val actorId: ActorId,
|
||||
var password: UserDetailHashedPassword,
|
||||
var autoAcceptFolloweeFollowRequest: Boolean,
|
||||
var lastMigration: Instant? = null,
|
||||
val homeTimelineId: TimelineId?
|
||||
) {
|
||||
var homeTimelineId: TimelineId?
|
||||
) : DomainEventStorable() {
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
|
@ -49,7 +52,7 @@ class UserDetail private constructor(
|
|||
lastMigration: Instant? = null,
|
||||
homeTimelineId: TimelineId? = null
|
||||
): UserDetail {
|
||||
return UserDetail(
|
||||
val userDetail = UserDetail(
|
||||
id,
|
||||
actorId,
|
||||
password,
|
||||
|
@ -57,6 +60,8 @@ class UserDetail private constructor(
|
|||
lastMigration,
|
||||
homeTimelineId
|
||||
)
|
||||
userDetail.addDomainEvent(UserDetailDomainEventFactory(userDetail).createEvent(UserDetailEvent.CREATE))
|
||||
return userDetail
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import org.springframework.stereotype.Component
|
|||
|
||||
interface IPostReadAccessControl {
|
||||
suspend fun isAllow(post: Post, principal: Principal): Boolean
|
||||
suspend fun areAllows(postList: List<Post>, principal: Principal): List<Post>
|
||||
}
|
||||
|
||||
@Component
|
||||
|
@ -57,4 +58,47 @@ class DefaultPostReadAccessControl(private val relationshipRepository: Relations
|
|||
// その他の場合は見れない
|
||||
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.support.page.Page
|
||||
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.timelinerelationship.TimelineRelationship
|
||||
|
@ -22,6 +23,7 @@ interface TimelineStore {
|
|||
suspend fun readTimeline(
|
||||
timeline: Timeline,
|
||||
option: ReadTimelineOption? = null,
|
||||
page: Page? = null
|
||||
page: Page? = null,
|
||||
principal: Principal
|
||||
): PaginationList<TimelineObjectDetail, PostId>
|
||||
}
|
||||
|
|
|
@ -17,6 +17,10 @@
|
|||
package dev.usbharu.hideout.core.infrastructure.exposedrepository
|
||||
|
||||
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.slf4j.Logger
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
|
@ -35,7 +39,20 @@ abstract class AbstractRepository {
|
|||
@Value("\${hideout.debug.trace-query-call: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) {
|
||||
@Suppress("ThrowingExceptionsWithoutMessageOrCause")
|
||||
logger.trace(
|
||||
|
@ -49,7 +66,7 @@ ${Throwable().stackTrace.joinToString("\n")}
|
|||
)
|
||||
}
|
||||
|
||||
block.invoke()
|
||||
block.invoke(TransactionInterceptor(TransactionManager.current()))
|
||||
} catch (e: SQLException) {
|
||||
if (traceQueryException) {
|
||||
logger.trace("FAILED EXECUTE SQL", e)
|
||||
|
|
|
@ -45,8 +45,11 @@ class ExposedActorInstanceRelationshipRepository(override val domainEventPublish
|
|||
it[muting] = actorInstanceRelationship.muting
|
||||
it[doNotSendPrivate] = actorInstanceRelationship.doNotSendPrivate
|
||||
}
|
||||
onComplete {
|
||||
update(actorInstanceRelationship)
|
||||
}
|
||||
}
|
||||
update(actorInstanceRelationship)
|
||||
|
||||
return actorInstanceRelationship
|
||||
}
|
||||
|
||||
|
@ -56,8 +59,10 @@ class ExposedActorInstanceRelationshipRepository(override val domainEventPublish
|
|||
actorId eq actorInstanceRelationship.actorId.id and
|
||||
(instanceId eq actorInstanceRelationship.instanceId.instanceId)
|
||||
}
|
||||
onComplete {
|
||||
update(actorInstanceRelationship)
|
||||
}
|
||||
}
|
||||
update(actorInstanceRelationship)
|
||||
}
|
||||
|
||||
override suspend fun findByActorIdAndInstanceId(
|
||||
|
|
|
@ -59,8 +59,11 @@ class ExposedActorRepository(
|
|||
this[ActorsAlsoKnownAs.actorId] = actor.id.id
|
||||
this[ActorsAlsoKnownAs.alsoKnownAs] = it.id
|
||||
}
|
||||
onComplete {
|
||||
update(actor)
|
||||
}
|
||||
}
|
||||
update(actor)
|
||||
|
||||
return actor
|
||||
}
|
||||
|
||||
|
@ -68,8 +71,10 @@ class ExposedActorRepository(
|
|||
query {
|
||||
Actors.deleteWhere { id eq actor.id.id }
|
||||
ActorsAlsoKnownAs.deleteWhere { actorId eq actor.id.id }
|
||||
onComplete {
|
||||
update(actor)
|
||||
}
|
||||
}
|
||||
update(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 }
|
||||
}
|
||||
|
||||
override suspend fun findByFilterKeywordId(filterKeywordId: FilterKeywordId): Filter? {
|
||||
override suspend fun findByFilterKeywordId(filterKeywordId: FilterKeywordId): Filter? = query {
|
||||
val filterId = FilterKeywords
|
||||
.selectAll()
|
||||
.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 }
|
||||
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 }
|
||||
return filterQueryMapper.map(where).firstOrNull()
|
||||
return@query filterQueryMapper.map(where).firstOrNull()
|
||||
}
|
||||
|
||||
override suspend fun findByUserDetailId(userDetailId: UserDetailId): List<Filter> {
|
||||
return Filters.selectAll().where { Filters.userId eq userDetailId.id }.let(filterQueryMapper::map)
|
||||
override suspend fun findByUserDetailId(userDetailId: UserDetailId): List<Filter> = query {
|
||||
return@query Filters.selectAll().where { Filters.userId eq userDetailId.id }.let(filterQueryMapper::map)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -98,8 +98,11 @@ class ExposedPostRepository(
|
|||
this[PostsVisibleActors.postId] = post.id.id
|
||||
this[PostsVisibleActors.actorId] = it.id
|
||||
}
|
||||
onComplete {
|
||||
update(post)
|
||||
}
|
||||
}
|
||||
update(post)
|
||||
|
||||
return post
|
||||
}
|
||||
|
||||
|
@ -148,9 +151,11 @@ class ExposedPostRepository(
|
|||
this[PostsVisibleActors.postId] = it.first
|
||||
this[PostsVisibleActors.actorId] = it.second
|
||||
}
|
||||
}
|
||||
posts.forEach {
|
||||
update(it)
|
||||
onComplete {
|
||||
posts.forEach {
|
||||
update(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
return posts
|
||||
}
|
||||
|
@ -195,8 +200,10 @@ class ExposedPostRepository(
|
|||
Posts.deleteWhere {
|
||||
id eq post.id.id
|
||||
}
|
||||
onComplete {
|
||||
update(post)
|
||||
}
|
||||
}
|
||||
update(post)
|
||||
}
|
||||
|
||||
override suspend fun findByActorIdAndVisibilityInList(
|
||||
|
|
|
@ -47,8 +47,11 @@ class ExposedRelationshipRepository(override val domainEventPublisher: DomainEve
|
|||
it[followRequesting] = relationship.followRequesting
|
||||
it[mutingFollowRequest] = relationship.mutingFollowRequest
|
||||
}
|
||||
onComplete {
|
||||
update(relationship)
|
||||
}
|
||||
}
|
||||
update(relationship)
|
||||
|
||||
return relationship
|
||||
}
|
||||
|
||||
|
@ -57,8 +60,10 @@ class ExposedRelationshipRepository(override val domainEventPublisher: DomainEve
|
|||
Relationships.deleteWhere {
|
||||
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 {
|
||||
|
@ -67,6 +72,26 @@ class ExposedRelationshipRepository(override val domainEventPublisher: DomainEve
|
|||
}.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(
|
||||
targetId: ActorId,
|
||||
option: FindRelationshipOption?,
|
||||
|
|
|
@ -24,8 +24,11 @@ class ExposedTimelineRepository(override val domainEventPublisher: DomainEventPu
|
|||
it[visibility] = timeline.visibility.name
|
||||
it[isSystem] = timeline.isSystem
|
||||
}
|
||||
onComplete {
|
||||
update(timeline)
|
||||
}
|
||||
}
|
||||
update(timeline)
|
||||
|
||||
return timeline
|
||||
}
|
||||
|
||||
|
@ -34,8 +37,10 @@ class ExposedTimelineRepository(override val domainEventPublisher: DomainEventPu
|
|||
Timelines.deleteWhere {
|
||||
Timelines.id eq timeline.id.value
|
||||
}
|
||||
onComplete {
|
||||
update(timeline)
|
||||
}
|
||||
}
|
||||
update(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.UserDetailId
|
||||
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.SqlExpressionBuilder.eq
|
||||
import org.jetbrains.exposed.sql.javatime.timestamp
|
||||
|
@ -30,34 +32,51 @@ import org.slf4j.LoggerFactory
|
|||
import org.springframework.stereotype.Repository
|
||||
|
||||
@Repository
|
||||
class UserDetailRepositoryImpl : UserDetailRepository, AbstractRepository() {
|
||||
class UserDetailRepositoryImpl(override val domainEventPublisher: DomainEventPublisher) :
|
||||
UserDetailRepository,
|
||||
AbstractRepository(),
|
||||
DomainEventPublishableRepository<UserDetail> {
|
||||
override val logger: Logger
|
||||
get() = Companion.logger
|
||||
|
||||
override suspend fun save(userDetail: UserDetail): UserDetail = query {
|
||||
val singleOrNull =
|
||||
UserDetails.selectAll().where { UserDetails.id eq userDetail.id.id }.forUpdate().singleOrNull()
|
||||
if (singleOrNull == null) {
|
||||
UserDetails.insert {
|
||||
it[id] = userDetail.id.id
|
||||
it[actorId] = userDetail.actorId.id
|
||||
it[password] = userDetail.password.password
|
||||
it[autoAcceptFolloweeFollowRequest] = userDetail.autoAcceptFolloweeFollowRequest
|
||||
it[lastMigration] = userDetail.lastMigration
|
||||
override suspend fun save(userDetail: UserDetail): UserDetail {
|
||||
val userDetail1 = query {
|
||||
val singleOrNull =
|
||||
UserDetails.selectAll().where { UserDetails.id eq userDetail.id.id }.forUpdate().singleOrNull()
|
||||
if (singleOrNull == null) {
|
||||
UserDetails.insert {
|
||||
it[id] = 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 {
|
||||
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 {
|
||||
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
|
||||
onComplete {
|
||||
update(userDetail)
|
||||
}
|
||||
userDetail
|
||||
}
|
||||
return@query userDetail
|
||||
|
||||
return userDetail1
|
||||
}
|
||||
|
||||
override suspend fun delete(userDetail: UserDetail): Unit = query {
|
||||
UserDetails.deleteWhere { id eq userDetail.id.id }
|
||||
override suspend fun delete(userDetail: UserDetail) {
|
||||
query {
|
||||
UserDetails.deleteWhere { id eq userDetail.id.id }
|
||||
onComplete {
|
||||
update(userDetail)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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]),
|
||||
ActorId(it[UserDetails.actorId]),
|
||||
UserDetailHashedPassword(it[UserDetails.password]),
|
||||
|
|
|
@ -19,6 +19,7 @@ import kotlinx.coroutines.flow.Flow
|
|||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.toList
|
||||
import org.springframework.data.annotation.Id
|
||||
import org.springframework.data.domain.Sort
|
||||
import org.springframework.data.mongodb.core.MongoTemplate
|
||||
import org.springframework.data.mongodb.core.mapping.Document
|
||||
|
@ -61,6 +62,22 @@ class MongoInternalTimelineObjectRepository(
|
|||
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(
|
||||
timelineId: TimelineId,
|
||||
internalTimelineObjectOption: InternalTimelineObjectOption?,
|
||||
|
@ -70,12 +87,12 @@ class MongoInternalTimelineObjectRepository(
|
|||
|
||||
if (page?.minId != null) {
|
||||
query.with(Sort.by(Sort.Direction.ASC, "postCreatedAt"))
|
||||
page.minId?.let { query.addCriteria(Criteria.where("id").gt(it)) }
|
||||
page.maxId?.let { query.addCriteria(Criteria.where("id").lt(it)) }
|
||||
page.minId?.let { query.addCriteria(Criteria.where("postId").gt(it)) }
|
||||
page.maxId?.let { query.addCriteria(Criteria.where("postId").lt(it)) }
|
||||
} else {
|
||||
query.with(Sort.by(Sort.Direction.DESC, "postCreatedAt"))
|
||||
page?.sinceId?.let { query.addCriteria(Criteria.where("id").gt(it)) }
|
||||
page?.maxId?.let { query.addCriteria(Criteria.where("id").lt(it)) }
|
||||
page?.sinceId?.let { query.addCriteria(Criteria.where("postId").gt(it)) }
|
||||
page?.maxId?.let { query.addCriteria(Criteria.where("postId").lt(it)) }
|
||||
}
|
||||
|
||||
page?.limit?.let { query.limit(it) }
|
||||
|
@ -83,16 +100,23 @@ class MongoInternalTimelineObjectRepository(
|
|||
val timelineObjects =
|
||||
mongoTemplate.find(query, SpringDataMongoTimelineObject::class.java).map { it.toTimelineObject() }
|
||||
|
||||
val objectList = if (page?.minId != null) {
|
||||
timelineObjects.reversed()
|
||||
} else {
|
||||
timelineObjects
|
||||
}
|
||||
|
||||
return PaginationList(
|
||||
timelineObjects,
|
||||
timelineObjects.lastOrNull()?.postId,
|
||||
timelineObjects.firstOrNull()?.postId
|
||||
objectList,
|
||||
objectList.lastOrNull()?.postId,
|
||||
objectList.firstOrNull()?.postId
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Document
|
||||
data class SpringDataMongoTimelineObject(
|
||||
@Id
|
||||
val id: Long,
|
||||
val userDetailId: Long,
|
||||
val timelineId: Long,
|
||||
|
@ -194,4 +218,14 @@ interface SpringDataMongoTimelineObjectRepository : CoroutineCrudRepository<Spri
|
|||
suspend fun deleteByTimelineId(timelineId: Long)
|
||||
|
||||
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.DomainEventPublisher
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.context.ApplicationEventPublisher
|
||||
import org.springframework.stereotype.Component
|
||||
|
||||
|
@ -25,6 +26,11 @@ import org.springframework.stereotype.Component
|
|||
class SpringFrameworkDomainEventPublisher(private val applicationEventPublisher: ApplicationEventPublisher) :
|
||||
DomainEventPublisher {
|
||||
override suspend fun publishEvent(domainEvent: DomainEvent<*>) {
|
||||
logger.trace("Publish ${domainEvent.id} ${domainEvent.name}")
|
||||
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.domain.shared.domainevent.DomainEvent
|
||||
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventBody
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.context.event.EventListener
|
||||
import org.springframework.stereotype.Component
|
||||
|
||||
|
@ -18,11 +19,17 @@ class SpringFrameworkDomainEventSubscriber : DomainEventSubscriber {
|
|||
|
||||
@EventListener
|
||||
suspend fun onDomainEventPublished(domainEvent: DomainEvent<*>) {
|
||||
logger.trace("Domain Event Published: $domainEvent")
|
||||
map[domainEvent.name]?.forEach {
|
||||
try {
|
||||
it.invoke(domainEvent)
|
||||
} 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.filter.Filter
|
||||
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.PostId
|
||||
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.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.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 getPostsByPostId(postIds: List<PostId>): List<Post>
|
||||
protected abstract suspend fun getPostsByPostId(postIds: List<PostId>, principal: Principal): List<Post>
|
||||
|
||||
protected abstract suspend fun getTimelineObject(
|
||||
timelineId: TimelineId,
|
||||
|
@ -202,12 +205,21 @@ abstract class AbstractTimelineStore(private val idGenerateService: IdGenerateSe
|
|||
removeTimelineObject(timeline.id)
|
||||
}
|
||||
|
||||
protected abstract suspend fun getNextPaging(
|
||||
timelineId: TimelineId,
|
||||
page: Page?
|
||||
): PaginationList<TimelineObjectDetail, PostId>
|
||||
|
||||
override suspend fun readTimeline(
|
||||
timeline: Timeline,
|
||||
option: ReadTimelineOption?,
|
||||
page: Page?
|
||||
page: Page?,
|
||||
principal: Principal
|
||||
): PaginationList<TimelineObjectDetail, PostId> {
|
||||
val timelineObjectList = getTimelineObject(timeline.id, option, page)
|
||||
if (timelineObjectList.isEmpty()) {
|
||||
return getNextPaging(timeline.id, page)
|
||||
}
|
||||
val lastUpdatedAt = timelineObjectList.minBy { it.lastUpdatedAt }.lastUpdatedAt
|
||||
|
||||
val newerFilters = getNewerFilters(timeline.userDetailId, lastUpdatedAt)
|
||||
|
@ -216,7 +228,8 @@ abstract class AbstractTimelineStore(private val idGenerateService: IdGenerateSe
|
|||
getPostsByPostId(
|
||||
timelineObjectList.map {
|
||||
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 })
|
||||
|
@ -232,24 +245,35 @@ abstract class AbstractTimelineStore(private val idGenerateService: IdGenerateSe
|
|||
post.id to applyFilters(post, newerFilters)
|
||||
}
|
||||
|
||||
val mediaMap = getMedias(posts.flatMap { it.mediaIds } + actors.mapNotNull { it.value.icon })
|
||||
|
||||
return PaginationList(
|
||||
timelineObjectList.mapNotNull<TimelineObject, TimelineObjectDetail> {
|
||||
val timelineUserDetail = userDetails[it.userDetailId] ?: return@mapNotNull null
|
||||
val actor = actors[it.postActorId] ?: 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 replyMedias = reply?.post?.mediaIds?.mapNotNull { mediaId -> mediaMap[mediaId] }
|
||||
val replyActor = actors[it.replyActorId]
|
||||
val repost = postMap[it.repostId]
|
||||
val repostMedias = repost?.post?.mediaIds?.mapNotNull { mediaId -> mediaMap[mediaId] }
|
||||
val repostActor = actors[it.repostActorId]
|
||||
TimelineObjectDetail.of(
|
||||
timelineObject = it,
|
||||
timelineUserDetail = timelineUserDetail,
|
||||
post = post.post,
|
||||
postMedias = postMedias,
|
||||
postActor = actor,
|
||||
postActorIconMedia = mediaMap[actor.icon],
|
||||
replyPost = reply?.post,
|
||||
replyPostMedias = replyMedias,
|
||||
replyPostActor = replyActor,
|
||||
replyPostActorIconMedia = mediaMap[replyActor?.icon],
|
||||
repostPost = repost?.post,
|
||||
repostPostMedias = repostMedias,
|
||||
repostPostActor = repostActor,
|
||||
repostPostActorIconMedia = mediaMap[repostActor?.icon],
|
||||
warnFilter = it.warnFilters + post.filterResults.map {
|
||||
TimelineObjectWarnFilter(
|
||||
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 getMedias(mediaIds: List<MediaId>): Map<MediaId, Media>
|
||||
|
||||
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.FilterRepository
|
||||
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.PostId
|
||||
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.support.page.Page
|
||||
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.TimelineId
|
||||
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.UserDetailRepository
|
||||
import dev.usbharu.hideout.core.domain.service.filter.FilterDomainService
|
||||
import dev.usbharu.hideout.core.domain.service.post.IPostReadAccessControl
|
||||
import dev.usbharu.hideout.core.domain.shared.id.IdGenerateService
|
||||
import dev.usbharu.hideout.core.external.timeline.ReadTimelineOption
|
||||
import org.springframework.stereotype.Component
|
||||
|
@ -40,7 +46,9 @@ open class DefaultTimelineStore(
|
|||
private val defaultTimelineStoreConfig: DefaultTimelineStoreConfig,
|
||||
private val internalTimelineObjectRepository: InternalTimelineObjectRepository,
|
||||
private val userDetailRepository: UserDetailRepository,
|
||||
private val actorRepository: ActorRepository
|
||||
private val actorRepository: ActorRepository,
|
||||
private val mediaRepository: MediaRepository,
|
||||
private val postIPostReadAccessControl: IPostReadAccessControl
|
||||
) : AbstractTimelineStore(idGenerateService) {
|
||||
override suspend fun getTimelines(actorId: ActorId): List<Timeline> {
|
||||
return timelineRepository.findByIds(
|
||||
|
@ -60,7 +68,7 @@ open class DefaultTimelineStore(
|
|||
}
|
||||
|
||||
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 {
|
||||
|
@ -99,8 +107,9 @@ open class DefaultTimelineStore(
|
|||
return timelineRelationshipList.flatMap { getActorPost(it.actorId, visibilities(it)) }
|
||||
}
|
||||
|
||||
override suspend fun getPostsByPostId(postIds: List<PostId>): List<Post> {
|
||||
return postRepository.findAllById(postIds)
|
||||
override suspend fun getPostsByPostId(postIds: List<PostId>, principal: Principal): List<Post> {
|
||||
val findAllById = postRepository.findAllById(postIds)
|
||||
return postIPostReadAccessControl.areAllows(findAllById, principal)
|
||||
}
|
||||
|
||||
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> {
|
||||
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> {
|
||||
return userDetailRepository.findAllById(userDetailIdList).associateBy { it.id }
|
||||
}
|
||||
|
|
|
@ -19,6 +19,17 @@ interface InternalTimelineObjectRepository {
|
|||
suspend fun deleteByTimelineIdAndActorId(timelineId: TimelineId, actorId: ActorId)
|
||||
|
||||
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(
|
||||
timelineId: TimelineId,
|
||||
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:
|
||||
data:
|
||||
mongodb:
|
||||
auto-index-creation: true
|
||||
host: localhost
|
||||
port: 27017
|
||||
database: hideout
|
||||
jmx:
|
||||
enabled: false
|
||||
jackson:
|
||||
|
@ -31,12 +37,6 @@ spring:
|
|||
url: "jdbc:postgresql:hideout"
|
||||
username: "postgres"
|
||||
password: "password"
|
||||
data:
|
||||
mongodb:
|
||||
auto-index-creation: true
|
||||
host: localhost
|
||||
port: 27017
|
||||
database: hideout
|
||||
servlet:
|
||||
multipart:
|
||||
max-file-size: 40MB
|
||||
|
|
|
@ -81,7 +81,7 @@ create table timelines
|
|||
);
|
||||
create table if not exists user_details
|
||||
(
|
||||
id bigserial primary key,
|
||||
id bigint primary key,
|
||||
actor_id bigint not null unique,
|
||||
password varchar(255) 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),
|
||||
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
|
||||
);
|
||||
);
|
||||
|
||||
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>
|
||||
</Appenders>
|
||||
<Loggers>
|
||||
<Root level="INFO">
|
||||
<Root level="DEBUG">
|
||||
<AppenderRef ref="Console"/>
|
||||
</Root>
|
||||
<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="Exposed" level="DEBUG"/>
|
||||
<Logger name="sun.rmi" level="INFO"/>
|
||||
<Logger name="javax.management.remote.rmi" level="INFO"/>
|
||||
</Loggers>
|
||||
</Configuration>
|
|
@ -1,6 +1,8 @@
|
|||
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.empty=\u8868\u793A\u3059\u308B\u3082\u306E\u304C\u3042\u308A\u307E\u305B\u3093
|
||||
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.unknwon-file-type=\u4E0D\u660E\u306A\u30D5\u30A1\u30A4\u30EB\u5F62\u5F0F
|
||||
common.video=\u52D5\u753B
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
common.audio=Audio
|
||||
common.audio-download-link=Download the audio.
|
||||
common.empty=Empty
|
||||
common.media-original-link=original
|
||||
common.paging-load=Show more
|
||||
common.thumbnail=thumbnail
|
||||
common.unknwon-file-type=Unknown filetype
|
||||
common.video=Video
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
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.empty=\u8868\u793A\u3059\u308B\u3082\u306E\u304C\u3042\u308A\u307E\u305B\u3093
|
||||
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.unknwon-file-type=\u4E0D\u660E\u306A\u30D5\u30A1\u30A4\u30EB\u5F62\u5F0F
|
||||
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.domain.model.actor.ActorRepository
|
||||
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.TestPostFactory
|
||||
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.UserDetailRepository
|
||||
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 kotlinx.coroutines.test.runTest
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
|
@ -56,6 +58,12 @@ class DefaultTimelineStoreTest {
|
|||
@Mock
|
||||
lateinit var actorRepository: ActorRepository
|
||||
|
||||
@Mock
|
||||
lateinit var mediaRepository: MediaRepository
|
||||
|
||||
@Mock
|
||||
lateinit var iPostReadAccessControl: IPostReadAccessControl
|
||||
|
||||
@Spy
|
||||
val defaultTimelineStoreConfig = DefaultTimelineStoreConfig(500)
|
||||
|
||||
|
|
Loading…
Reference in New Issue