feat: ブロック等ができるように

This commit is contained in:
usbharu 2024-06-08 18:12:08 +09:00
parent 54e3af2253
commit a939dd5f30
21 changed files with 792 additions and 6 deletions

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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<GetRelationship, Relationship>(
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)
}
}

View File

@ -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()
)
}
}
}

View File

@ -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)

View File

@ -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<Block, Unit>(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)
}
}

View File

@ -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<FollowRequest, Unit>(
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)
}
}

View File

@ -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<Unblock, Unit>(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)
}
}

View File

@ -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
)
}
}
} }

View File

@ -16,7 +16,11 @@
package dev.usbharu.hideout.core.domain.model.actorinstancerelationship 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 { interface ActorInstanceRelationshipRepository {
suspend fun save(actorInstanceRelationship: ActorInstanceRelationship): ActorInstanceRelationship suspend fun save(actorInstanceRelationship: ActorInstanceRelationship): ActorInstanceRelationship
suspend fun delete(actorInstanceRelationship: ActorInstanceRelationship) suspend fun delete(actorInstanceRelationship: ActorInstanceRelationship)
suspend fun findByActorIdAndInstanceId(actorId: ActorId, instanceId: InstanceId): ActorInstanceRelationship?
} }

View File

@ -102,4 +102,35 @@ class Relationship(
fun rejectFollowRequest() { fun rejectFollowRequest() {
followRequesting = false 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
)
}
} }

View File

@ -16,7 +16,10 @@
package dev.usbharu.hideout.core.domain.model.relationship package dev.usbharu.hideout.core.domain.model.relationship
import dev.usbharu.hideout.core.domain.model.actor.ActorId
interface RelationshipRepository { interface RelationshipRepository {
suspend fun save(relationship: Relationship): Relationship suspend fun save(relationship: Relationship): Relationship
suspend fun delete(relationship: Relationship) suspend fun delete(relationship: Relationship)
suspend fun findByActorIdAndTargetId(actorId: ActorId, targetId: ActorId): Relationship?
} }

View File

@ -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()
}
}

View File

@ -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<ActorInstanceRelationship> {
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)
}

View File

@ -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<Relationship> {
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)
}

View File

@ -17,5 +17,7 @@
package dev.usbharu.hideout.core.infrastructure.springframework.oauth2 package dev.usbharu.hideout.core.infrastructure.springframework.oauth2
import dev.usbharu.hideout.core.application.shared.CommandExecutor 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 class Oauth2CommandExecutor(override val executor: String, override val userDetailId: Long) : CommandExecutor,
UserDetailGettableCommandExecutor

View File

@ -249,3 +249,15 @@ CREATE TABLE oauth2_authorization
device_code_metadata varchar(4000) DEFAULT NULL, device_code_metadata varchar(4000) DEFAULT NULL,
PRIMARY KEY (id) 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
);

View File

@ -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)

View File

@ -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<GetAccount, Account>(
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)
}
}

View File

@ -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.GetUserDetail
import dev.usbharu.hideout.core.application.actor.GetUserDetailApplicationService 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.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.AccountApi
import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.* 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.http.ResponseEntity
import org.springframework.stereotype.Controller import org.springframework.stereotype.Controller
@ -28,20 +33,60 @@ import org.springframework.stereotype.Controller
class SpringAccountApi( class SpringAccountApi(
private val oauth2CommandExecutorFactory: Oauth2CommandExecutorFactory, private val oauth2CommandExecutorFactory: Oauth2CommandExecutorFactory,
private val getUserDetailApplicationService: GetUserDetailApplicationService, 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 { ) : AccountApi {
override suspend fun apiV1AccountsIdBlockPost(id: String): ResponseEntity<Relationship> { override suspend fun apiV1AccountsIdBlockPost(id: String): ResponseEntity<Relationship> {
return super.apiV1AccountsIdBlockPost(id) val executor = oauth2CommandExecutorFactory.getCommandExecutor()
userBlockApplicationService.execute(Block(id.toLong()), executor)
return fetchRelationship(id, executor)
} }
override suspend fun apiV1AccountsIdFollowPost( override suspend fun apiV1AccountsIdFollowPost(
id: String, id: String,
followRequestBody: FollowRequestBody?, followRequestBody: FollowRequestBody?,
): ResponseEntity<Relationship> { ): ResponseEntity<Relationship> {
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<Relationship> {
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<Account> { override suspend fun apiV1AccountsIdGet(id: String): ResponseEntity<Account> {
return super.apiV1AccountsIdGet(id) return ResponseEntity.ok(
getAccountApplicationService.execute(
GetAccount(id),
oauth2CommandExecutorFactory.getCommandExecutor()
)
)
} }
override suspend fun apiV1AccountsIdMutePost(id: String): ResponseEntity<Relationship> { override suspend fun apiV1AccountsIdMutePost(id: String): ResponseEntity<Relationship> {
@ -53,7 +98,11 @@ class SpringAccountApi(
} }
override suspend fun apiV1AccountsIdUnblockPost(id: String): ResponseEntity<Relationship> { override suspend fun apiV1AccountsIdUnblockPost(id: String): ResponseEntity<Relationship> {
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<Relationship> { override suspend fun apiV1AccountsIdUnfollowPost(id: String): ResponseEntity<Relationship> {