feat: フォローを外したときにホームタイムラインから自動で外れるように

This commit is contained in:
usbharu 2024-09-07 16:04:19 +09:00
parent 2e836c228f
commit 8c5c2abb3f
Signed by: usbharu
GPG Key ID: 6556747BF94EEBC8
10 changed files with 146 additions and 23 deletions

View File

@ -5,24 +5,20 @@ import dev.usbharu.hideout.core.application.timeline.AddTimelineRelationship
import dev.usbharu.hideout.core.application.timeline.UserAddTimelineRelationshipApplicationService import dev.usbharu.hideout.core.application.timeline.UserAddTimelineRelationshipApplicationService
import dev.usbharu.hideout.core.domain.event.relationship.RelationshipEvent import dev.usbharu.hideout.core.domain.event.relationship.RelationshipEvent
import dev.usbharu.hideout.core.domain.event.relationship.RelationshipEventBody import dev.usbharu.hideout.core.domain.event.relationship.RelationshipEventBody
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.Visible import dev.usbharu.hideout.core.domain.model.timelinerelationship.Visible
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.id.IdGenerateService
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.stereotype.Component import org.springframework.stereotype.Component
@Component @Component
class TimelineRelationshipFollowSubscriber( class TimelineRelationshipFollowSubscriber(
private val userAddTimelineRelationshipApplicationService: UserAddTimelineRelationshipApplicationService, private val userAddTimelineRelationshipApplicationService: UserAddTimelineRelationshipApplicationService,
private val idGenerateService: IdGenerateService,
private val userDetailRepository: UserDetailRepository, private val userDetailRepository: UserDetailRepository,
private val domainEventSubscriber: DomainEventSubscriber private val domainEventSubscriber: DomainEventSubscriber
) : Subscriber { ) : Subscriber {
override fun init() { override fun init() {
domainEventSubscriber.subscribe<RelationshipEventBody>(RelationshipEvent.FOLLOW.eventName) { domainEventSubscriber.subscribe<RelationshipEventBody>(RelationshipEvent.ACCEPT_FOLLOW.eventName) {
val relationship = it.body.getRelationship() val relationship = it.body.getRelationship()
val userDetail = userDetailRepository.findByActorId(relationship.actorId.id) val userDetail = userDetailRepository.findByActorId(relationship.actorId.id)
?: throw InternalServerException("Userdetail ${relationship.actorId} not found by actorid.") ?: throw InternalServerException("Userdetail ${relationship.actorId} not found by actorid.")
@ -34,12 +30,9 @@ class TimelineRelationshipFollowSubscriber(
@Suppress("UnsafeCallOnNullableType") @Suppress("UnsafeCallOnNullableType")
userAddTimelineRelationshipApplicationService.execute( userAddTimelineRelationshipApplicationService.execute(
AddTimelineRelationship( AddTimelineRelationship(
TimelineRelationship(
TimelineRelationshipId(idGenerateService.generateId()),
userDetail.homeTimelineId!!, userDetail.homeTimelineId!!,
relationship.targetActorId, relationship.targetActorId,
Visible.FOLLOWERS Visible.FOLLOWERS
)
), ),
it.body.principal it.body.principal
) )

View File

@ -0,0 +1,46 @@
package dev.usbharu.hideout.core.application.domainevent.subscribers
import dev.usbharu.hideout.core.application.timeline.RemoveTimelineRelationship
import dev.usbharu.hideout.core.application.timeline.UserRemoveTimelineRelationshipApplicationService
import dev.usbharu.hideout.core.domain.event.relationship.RelationshipEvent
import dev.usbharu.hideout.core.domain.event.relationship.RelationshipEventBody
import dev.usbharu.hideout.core.domain.model.timelinerelationship.TimelineRelationshipRepository
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailRepository
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Component
@Component
class TimelineRelationshipUnfollowSubscriber(
private val domainEventSubscriber: DomainEventSubscriber,
private val userRemoveTimelineRelationshipApplicationService: UserRemoveTimelineRelationshipApplicationService,
private val userDetailRepository: UserDetailRepository,
private val timelineRelationshipRepository: TimelineRelationshipRepository,
) : Subscriber {
override fun init() {
domainEventSubscriber.subscribe<RelationshipEventBody>(RelationshipEvent.UNFOLLOW.eventName) {
val relationship = it.body.getRelationship()
val userDetail = userDetailRepository.findByActorId(relationship.actorId.id) ?: throw IllegalStateException(
"UserDetail ${relationship.actorId} not found by actorId."
)
if (userDetail.homeTimelineId == null) {
logger.warn("HomeTimeline for ${userDetail.id} not found.")
return@subscribe
}
val timelineRelationship = timelineRelationshipRepository.findByTimelineIdAndActorId(
userDetail.homeTimelineId!!,
relationship.targetActorId
)
?: throw IllegalStateException("TimelineRelationship ${userDetail.homeTimelineId} to ${relationship.targetActorId} not found by timelineId and ActorId")
userRemoveTimelineRelationshipApplicationService.execute(
RemoveTimelineRelationship(timelineRelationship.id),
it.body.principal
)
}
}
companion object {
private val logger = LoggerFactory.getLogger(TimelineRelationshipUnfollowSubscriber::class.java)
}
}

View File

@ -1,7 +1,11 @@
package dev.usbharu.hideout.core.application.timeline package dev.usbharu.hideout.core.application.timeline
import dev.usbharu.hideout.core.domain.model.timelinerelationship.TimelineRelationship 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.timelinerelationship.Visible
data class AddTimelineRelationship( data class AddTimelineRelationship(
val timelineRelationship: TimelineRelationship val timelineId: TimelineId,
val actorId: ActorId,
val visible: Visible
) )

View File

@ -0,0 +1,5 @@
package dev.usbharu.hideout.core.application.timeline
import dev.usbharu.hideout.core.domain.model.timelinerelationship.TimelineRelationshipId
data class RemoveTimelineRelationship(val timelineRelationshipId: TimelineRelationshipId)

View File

@ -1,15 +1,22 @@
package dev.usbharu.hideout.core.application.timeline package dev.usbharu.hideout.core.application.timeline
import dev.usbharu.hideout.core.application.exception.PermissionDeniedException
import dev.usbharu.hideout.core.application.shared.LocalUserAbstractApplicationService import dev.usbharu.hideout.core.application.shared.LocalUserAbstractApplicationService
import dev.usbharu.hideout.core.application.shared.Transaction 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.support.principal.LocalUser
import dev.usbharu.hideout.core.domain.model.timeline.TimelineRepository
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.TimelineRelationshipRepository
import dev.usbharu.hideout.core.domain.shared.id.IdGenerateService
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
@Service @Service
class UserAddTimelineRelationshipApplicationService( class UserAddTimelineRelationshipApplicationService(
private val timelineRelationshipRepository: TimelineRelationshipRepository, private val timelineRelationshipRepository: TimelineRelationshipRepository,
private val timelineRepository: TimelineRepository,
private val idGenerateService: IdGenerateService,
transaction: Transaction transaction: Transaction
) : ) :
LocalUserAbstractApplicationService<AddTimelineRelationship, Unit>( LocalUserAbstractApplicationService<AddTimelineRelationship, Unit>(
@ -17,7 +24,21 @@ class UserAddTimelineRelationshipApplicationService(
logger logger
) { ) {
override suspend fun internalExecute(command: AddTimelineRelationship, principal: LocalUser) { override suspend fun internalExecute(command: AddTimelineRelationship, principal: LocalUser) {
timelineRelationshipRepository.save(command.timelineRelationship) val timeline = timelineRepository.findById(command.timelineId)
?: throw IllegalArgumentException("Timeline ${command.timelineId} not found.")
if (timeline.userDetailId != principal.userDetailId) {
throw PermissionDeniedException()
}
timelineRelationshipRepository.save(
TimelineRelationship(
TimelineRelationshipId(idGenerateService.generateId()),
command.timelineId,
command.actorId,
command.visible
)
)
} }
companion object { companion object {

View File

@ -0,0 +1,39 @@
package dev.usbharu.hideout.core.application.timeline
import dev.usbharu.hideout.core.application.exception.PermissionDeniedException
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.TimelineRepository
import dev.usbharu.hideout.core.domain.model.timelinerelationship.TimelineRelationshipRepository
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
@Service
class UserRemoveTimelineRelationshipApplicationService(
transaction: Transaction,
private val timelineRelationshipRepository: TimelineRelationshipRepository,
private val timelineRepository: TimelineRepository
) :
LocalUserAbstractApplicationService<RemoveTimelineRelationship, Unit>(
transaction, logger
) {
override suspend fun internalExecute(command: RemoveTimelineRelationship, principal: LocalUser) {
val timelineRelationship = (timelineRelationshipRepository.findById(command.timelineRelationshipId)
?: throw IllegalArgumentException("TimelineRelationship ${command.timelineRelationshipId} not found."))
val timeline = (timelineRepository.findById(timelineRelationship.timelineId)
?: throw IllegalArgumentException("Timeline ${timelineRelationship.timelineId} not found."))
if (timeline.userDetailId != principal.userDetailId) {
throw PermissionDeniedException()
}
timelineRelationshipRepository.delete(timelineRelationship)
}
companion object {
private val logger = LoggerFactory.getLogger(UserRemoveTimelineRelationshipApplicationService::class.java)
}
}

View File

@ -35,7 +35,8 @@ class RelationshipEventBody(
} }
enum class RelationshipEvent(val eventName: String) { enum class RelationshipEvent(val eventName: String) {
FOLLOW("RelationshipFollow"), ACCEPT_FOLLOW("RelationshipFollow"),
REJECT_FOLLOW("RelationshipRejectFollow"),
UNFOLLOW("RelationshipUnfollow"), UNFOLLOW("RelationshipUnfollow"),
BLOCK("RelationshipBlock"), BLOCK("RelationshipBlock"),
UNBLOCK("RelationshipUnblock"), UNBLOCK("RelationshipUnblock"),

View File

@ -44,12 +44,6 @@ class Relationship(
var mutingFollowRequest: Boolean = mutingFollowRequest var mutingFollowRequest: Boolean = mutingFollowRequest
private set private set
fun follow() {
require(blocking.not())
following = true
addDomainEvent(RelationshipEventFactory(this).createEvent(RelationshipEvent.FOLLOW))
}
fun unfollow() { fun unfollow() {
following = false following = false
addDomainEvent(RelationshipEventFactory(this).createEvent(RelationshipEvent.UNFOLLOW)) addDomainEvent(RelationshipEventFactory(this).createEvent(RelationshipEvent.UNFOLLOW))
@ -96,11 +90,15 @@ class Relationship(
} }
fun acceptFollowRequest() { fun acceptFollowRequest() {
follow() require(blocking.not())
following = true
addDomainEvent(RelationshipEventFactory(this).createEvent(RelationshipEvent.ACCEPT_FOLLOW))
followRequesting = false followRequesting = false
} }
fun rejectFollowRequest() { fun rejectFollowRequest() {
require(followRequesting)
addDomainEvent(RelationshipEventFactory(this).createEvent(RelationshipEvent.REJECT_FOLLOW))
followRequesting = false followRequesting = false
} }

View File

@ -8,7 +8,20 @@ class TimelineRelationship(
val timelineId: TimelineId, val timelineId: TimelineId,
val actorId: ActorId, val actorId: ActorId,
val visible: Visible val visible: Visible
) ) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as TimelineRelationship
return id == other.id
}
override fun hashCode(): Int {
return id.hashCode()
}
}
enum class Visible { enum class Visible {
PUBLIC, PUBLIC,

View File

@ -1,10 +1,13 @@
package dev.usbharu.hideout.core.domain.model.timelinerelationship package dev.usbharu.hideout.core.domain.model.timelinerelationship
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
interface TimelineRelationshipRepository { interface TimelineRelationshipRepository {
suspend fun save(timelineRelationship: TimelineRelationship): TimelineRelationship suspend fun save(timelineRelationship: TimelineRelationship): TimelineRelationship
suspend fun delete(timelineRelationship: TimelineRelationship) suspend fun delete(timelineRelationship: TimelineRelationship)
suspend fun findByActorId(actorId: ActorId): List<TimelineRelationship> suspend fun findByActorId(actorId: ActorId): List<TimelineRelationship>
suspend fun findById(timelineRelationshipId: TimelineRelationshipId): TimelineRelationship?
suspend fun findByTimelineIdAndActorId(timelineId: TimelineId, actorId: ActorId): TimelineRelationship?
} }