diff --git a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/Actor.kt b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/Actor.kt index 9f0fba72..6c645c45 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/Actor.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/Actor.kt @@ -22,7 +22,11 @@ data class Actor private constructor( val followers: String? = null, val following: String? = null, val instance: Long? = null, - val locked: Boolean + val locked: Boolean, + val followersCount: Int = 0, + val followingCount: Int = 0, + val postsCount: Int = 0, + val lastPostDate: Instant? = null ) { @Component @@ -47,7 +51,11 @@ data class Actor private constructor( following: String? = null, followers: String? = null, instance: Long? = null, - locked: Boolean + locked: Boolean, + followersCount: Int, + followingCount: Int, + postsCount: Int, + lastPostDate: Instant? ): Actor { // idは0未満ではいけない require(id >= 0) { "id must be greater than or equal to 0." } @@ -123,6 +131,18 @@ data class Actor private constructor( "keyId must contain non-blank characters." } + require(postsCount >= 0) { + "postsCount must be greater than or equal to 0" + } + + require(followersCount >= 0) { + "followersCount must be greater than or equal to 0" + } + + require(followingCount >= 0) { + "followingCount must be greater than or equal to 0" + } + return Actor( id = id, name = limitedName, @@ -139,29 +159,47 @@ data class Actor private constructor( followers = followers, following = following, instance = instance, - locked + locked = locked, + followersCount = followersCount, + followingCount = followingCount, + postsCount = postsCount, + lastPostDate = lastPostDate ) } } + fun incrementFollowing(): Actor = this.copy(followingCount = this.followingCount + 1) + + fun decrementFollowing(): Actor = this.copy(followingCount = this.followingCount - 1) + + fun incrementFollowers(): Actor = this.copy(followersCount = this.followersCount + 1) + + fun decrementFollowers(): Actor = this.copy(followersCount = this.followersCount - 1) + + fun incrementPostsCount(): Actor = this.copy(postsCount = this.postsCount + 1) + + fun decrementPostsCount(): Actor = this.copy(postsCount = this.postsCount - 1) + + fun withLastPostAt(lastPostDate: Instant): Actor = this.copy(lastPostDate = lastPostDate) + override fun toString(): String { return "Actor(" + - "id=$id, " + - "name='$name', " + - "domain='$domain', " + - "screenName='$screenName', " + - "description='$description', " + - "inbox='$inbox', " + - "outbox='$outbox', " + - "url='$url', " + - "publicKey='$publicKey', " + - "privateKey=$privateKey, " + - "createdAt=$createdAt, " + - "keyId='$keyId', " + - "followers=$followers, " + - "following=$following, " + - "instance=$instance, " + - "locked=$locked" + - ")" + "id=$id, " + + "name='$name', " + + "domain='$domain', " + + "screenName='$screenName', " + + "description='$description', " + + "inbox='$inbox', " + + "outbox='$outbox', " + + "url='$url', " + + "publicKey='$publicKey', " + + "privateKey=$privateKey, " + + "createdAt=$createdAt, " + + "keyId='$keyId', " + + "followers=$followers, " + + "following=$following, " + + "instance=$instance, " + + "locked=$locked" + + ")" } } diff --git a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/UserResultRowMapper.kt b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/UserResultRowMapper.kt index abf8d880..a5b58c60 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/UserResultRowMapper.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/UserResultRowMapper.kt @@ -26,7 +26,11 @@ class UserResultRowMapper(private val actorBuilder: Actor.UserBuilder) : ResultR followers = resultRow[Actors.followers], following = resultRow[Actors.following], instance = resultRow[Actors.instance], - locked = resultRow[Actors.locked] + locked = resultRow[Actors.locked], + followingCount = resultRow[Actors.followingCount], + followersCount = resultRow[Actors.followersCount], + postsCount = resultRow[Actors.postsCount], + lastPostDate = resultRow[Actors.lastPostAt], ) } } diff --git a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ActorRepositoryImpl.kt b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ActorRepositoryImpl.kt index 0eb59b59..96ce6409 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ActorRepositoryImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ActorRepositoryImpl.kt @@ -6,6 +6,7 @@ import dev.usbharu.hideout.core.domain.model.actor.Actor import dev.usbharu.hideout.core.domain.model.actor.ActorRepository import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq +import org.jetbrains.exposed.sql.javatime.timestamp import org.springframework.stereotype.Repository @Repository @@ -35,6 +36,10 @@ class ActorRepositoryImpl( it[followers] = actor.followers it[instance] = actor.instance it[locked] = actor.locked + it[followersCount] = actor.followersCount + it[followingCount] = actor.followingCount + it[postsCount] = actor.postsCount + it[lastPostAt] = actor.lastPostDate } } else { Actors.update({ Actors.id eq actor.id }) { @@ -53,6 +58,10 @@ class ActorRepositoryImpl( it[followers] = actor.followers it[instance] = actor.instance it[locked] = actor.locked + it[followersCount] = actor.followersCount + it[followingCount] = actor.followingCount + it[postsCount] = actor.postsCount + it[lastPostAt] = actor.lastPostDate } } return actor @@ -91,6 +100,10 @@ object Actors : Table("actors") { val followers = varchar("followers", length = 1000).nullable() val instance = long("instance").references(Instance.id).nullable() val locked = bool("locked") + val followingCount = integer("following_count") + val followersCount = integer("followers_count") + val postsCount = integer("posts_count") + val lastPostAt = timestamp("last_post_at").nullable() override val primaryKey: PrimaryKey = PrimaryKey(id) diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/relationship/RelationshipServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/relationship/RelationshipServiceImpl.kt index 8b508af0..92e47772 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/service/relationship/RelationshipServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/relationship/RelationshipServiceImpl.kt @@ -8,6 +8,7 @@ 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.actor.Actor +import dev.usbharu.hideout.core.domain.model.actor.ActorRepository import dev.usbharu.hideout.core.domain.model.relationship.Relationship import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository import dev.usbharu.hideout.core.query.ActorQueryService @@ -24,7 +25,8 @@ class RelationshipServiceImpl( private val apSendBlockService: APSendBlockService, private val apSendAcceptService: ApSendAcceptService, private val apSendRejectService: ApSendRejectService, - private val apSendUndoService: APSendUndoService + private val apSendUndoService: APSendUndoService, + private val actorRepository: ActorRepository ) : RelationshipService { override suspend fun followRequest(actorId: Long, targetId: Long) { logger.info("START Follow Request userId: {} targetId: {}", actorId, targetId) @@ -90,6 +92,15 @@ class RelationshipServiceImpl( override suspend fun block(actorId: Long, targetId: Long) { val relationship = relationshipRepository.findByUserIdAndTargetUserId(actorId, targetId) + + val user = actorQueryService.findById(actorId) + val targetActor = actorQueryService.findById(targetId) + if (relationship?.following == true) { + actorRepository.save(user.decrementFollowing()) + actorRepository.save(targetActor.decrementFollowers()) + } + + val blockedRelationship = relationship ?.copy(blocking = true, followRequest = false, following = false) ?: Relationship( actorId = actorId, targetActorId = targetId, @@ -101,17 +112,26 @@ class RelationshipServiceImpl( ) val inverseRelationship = relationshipRepository.findByUserIdAndTargetUserId(targetId, actorId) + + + + if (inverseRelationship?.following == true) { + actorRepository.save(targetActor.decrementFollowing()) + actorRepository.save(user.decrementFollowers()) + } + + val blockedInverseRelationship = inverseRelationship ?.copy(followRequest = false, following = false) - relationshipRepository.save(relationship) - if (inverseRelationship != null) { - relationshipRepository.save(inverseRelationship) + relationshipRepository.save(blockedRelationship) + if (blockedInverseRelationship != null) { + relationshipRepository.save(blockedInverseRelationship) } val remoteUser = isRemoteUser(targetId) if (remoteUser != null) { - val user = actorQueryService.findById(actorId) + apSendBlockService.sendBlock(user, remoteUser) } } @@ -157,13 +177,18 @@ class RelationshipServiceImpl( val copy = relationship.copy(followRequest = false, following = true, blocking = false) + val user = actorQueryService.findById(actorId) + + actorRepository.save(user.incrementFollowers()) + relationshipRepository.save(copy) - val remoteUser = isRemoteUser(targetId) + val remoteActor = actorQueryService.findById(targetId) - if (remoteUser != null) { - val user = actorQueryService.findById(actorId) - apSendAcceptService.sendAcceptFollow(user, remoteUser) + actorRepository.save(remoteActor.incrementFollowing()) + + if (isRemoteActor(remoteActor)) { + apSendAcceptService.sendAcceptFollow(user, remoteActor) } } @@ -216,6 +241,14 @@ class RelationshipServiceImpl( return } + val user = actorQueryService.findById(actorId) + val targetActor = actorQueryService.findById(targetId) + + if (relationship.following) { + actorRepository.save(user.decrementFollowing()) + actorRepository.save(targetActor.decrementFollowers()) + } + if (relationship.following.not()) { logger.warn("SUCCESS User already unfollow. userId: {} targetId: {}", actorId, targetId) return @@ -228,7 +261,7 @@ class RelationshipServiceImpl( val remoteUser = isRemoteUser(targetId) if (remoteUser != null) { - val user = actorQueryService.findById(actorId) + apSendUndoService.sendUndoFollow(user, remoteUser) } } @@ -282,6 +315,8 @@ class RelationshipServiceImpl( relationshipRepository.save(relationship) } + private fun isRemoteActor(actor: Actor): Boolean = actor.domain != applicationConfig.url.host + private suspend fun isRemoteUser(userId: Long): Actor? { logger.trace("isRemoteUser({})", userId) val user = try {