From a939dd5f305c8ac019e6bd03d5eff4f9881e5303 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Sat, 8 Jun 2024 18:12:08 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=E3=83=96=E3=83=AD=E3=83=83=E3=82=AF?= =?UTF-8?q?=E7=AD=89=E3=81=8C=E3=81=A7=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/application/relationship/Block.kt | 19 ++++ .../application/relationship/FollowRequest.kt | 19 ++++ .../relationship/GetRelationship.kt | 19 ++++ .../GetRelationshipApplicationService.kt | 68 +++++++++++++ .../application/relationship/Relationship.kt | 58 +++++++++++ .../core/application/relationship/Unblock.kt | 19 ++++ .../UserBlockApplicationService.kt | 69 +++++++++++++ .../UserFollowRequestApplicationService.kt | 61 ++++++++++++ .../UserUnblockApplicationService.kt | 59 +++++++++++ .../ActorInstanceRelationship.kt | 12 ++- .../ActorInstanceRelationshipRepository.kt | 4 + .../domain/model/relationship/Relationship.kt | 31 ++++++ .../relationship/RelationshipRepository.kt | 3 + .../relationship/RelationshipDomainService.kt | 33 +++++++ ...osedActorInstanceRelationshipRepository.kt | 98 +++++++++++++++++++ .../ExposedRelationshipRepository.kt | 94 ++++++++++++++++++ .../oauth2/Oauth2CommandExecutor.kt | 4 +- .../resources/db/migration/V1__Init_DB.sql | 12 +++ .../application/accounts/GetAccount.kt | 19 ++++ .../accounts/GetAccountApplicationService.kt | 40 ++++++++ .../interfaces/api/SpringAccountApi.kt | 57 ++++++++++- 21 files changed, 792 insertions(+), 6 deletions(-) create mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/Block.kt create mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/FollowRequest.kt create mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/GetRelationship.kt create mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/GetRelationshipApplicationService.kt create mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/Relationship.kt create mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/Unblock.kt create mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/UserBlockApplicationService.kt create mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/UserFollowRequestApplicationService.kt create mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/UserUnblockApplicationService.kt create mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/relationship/RelationshipDomainService.kt create mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedActorInstanceRelationshipRepository.kt create mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedRelationshipRepository.kt create mode 100644 hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/accounts/GetAccount.kt create mode 100644 hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/accounts/GetAccountApplicationService.kt diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/Block.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/Block.kt new file mode 100644 index 00000000..a80ad2b7 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/Block.kt @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2024 usbharu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.usbharu.hideout.core.application.relationship + +data class Block(val targetActorId: Long) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/FollowRequest.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/FollowRequest.kt new file mode 100644 index 00000000..eb61df82 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/FollowRequest.kt @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2024 usbharu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.usbharu.hideout.core.application.relationship + +data class FollowRequest(val targetActorId: Long) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/GetRelationship.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/GetRelationship.kt new file mode 100644 index 00000000..c040253b --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/GetRelationship.kt @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2024 usbharu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.usbharu.hideout.core.application.relationship + +data class GetRelationship(val targetActorId: Long) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/GetRelationshipApplicationService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/GetRelationshipApplicationService.kt new file mode 100644 index 00000000..9ba79ff3 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/GetRelationshipApplicationService.kt @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2024 usbharu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.usbharu.hideout.core.application.relationship + +import dev.usbharu.hideout.core.application.shared.AbstractApplicationService +import dev.usbharu.hideout.core.application.shared.CommandExecutor +import dev.usbharu.hideout.core.application.shared.Transaction +import dev.usbharu.hideout.core.application.shared.UserDetailGettableCommandExecutor +import dev.usbharu.hideout.core.domain.model.actor.ActorId +import dev.usbharu.hideout.core.domain.model.actor.ActorRepository +import dev.usbharu.hideout.core.domain.model.actorinstancerelationship.ActorInstanceRelationship +import dev.usbharu.hideout.core.domain.model.actorinstancerelationship.ActorInstanceRelationshipRepository +import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository +import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailRepository +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Service + +@Service +class GetRelationshipApplicationService( + private val relationshipRepository: RelationshipRepository, + private val actorRepository: ActorRepository, + private val userDetailRepository: UserDetailRepository, + private val actorInstanceRelationshipRepository: ActorInstanceRelationshipRepository, + transaction: Transaction, +) : + AbstractApplicationService( + transaction, logger + ) { + companion object { + private val logger = LoggerFactory.getLogger(GetRelationshipApplicationService::class.java) + } + + override suspend fun internalExecute(command: GetRelationship, executor: CommandExecutor): Relationship { + require(executor is UserDetailGettableCommandExecutor) + val userDetail = userDetailRepository.findById(executor.userDetailId)!! + val actor = actorRepository.findById(userDetail.actorId)!! + val targetId = ActorId(command.targetActorId) + val target = actorRepository.findById(targetId)!! + val relationship = (relationshipRepository.findByActorIdAndTargetId(actor.id, targetId) + ?: dev.usbharu.hideout.core.domain.model.relationship.Relationship.default(actor.id, targetId)) + + val relationship1 = (relationshipRepository.findByActorIdAndTargetId(targetId, actor.id) + ?: dev.usbharu.hideout.core.domain.model.relationship.Relationship.default(targetId, actor.id)) + + val actorInstanceRelationship = + actorInstanceRelationshipRepository.findByActorIdAndInstanceId(actor.id, target.instance) + ?: ActorInstanceRelationship.default( + actor.id, + target.instance + ) + + return Relationship.of(relationship, relationship1, actorInstanceRelationship) + } +} \ No newline at end of file diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/Relationship.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/Relationship.kt new file mode 100644 index 00000000..c7b2377a --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/Relationship.kt @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2024 usbharu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.usbharu.hideout.core.application.relationship + +import dev.usbharu.hideout.core.domain.model.actorinstancerelationship.ActorInstanceRelationship +import dev.usbharu.hideout.core.domain.model.relationship.Relationship + +data class Relationship( + val actorId: Long, + val targetId: Long, + val following: Boolean, + val followedBy: Boolean, + val blocking: Boolean, + val blockedBy: Boolean, + val muting: Boolean, + val followRequesting: Boolean, + val followRequestedBy: Boolean, + val domainBlocking: Boolean, + val domainMuting: Boolean, + val domainDoNotSendPrivate: Boolean, +) { + companion object { + fun of( + relationship: Relationship, + relationship2: Relationship, + actorInstanceRelationship: ActorInstanceRelationship, + ): dev.usbharu.hideout.core.application.relationship.Relationship { + return Relationship( + relationship.actorId.id, + relationship.targetActorId.id, + relationship.following, + relationship2.following, + relationship.blocking, + relationship2.blocking, + relationship.muting, + relationship.followRequesting, + relationship2.followRequesting, + actorInstanceRelationship.isBlocking(), + actorInstanceRelationship.isMuting(), + actorInstanceRelationship.isDoNotSendPrivate() + ) + } + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/Unblock.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/Unblock.kt new file mode 100644 index 00000000..db5d856d --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/Unblock.kt @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2024 usbharu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.usbharu.hideout.core.application.relationship + +data class Unblock(val targetActorId: Long) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/UserBlockApplicationService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/UserBlockApplicationService.kt new file mode 100644 index 00000000..8103b9e4 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/UserBlockApplicationService.kt @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2024 usbharu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.usbharu.hideout.core.application.relationship + +import dev.usbharu.hideout.core.application.shared.AbstractApplicationService +import dev.usbharu.hideout.core.application.shared.CommandExecutor +import dev.usbharu.hideout.core.application.shared.Transaction +import dev.usbharu.hideout.core.application.shared.UserDetailGettableCommandExecutor +import dev.usbharu.hideout.core.domain.model.actor.ActorId +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.domain.model.userdetails.UserDetailRepository +import dev.usbharu.hideout.core.domain.service.relationship.RelationshipDomainService +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Service + +@Service +class UserBlockApplicationService( + private val relationshipRepository: RelationshipRepository, + transaction: Transaction, + private val actorRepository: ActorRepository, + private val userDetailRepository: UserDetailRepository, + private val relationshipDomainService: RelationshipDomainService, +) : + AbstractApplicationService(transaction, logger) { + companion object { + private val logger = LoggerFactory.getLogger(UserBlockApplicationService::class.java) + } + + override suspend fun internalExecute(command: Block, executor: CommandExecutor) { + require(executor is UserDetailGettableCommandExecutor) + + val userDetail = userDetailRepository.findById(executor.userDetailId)!! + val actor = actorRepository.findById(userDetail.actorId)!! + + val targetId = ActorId(command.targetActorId) + val relationship = relationshipRepository.findByActorIdAndTargetId(actor.id, targetId) ?: Relationship.default( + actor.id, + targetId + ) + + val inverseRelationship = + relationshipRepository.findByActorIdAndTargetId(targetId, actor.id) ?: Relationship.default( + targetId, + actor.id + ) + + relationshipDomainService.block(relationship, inverseRelationship) + + + relationshipRepository.save(relationship) + relationshipRepository.save(inverseRelationship) + } +} \ No newline at end of file diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/UserFollowRequestApplicationService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/UserFollowRequestApplicationService.kt new file mode 100644 index 00000000..29c6f805 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/UserFollowRequestApplicationService.kt @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2024 usbharu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.usbharu.hideout.core.application.relationship + +import dev.usbharu.hideout.core.application.shared.AbstractApplicationService +import dev.usbharu.hideout.core.application.shared.CommandExecutor +import dev.usbharu.hideout.core.application.shared.Transaction +import dev.usbharu.hideout.core.application.shared.UserDetailGettableCommandExecutor +import dev.usbharu.hideout.core.domain.model.actor.ActorId +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.domain.model.userdetails.UserDetailRepository +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Service + +@Service +class UserFollowRequestApplicationService( + private val relationshipRepository: RelationshipRepository, + transaction: Transaction, + private val actorRepository: ActorRepository, + private val userDetailRepository: UserDetailRepository, +) : AbstractApplicationService( + transaction, logger +) { + + override suspend fun internalExecute(command: FollowRequest, executor: CommandExecutor) { + require(executor is UserDetailGettableCommandExecutor) + + val userDetail = userDetailRepository.findById(executor.userDetailId)!! + val actor = actorRepository.findById(userDetail.actorId)!! + + val targetId = ActorId(command.targetActorId) + val relationship = relationshipRepository.findByActorIdAndTargetId(actor.id, targetId) ?: Relationship.default( + actor.id, + targetId + ) + + relationship.followRequest() + + relationshipRepository.save(relationship) + } + + companion object { + private val logger = LoggerFactory.getLogger(UserFollowRequestApplicationService::class.java) + } +} \ No newline at end of file diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/UserUnblockApplicationService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/UserUnblockApplicationService.kt new file mode 100644 index 00000000..3bf42d3e --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/UserUnblockApplicationService.kt @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2024 usbharu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.usbharu.hideout.core.application.relationship + +import dev.usbharu.hideout.core.application.shared.AbstractApplicationService +import dev.usbharu.hideout.core.application.shared.CommandExecutor +import dev.usbharu.hideout.core.application.shared.Transaction +import dev.usbharu.hideout.core.application.shared.UserDetailGettableCommandExecutor +import dev.usbharu.hideout.core.domain.model.actor.ActorId +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.domain.model.userdetails.UserDetailRepository +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Service + +@Service +class UserUnblockApplicationService( + private val relationshipRepository: RelationshipRepository, + transaction: Transaction, + private val actorRepository: ActorRepository, + private val userDetailRepository: UserDetailRepository, +) : + AbstractApplicationService(transaction, logger) { + companion object { + private val logger = LoggerFactory.getLogger(UserBlockApplicationService::class.java) + } + + override suspend fun internalExecute(command: Unblock, executor: CommandExecutor) { + require(executor is UserDetailGettableCommandExecutor) + + val userDetail = userDetailRepository.findById(executor.userDetailId)!! + val actor = actorRepository.findById(userDetail.actorId)!! + + val targetId = ActorId(command.targetActorId) + val relationship = relationshipRepository.findByActorIdAndTargetId(actor.id, targetId) ?: Relationship.default( + actor.id, + targetId + ) + + relationship.unblock() + + relationshipRepository.save(relationship) + } +} \ No newline at end of file diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actorinstancerelationship/ActorInstanceRelationship.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actorinstancerelationship/ActorInstanceRelationship.kt index 178716a0..3cef6c86 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actorinstancerelationship/ActorInstanceRelationship.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actorinstancerelationship/ActorInstanceRelationship.kt @@ -96,5 +96,15 @@ data class ActorInstanceRelationship( ")" } - + companion object { + fun default(actorId: ActorId, instanceId: InstanceId): ActorInstanceRelationship { + return ActorInstanceRelationship( + actorId = actorId, + instanceId = instanceId, + blocking = false, + muting = false, + doNotSendPrivate = false + ) + } + } } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actorinstancerelationship/ActorInstanceRelationshipRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actorinstancerelationship/ActorInstanceRelationshipRepository.kt index 5bc7abd5..ea2bba54 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actorinstancerelationship/ActorInstanceRelationshipRepository.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actorinstancerelationship/ActorInstanceRelationshipRepository.kt @@ -16,7 +16,11 @@ package dev.usbharu.hideout.core.domain.model.actorinstancerelationship +import dev.usbharu.hideout.core.domain.model.actor.ActorId +import dev.usbharu.hideout.core.domain.model.instance.InstanceId + interface ActorInstanceRelationshipRepository { suspend fun save(actorInstanceRelationship: ActorInstanceRelationship): ActorInstanceRelationship suspend fun delete(actorInstanceRelationship: ActorInstanceRelationship) + suspend fun findByActorIdAndInstanceId(actorId: ActorId, instanceId: InstanceId): ActorInstanceRelationship? } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/Relationship.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/Relationship.kt index dd06c900..930670a9 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/Relationship.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/Relationship.kt @@ -102,4 +102,35 @@ class Relationship( fun rejectFollowRequest() { followRequesting = false } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Relationship + + if (actorId != other.actorId) return false + if (targetActorId != other.targetActorId) return false + + return true + } + + override fun hashCode(): Int { + var result = actorId.hashCode() + result = 31 * result + targetActorId.hashCode() + return result + } + + + companion object { + fun default(actorId: ActorId, targetActorId: ActorId): Relationship = Relationship( + actorId = actorId, + targetActorId = targetActorId, + following = false, + blocking = false, + muting = false, + followRequesting = false, + mutingFollowRequest = false + ) + } } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/RelationshipRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/RelationshipRepository.kt index 6b84b91c..d74884af 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/RelationshipRepository.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/RelationshipRepository.kt @@ -16,7 +16,10 @@ package dev.usbharu.hideout.core.domain.model.relationship +import dev.usbharu.hideout.core.domain.model.actor.ActorId + interface RelationshipRepository { suspend fun save(relationship: Relationship): Relationship suspend fun delete(relationship: Relationship) + suspend fun findByActorIdAndTargetId(actorId: ActorId, targetId: ActorId): Relationship? } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/relationship/RelationshipDomainService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/relationship/RelationshipDomainService.kt new file mode 100644 index 00000000..2c81724c --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/relationship/RelationshipDomainService.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2024 usbharu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.usbharu.hideout.core.domain.service.relationship + +import dev.usbharu.hideout.core.domain.model.relationship.Relationship +import org.springframework.stereotype.Service + +@Service +class RelationshipDomainService { + fun block(relationship: Relationship, inverseRelationship: Relationship) { + require(relationship != inverseRelationship) + require(relationship.actorId == inverseRelationship.targetActorId) + require(relationship.targetActorId == inverseRelationship.actorId) + + relationship.block() + inverseRelationship.unfollow() + inverseRelationship.unfollowRequest() + } +} \ No newline at end of file diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedActorInstanceRelationshipRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedActorInstanceRelationshipRepository.kt new file mode 100644 index 00000000..9cc1cfd4 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedActorInstanceRelationshipRepository.kt @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2024 usbharu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.usbharu.hideout.core.infrastructure.exposedrepository + +import dev.usbharu.hideout.core.domain.model.actor.ActorId +import dev.usbharu.hideout.core.domain.model.actorinstancerelationship.ActorInstanceRelationship +import dev.usbharu.hideout.core.domain.model.actorinstancerelationship.ActorInstanceRelationshipRepository +import dev.usbharu.hideout.core.domain.model.instance.InstanceId +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.slf4j.Logger +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Repository + + +@Repository +class ExposedActorInstanceRelationshipRepository(override val domainEventPublisher: DomainEventPublisher) : + ActorInstanceRelationshipRepository, AbstractRepository(), + DomainEventPublishableRepository { + override suspend fun save(actorInstanceRelationship: ActorInstanceRelationship): ActorInstanceRelationship { + query { + ActorInstanceRelationships.upsert { + it[actorId] = actorInstanceRelationship.actorId.id + it[instanceId] = actorInstanceRelationship.instanceId.instanceId + it[blocking] = actorInstanceRelationship.isBlocking() + it[muting] = actorInstanceRelationship.isMuting() + it[doNotSendPrivate] = actorInstanceRelationship.isDoNotSendPrivate() + } + } + update(actorInstanceRelationship) + return actorInstanceRelationship + } + + override suspend fun delete(actorInstanceRelationship: ActorInstanceRelationship) { + query { + ActorInstanceRelationships.deleteWhere { + actorId eq actorInstanceRelationship.actorId.id and (instanceId eq actorInstanceRelationship.instanceId.instanceId) + } + } + update(actorInstanceRelationship) + } + + override suspend fun findByActorIdAndInstanceId( + actorId: ActorId, + instanceId: InstanceId, + ): ActorInstanceRelationship? = query { + ActorInstanceRelationships + .selectAll() + .where { + ActorInstanceRelationships.actorId eq actorId.id and (ActorInstanceRelationships.instanceId eq instanceId.instanceId) + } + .singleOrNull() + ?.toActorInstanceRelationship() + } + + override val logger: Logger + get() = Companion.logger + + companion object { + private val logger = LoggerFactory.getLogger(ExposedActorInstanceRelationshipRepository::class.java) + } +} + +private fun ResultRow.toActorInstanceRelationship(): ActorInstanceRelationship { + return ActorInstanceRelationship( + actorId = ActorId(this[ActorInstanceRelationships.actorId]), + instanceId = InstanceId(this[ActorInstanceRelationships.instanceId]), + blocking = this[ActorInstanceRelationships.blocking], + muting = this[ActorInstanceRelationships.muting], + doNotSendPrivate = this[ActorInstanceRelationships.doNotSendPrivate], + ) +} + +object ActorInstanceRelationships : Table("actor_instance_relationships") { + val actorId = long("actor_id").references(Actors.id) + val instanceId = long("instance_id").references(Instance.id) + val blocking = bool("blocking") + val muting = bool("muting") + val doNotSendPrivate = bool("do_not_send_private") + + override val primaryKey: PrimaryKey = PrimaryKey(actorId, instanceId) +} \ No newline at end of file diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedRelationshipRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedRelationshipRepository.kt new file mode 100644 index 00000000..eef233b0 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedRelationshipRepository.kt @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2024 usbharu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.usbharu.hideout.core.infrastructure.exposedrepository + +import dev.usbharu.hideout.core.domain.model.actor.ActorId +import dev.usbharu.hideout.core.domain.model.relationship.Relationship +import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository +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.slf4j.Logger +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Repository + +@Repository +class ExposedRelationshipRepository(override val domainEventPublisher: DomainEventPublisher) : RelationshipRepository, + AbstractRepository(), + DomainEventPublishableRepository { + override suspend fun save(relationship: Relationship): Relationship { + query { + Relationships.upsert { + it[actorId] = relationship.actorId.id + it[targetActorId] = relationship.targetActorId.id + it[following] = relationship.following + it[blocking] = relationship.blocking + it[muting] = relationship.muting + it[followRequesting] = relationship.followRequesting + it[mutingFollowRequest] = relationship.mutingFollowRequest + } + } + update(relationship) + return relationship + } + + override suspend fun delete(relationship: Relationship) { + query { + Relationships.deleteWhere { + actorId eq relationship.actorId.id and (targetActorId eq relationship.targetActorId.id) + } + } + update(relationship) + } + + override suspend fun findByActorIdAndTargetId(actorId: ActorId, targetId: ActorId): Relationship? = query { + Relationships.selectAll().where { + Relationships.actorId eq actorId.id and (Relationships.targetActorId eq targetId.id) + }.singleOrNull()?.toRelationships() + } + + override val logger: Logger + get() = Companion.logger + + + companion object { + private val logger = LoggerFactory.getLogger(ExposedRelationshipRepository::class.java) + } +} + +fun ResultRow.toRelationships(): Relationship = Relationship( + actorId = ActorId(this[Relationships.actorId]), + targetActorId = ActorId(this[Relationships.targetActorId]), + following = this[Relationships.following], + blocking = this[Relationships.blocking], + muting = this[Relationships.muting], + followRequesting = this[Relationships.followRequesting], + mutingFollowRequest = this[Relationships.mutingFollowRequest] +) + +object Relationships : Table("relationships") { + val actorId = long("actor_id").references(Actors.id) + val targetActorId = long("target_actor_id").references(Actors.id) + val following = bool("following") + val blocking = bool("blocking") + val muting = bool("muting") + val followRequesting = bool("follow_requesting") + val mutingFollowRequest = bool("muting_follow_request") + + override val primaryKey: PrimaryKey = PrimaryKey(actorId, targetActorId) +} \ No newline at end of file diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/oauth2/Oauth2CommandExecutor.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/oauth2/Oauth2CommandExecutor.kt index 23f8a73e..3dd05d31 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/oauth2/Oauth2CommandExecutor.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/oauth2/Oauth2CommandExecutor.kt @@ -17,5 +17,7 @@ package dev.usbharu.hideout.core.infrastructure.springframework.oauth2 import dev.usbharu.hideout.core.application.shared.CommandExecutor +import dev.usbharu.hideout.core.application.shared.UserDetailGettableCommandExecutor -class Oauth2CommandExecutor(override val executor: String, val userDetailId: Long) : CommandExecutor \ No newline at end of file +class Oauth2CommandExecutor(override val executor: String, override val userDetailId: Long) : CommandExecutor, + UserDetailGettableCommandExecutor \ No newline at end of file diff --git a/hideout-core/src/main/resources/db/migration/V1__Init_DB.sql b/hideout-core/src/main/resources/db/migration/V1__Init_DB.sql index dc757618..fdc8afea 100644 --- a/hideout-core/src/main/resources/db/migration/V1__Init_DB.sql +++ b/hideout-core/src/main/resources/db/migration/V1__Init_DB.sql @@ -249,3 +249,15 @@ CREATE TABLE oauth2_authorization device_code_metadata varchar(4000) DEFAULT NULL, PRIMARY KEY (id) ); + +create table if not exists actor_instance_relationships +( + actor_id bigint not null, + instance_id bigint not null, + blocking boolean not null, + muting boolean not null, + do_not_send_private boolean not null, + 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 +); \ No newline at end of file diff --git a/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/accounts/GetAccount.kt b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/accounts/GetAccount.kt new file mode 100644 index 00000000..340607b9 --- /dev/null +++ b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/accounts/GetAccount.kt @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2024 usbharu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.usbharu.hideout.mastodon.application.accounts + +data class GetAccount(val accountId: String) diff --git a/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/accounts/GetAccountApplicationService.kt b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/accounts/GetAccountApplicationService.kt new file mode 100644 index 00000000..5fb7249f --- /dev/null +++ b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/accounts/GetAccountApplicationService.kt @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2024 usbharu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.usbharu.hideout.mastodon.application.accounts + +import dev.usbharu.hideout.core.application.shared.AbstractApplicationService +import dev.usbharu.hideout.core.application.shared.CommandExecutor +import dev.usbharu.hideout.core.application.shared.Transaction +import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.Account +import dev.usbharu.hideout.mastodon.query.AccountQueryService +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Service + +@Service +class GetAccountApplicationService(private val accountQueryService: AccountQueryService, transaction: Transaction) : + AbstractApplicationService( + transaction, + logger + ) { + override suspend fun internalExecute(command: GetAccount, executor: CommandExecutor): Account { + return accountQueryService.findById(command.accountId.toLong()) ?: throw Exception("Account not found") + } + + companion object { + private val logger = LoggerFactory.getLogger(GetAccountApplicationService::class.java) + } +} \ No newline at end of file diff --git a/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/SpringAccountApi.kt b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/SpringAccountApi.kt index 7483eda8..8367597f 100644 --- a/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/SpringAccountApi.kt +++ b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/SpringAccountApi.kt @@ -18,9 +18,14 @@ package dev.usbharu.hideout.mastodon.interfaces.api import dev.usbharu.hideout.core.application.actor.GetUserDetail import dev.usbharu.hideout.core.application.actor.GetUserDetailApplicationService +import dev.usbharu.hideout.core.application.relationship.* +import dev.usbharu.hideout.core.infrastructure.springframework.oauth2.Oauth2CommandExecutor import dev.usbharu.hideout.core.infrastructure.springframework.oauth2.Oauth2CommandExecutorFactory +import dev.usbharu.hideout.mastodon.application.accounts.GetAccount +import dev.usbharu.hideout.mastodon.application.accounts.GetAccountApplicationService import dev.usbharu.hideout.mastodon.interfaces.api.generated.AccountApi import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.* +import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.Relationship import org.springframework.http.ResponseEntity import org.springframework.stereotype.Controller @@ -28,20 +33,60 @@ import org.springframework.stereotype.Controller class SpringAccountApi( private val oauth2CommandExecutorFactory: Oauth2CommandExecutorFactory, private val getUserDetailApplicationService: GetUserDetailApplicationService, + private val getAccountApplicationService: GetAccountApplicationService, + private val userFollowRequestApplicationService: UserFollowRequestApplicationService, + private val getRelationshipApplicationService: GetRelationshipApplicationService, + private val userBlockApplicationService: UserBlockApplicationService, + private val userUnblockApplicationService: UserUnblockApplicationService, ) : AccountApi { override suspend fun apiV1AccountsIdBlockPost(id: String): ResponseEntity { - return super.apiV1AccountsIdBlockPost(id) + val executor = oauth2CommandExecutorFactory.getCommandExecutor() + userBlockApplicationService.execute(Block(id.toLong()), executor) + return fetchRelationship(id, executor) } override suspend fun apiV1AccountsIdFollowPost( id: String, followRequestBody: FollowRequestBody?, ): ResponseEntity { - return super.apiV1AccountsIdFollowPost(id, followRequestBody) + val executor = oauth2CommandExecutorFactory.getCommandExecutor() + userFollowRequestApplicationService.execute( + FollowRequest(id.toLong()), executor + ) + return fetchRelationship(id, executor) + } + + private suspend fun fetchRelationship( + id: String, + executor: Oauth2CommandExecutor, + ): ResponseEntity { + val relationship = getRelationshipApplicationService.execute(GetRelationship(id.toLong()), executor) + return ResponseEntity.ok( + Relationship( + id = relationship.targetId.toString(), + following = relationship.following, + showingReblogs = true, + notifying = false, + followedBy = relationship.followedBy, + blocking = relationship.blocking, + blockedBy = relationship.blockedBy, + muting = relationship.muting, + mutingNotifications = false, + requested = relationship.followRequesting, + domainBlocking = relationship.domainBlocking, + endorsed = false, + note = "" + ) + ) } override suspend fun apiV1AccountsIdGet(id: String): ResponseEntity { - return super.apiV1AccountsIdGet(id) + return ResponseEntity.ok( + getAccountApplicationService.execute( + GetAccount(id), + oauth2CommandExecutorFactory.getCommandExecutor() + ) + ) } override suspend fun apiV1AccountsIdMutePost(id: String): ResponseEntity { @@ -53,7 +98,11 @@ class SpringAccountApi( } override suspend fun apiV1AccountsIdUnblockPost(id: String): ResponseEntity { - return super.apiV1AccountsIdUnblockPost(id) + val executor = oauth2CommandExecutorFactory.getCommandExecutor() + userUnblockApplicationService.execute( + Unblock(id.toLong()), executor + ) + return fetchRelationship(id, executor) } override suspend fun apiV1AccountsIdUnfollowPost(id: String): ResponseEntity {