mirror of https://github.com/usbharu/Hideout.git
feat: ユーザー間の関係をRelationshipServiceで管理するように
This commit is contained in:
parent
03ae444d54
commit
0b3c27619e
|
@ -0,0 +1,16 @@
|
||||||
|
package dev.usbharu.hideout.activitypub.service.activity.accept
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.domain.model.user.User
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
|
interface ApSendAcceptService {
|
||||||
|
suspend fun sendAccept(user: User, target: User)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class ApSendAcceptServiceImpl : ApSendAcceptService {
|
||||||
|
override suspend fun sendAccept(user: User, target: User) {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
package dev.usbharu.hideout.activitypub.service.activity.block
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.activitypub.domain.model.Block
|
||||||
|
import dev.usbharu.hideout.activitypub.domain.model.Follow
|
||||||
|
import dev.usbharu.hideout.activitypub.domain.model.Reject
|
||||||
|
import dev.usbharu.hideout.application.config.ApplicationConfig
|
||||||
|
import dev.usbharu.hideout.core.domain.model.user.User
|
||||||
|
import dev.usbharu.hideout.core.external.job.DeliverBlockJob
|
||||||
|
import dev.usbharu.hideout.core.external.job.DeliverBlockJobParam
|
||||||
|
import dev.usbharu.hideout.core.service.job.JobQueueParentService
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
|
interface APSendBlockService {
|
||||||
|
suspend fun sendBlock(user: User, target: User)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class ApSendBlockServiceImpl(
|
||||||
|
private val applicationConfig: ApplicationConfig,
|
||||||
|
private val jobQueueParentService: JobQueueParentService,
|
||||||
|
private val deliverBlockJob: DeliverBlockJob
|
||||||
|
) : APSendBlockService {
|
||||||
|
override suspend fun sendBlock(user: User, target: User) {
|
||||||
|
val blockJobParam = DeliverBlockJobParam(
|
||||||
|
user.id,
|
||||||
|
Block(
|
||||||
|
user.url,
|
||||||
|
"${applicationConfig.url}/block/${user.id}/${target.id}",
|
||||||
|
target.url
|
||||||
|
),
|
||||||
|
Reject(
|
||||||
|
user.url,
|
||||||
|
"${applicationConfig.url}/reject/${user.id}/${target.id}",
|
||||||
|
Follow(
|
||||||
|
apObject = user.url,
|
||||||
|
actor = target.url
|
||||||
|
)
|
||||||
|
),
|
||||||
|
target.inbox
|
||||||
|
)
|
||||||
|
jobQueueParentService.scheduleTypeSafe(deliverBlockJob, blockJobParam)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package dev.usbharu.hideout.activitypub.service.activity.reject
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.domain.model.user.User
|
||||||
|
|
||||||
|
interface ApSendRejectService {
|
||||||
|
suspend fun sendReject(user: User, target: User)
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package dev.usbharu.hideout.activitypub.service.activity.undo
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.domain.model.user.User
|
||||||
|
|
||||||
|
interface APSendUndoService {
|
||||||
|
suspend fun sendUndoFollow(user: User, target: User)
|
||||||
|
suspend fun sendUndoBlock(user: User, target: User)
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package dev.usbharu.hideout.core.domain.model.relationship
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ユーザーとの関係を表します
|
||||||
|
*
|
||||||
|
* @property userId ユーザー
|
||||||
|
* @property targetUserId 相手ユーザー
|
||||||
|
* @property following フォローしているか
|
||||||
|
* @property blocking ブロックしているか
|
||||||
|
* @property muting ミュートしているか
|
||||||
|
* @property followRequest フォローリクエストを送っているか
|
||||||
|
* @property ignoreFollowRequestFromTarget フォローリクエストを無視しているか
|
||||||
|
*/
|
||||||
|
data class Relationship(
|
||||||
|
val userId: Long,
|
||||||
|
val targetUserId: Long,
|
||||||
|
val following: Boolean,
|
||||||
|
val blocking: Boolean,
|
||||||
|
val muting: Boolean,
|
||||||
|
val followRequest: Boolean,
|
||||||
|
val ignoreFollowRequestFromTarget: Boolean
|
||||||
|
)
|
|
@ -0,0 +1,31 @@
|
||||||
|
package dev.usbharu.hideout.core.domain.model.relationship
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [Relationship]の永続化
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
interface RelationshipRepository {
|
||||||
|
/**
|
||||||
|
* 永続化します
|
||||||
|
*
|
||||||
|
* @param relationship 永続化する[Relationship]
|
||||||
|
* @return 永続化された[Relationship]
|
||||||
|
*/
|
||||||
|
suspend fun save(relationship: Relationship): Relationship
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 永続化されたものを削除します
|
||||||
|
*
|
||||||
|
* @param relationship 削除する[Relationship]
|
||||||
|
*/
|
||||||
|
suspend fun delete(relationship: Relationship)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* userIdとtargetUserIdで[Relationship]を取得します
|
||||||
|
*
|
||||||
|
* @param userId 取得するユーザーID
|
||||||
|
* @param targetUserId 対象ユーザーID
|
||||||
|
* @return 取得された[Relationship] 存在しない場合nullが返ります
|
||||||
|
*/
|
||||||
|
suspend fun findByUserIdAndTargetUserId(userId: Long, targetUserId: Long): Relationship?
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package dev.usbharu.hideout.core.service.relationship
|
||||||
|
|
||||||
|
interface RelationshipService {
|
||||||
|
suspend fun followRequest(userId: Long, targetId: Long)
|
||||||
|
suspend fun block(userId: Long, targetId: Long)
|
||||||
|
suspend fun acceptFollowRequest(userId: Long, targetId: Long)
|
||||||
|
suspend fun rejectFollowRequest(userId: Long, targetId: Long)
|
||||||
|
suspend fun ignoreFollowRequest(userId: Long, targetId: Long)
|
||||||
|
suspend fun unfollow(userId: Long, targetId: Long)
|
||||||
|
suspend fun unblock(userId: Long, targetId: Long)
|
||||||
|
suspend fun mute(userId: Long, targetId: Long)
|
||||||
|
suspend fun unmute(userId: Long, targetId: Long)
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,266 @@
|
||||||
|
package dev.usbharu.hideout.core.service.relationship
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.activitypub.service.activity.accept.ApSendAcceptService
|
||||||
|
import dev.usbharu.hideout.activitypub.service.activity.block.APSendBlockService
|
||||||
|
import dev.usbharu.hideout.activitypub.service.activity.follow.APSendFollowService
|
||||||
|
import dev.usbharu.hideout.activitypub.service.activity.reject.ApSendRejectService
|
||||||
|
import dev.usbharu.hideout.activitypub.service.activity.undo.APSendUndoService
|
||||||
|
import dev.usbharu.hideout.application.config.ApplicationConfig
|
||||||
|
import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException
|
||||||
|
import dev.usbharu.hideout.core.domain.model.relationship.Relationship
|
||||||
|
import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository
|
||||||
|
import dev.usbharu.hideout.core.domain.model.user.User
|
||||||
|
import dev.usbharu.hideout.core.query.UserQueryService
|
||||||
|
import dev.usbharu.hideout.core.service.follow.SendFollowDto
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class RelationshipServiceImpl(
|
||||||
|
private val applicationConfig: ApplicationConfig,
|
||||||
|
private val userQueryService: UserQueryService,
|
||||||
|
private val relationshipRepository: RelationshipRepository,
|
||||||
|
private val apSendFollowService: APSendFollowService,
|
||||||
|
private val apSendBlockService: APSendBlockService,
|
||||||
|
private val apSendAcceptService: ApSendAcceptService,
|
||||||
|
private val apSendRejectService: ApSendRejectService,
|
||||||
|
private val apSendUndoService: APSendUndoService
|
||||||
|
) : RelationshipService {
|
||||||
|
override suspend fun followRequest(userId: Long, targetId: Long) {
|
||||||
|
val relationship =
|
||||||
|
relationshipRepository.findByUserIdAndTargetUserId(userId, targetId)?.copy(followRequest = true)
|
||||||
|
?: Relationship(
|
||||||
|
userId = userId,
|
||||||
|
targetUserId = targetId,
|
||||||
|
following = false,
|
||||||
|
blocking = false,
|
||||||
|
muting = false,
|
||||||
|
followRequest = true,
|
||||||
|
ignoreFollowRequestFromTarget = false
|
||||||
|
)
|
||||||
|
|
||||||
|
val inverseRelationship = relationshipRepository.findByUserIdAndTargetUserId(targetId, userId) ?: Relationship(
|
||||||
|
userId = targetId,
|
||||||
|
targetUserId = userId,
|
||||||
|
following = false,
|
||||||
|
blocking = false,
|
||||||
|
muting = false,
|
||||||
|
followRequest = false,
|
||||||
|
ignoreFollowRequestFromTarget = false
|
||||||
|
)
|
||||||
|
|
||||||
|
if (inverseRelationship.blocking) {
|
||||||
|
logger.debug("FAILED Blocked by target. userId: {} targetId: {}", userId, targetId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (relationship.blocking) {
|
||||||
|
logger.debug("FAILED Blocking user. userId: {} targetId: {}", userId, targetId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (inverseRelationship.ignoreFollowRequestFromTarget) {
|
||||||
|
logger.debug("SUCCESS Ignore Follow Request. userId: {} targetId: {}", userId, targetId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
relationshipRepository.save(relationship)
|
||||||
|
|
||||||
|
val remoteUser = isRemoteUser(targetId)
|
||||||
|
|
||||||
|
if (remoteUser != null) {
|
||||||
|
val user = userQueryService.findById(userId)
|
||||||
|
apSendFollowService.sendFollow(SendFollowDto(user, remoteUser))
|
||||||
|
} else {
|
||||||
|
//TODO: フォロー許可制ユーザーを実装したら消す
|
||||||
|
acceptFollowRequest(userId, targetId)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun block(userId: Long, targetId: Long) {
|
||||||
|
val relationship = relationshipRepository.findByUserIdAndTargetUserId(userId, targetId)
|
||||||
|
?.copy(blocking = true, followRequest = false, following = false) ?: Relationship(
|
||||||
|
userId = userId,
|
||||||
|
targetUserId = targetId,
|
||||||
|
following = false,
|
||||||
|
blocking = true,
|
||||||
|
muting = false,
|
||||||
|
followRequest = false,
|
||||||
|
ignoreFollowRequestFromTarget = false
|
||||||
|
)
|
||||||
|
|
||||||
|
relationshipRepository.save(relationship)
|
||||||
|
|
||||||
|
val remoteUser = isRemoteUser(targetId)
|
||||||
|
|
||||||
|
if (remoteUser != null) {
|
||||||
|
val user = userQueryService.findById(userId)
|
||||||
|
apSendBlockService.sendBlock(user, remoteUser)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun acceptFollowRequest(userId: Long, targetId: Long) {
|
||||||
|
val relationship = relationshipRepository.findByUserIdAndTargetUserId(userId, targetId)
|
||||||
|
|
||||||
|
if (relationship == null) {
|
||||||
|
logger.warn("FAILED Follow Request Not Found. (Relationship) userId: {} targetId: {}", userId, targetId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (relationship.followRequest.not()) {
|
||||||
|
logger.warn("FAILED Follow Request Not Found. (Follow Request) userId: {} targetId: {}", userId, targetId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (relationship.blocking) {
|
||||||
|
logger.warn("FAILED Blocking user userId: {} targetId: {}", userId, targetId)
|
||||||
|
throw IllegalStateException("Cannot accept a follow request from a blocked user. userId: $userId targetId: $targetId")
|
||||||
|
}
|
||||||
|
|
||||||
|
val copy = relationship.copy(followRequest = false, following = true, blocking = false)
|
||||||
|
|
||||||
|
relationshipRepository.save(copy)
|
||||||
|
|
||||||
|
val remoteUser = isRemoteUser(targetId)
|
||||||
|
|
||||||
|
if (remoteUser != null) {
|
||||||
|
val user = userQueryService.findById(userId)
|
||||||
|
apSendAcceptService.sendAccept(user, remoteUser)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun rejectFollowRequest(userId: Long, targetId: Long) {
|
||||||
|
val relationship = relationshipRepository.findByUserIdAndTargetUserId(userId, targetId)
|
||||||
|
|
||||||
|
if (relationship == null) {
|
||||||
|
logger.warn("FAILED Follow Request Not Found. (Relationship) userId: {} targetId: {}", userId, targetId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (relationship.followRequest.not()) {
|
||||||
|
logger.warn("FAILED Follow Request Not Found. (Follow Request) userId: {} targetId: {}", userId, targetId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val copy = relationship.copy(followRequest = false, following = false, blocking = false)
|
||||||
|
|
||||||
|
relationshipRepository.save(copy)
|
||||||
|
|
||||||
|
val remoteUser = isRemoteUser(targetId)
|
||||||
|
|
||||||
|
if (remoteUser != null) {
|
||||||
|
val user = userQueryService.findById(userId)
|
||||||
|
apSendRejectService.sendReject(user, remoteUser)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun ignoreFollowRequest(userId: Long, targetId: Long) {
|
||||||
|
val relationship = relationshipRepository.findByUserIdAndTargetUserId(userId, targetId)
|
||||||
|
?.copy(ignoreFollowRequestFromTarget = true)
|
||||||
|
?: Relationship(
|
||||||
|
userId = userId,
|
||||||
|
targetUserId = targetId,
|
||||||
|
following = false,
|
||||||
|
blocking = false,
|
||||||
|
muting = false,
|
||||||
|
followRequest = false,
|
||||||
|
ignoreFollowRequestFromTarget = true
|
||||||
|
)
|
||||||
|
|
||||||
|
relationshipRepository.save(relationship)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun unfollow(userId: Long, targetId: Long) {
|
||||||
|
val relationship = relationshipRepository.findByUserIdAndTargetUserId(userId, targetId)
|
||||||
|
|
||||||
|
if (relationship == null) {
|
||||||
|
logger.warn("FAILED Unfollow. (Relationship) userId: {} targetId: {}", userId, targetId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (relationship.following.not()) {
|
||||||
|
logger.warn("SUCCESS User already unfollow. userId: {} targetId: {}", userId, targetId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val copy = relationship.copy(following = false)
|
||||||
|
|
||||||
|
relationshipRepository.save(copy)
|
||||||
|
|
||||||
|
val remoteUser = isRemoteUser(targetId)
|
||||||
|
|
||||||
|
if (remoteUser != null) {
|
||||||
|
val user = userQueryService.findById(userId)
|
||||||
|
apSendUndoService.sendUndoFollow(user, remoteUser)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun unblock(userId: Long, targetId: Long) {
|
||||||
|
val relationship = relationshipRepository.findByUserIdAndTargetUserId(userId, targetId)
|
||||||
|
|
||||||
|
if (relationship == null) {
|
||||||
|
logger.warn("FAILED Unblock. (Relationship) userId: {} targetId: {}", userId, targetId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (relationship.blocking.not()) {
|
||||||
|
logger.warn("SUCCESS User is not blocking. userId: {] targetId: {}", userId, targetId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val copy = relationship.copy(blocking = false)
|
||||||
|
relationshipRepository.save(copy)
|
||||||
|
|
||||||
|
|
||||||
|
val remoteUser = isRemoteUser(targetId)
|
||||||
|
if (remoteUser == null) {
|
||||||
|
val user = userQueryService.findById(userId)
|
||||||
|
apSendUndoService.sendUndoBlock(user, targetId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun mute(userId: Long, targetId: Long) {
|
||||||
|
val relationship = relationshipRepository.findByUserIdAndTargetUserId(userId, targetId)?.copy(muting = true)
|
||||||
|
?: Relationship(
|
||||||
|
userId = userId,
|
||||||
|
targetUserId = targetId,
|
||||||
|
following = false,
|
||||||
|
blocking = false,
|
||||||
|
muting = true,
|
||||||
|
followRequest = false,
|
||||||
|
ignoreFollowRequestFromTarget = false
|
||||||
|
)
|
||||||
|
|
||||||
|
relationshipRepository.save(relationship)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun unmute(userId: Long, targetId: Long) {
|
||||||
|
val relationship = relationshipRepository.findByUserIdAndTargetUserId(userId, targetId)?.copy(muting = false)
|
||||||
|
|
||||||
|
if (relationship == null) {
|
||||||
|
logger.warn("FAILED Mute. (Relationship) userId: {} targetId: {}", userId, targetId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
relationshipRepository.save(relationship)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun isRemoteUser(userId: Long): User? {
|
||||||
|
val user = try {
|
||||||
|
userQueryService.findById(userId)
|
||||||
|
} catch (e: FailedToGetResourcesException) {
|
||||||
|
logger.warn("User not found.", e)
|
||||||
|
throw IllegalStateException("User not found.", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user.domain == applicationConfig.url.host) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val logger = LoggerFactory.getLogger(RelationshipServiceImpl::class.java)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue