diff --git a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/deletedActor/DeletedActor.kt b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/deletedActor/DeletedActor.kt new file mode 100644 index 00000000..ea5fb843 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/deletedActor/DeletedActor.kt @@ -0,0 +1,11 @@ +package dev.usbharu.hideout.core.domain.model.deletedActor + +import java.time.Instant + +data class DeletedActor( + val id: Long, + val name: String, + val domain: String, + val publicKey: String, + val deletedAt: Instant +) diff --git a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/deletedActor/DeletedActorRepository.kt b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/deletedActor/DeletedActorRepository.kt new file mode 100644 index 00000000..f2d18368 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/deletedActor/DeletedActorRepository.kt @@ -0,0 +1,7 @@ +package dev.usbharu.hideout.core.domain.model.deletedActor + +interface DeletedActorRepository { + suspend fun save(deletedActor: DeletedActor): DeletedActor + suspend fun delete(deletedActor: DeletedActor) + suspend fun findById(id: Long): DeletedActor +} diff --git a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/RelationshipRepository.kt b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/RelationshipRepository.kt index 75da4e35..6335a878 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/RelationshipRepository.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/RelationshipRepository.kt @@ -28,4 +28,6 @@ interface RelationshipRepository { * @return 取得された[Relationship] 存在しない場合nullが返ります */ suspend fun findByUserIdAndTargetUserId(actorId: Long, targetActorId: Long): Relationship? + + suspend fun deleteByActorIdOrTargetActorId(actorId: Long, targetActorId: Long) } diff --git a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/RelationshipRepositoryImpl.kt b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/RelationshipRepositoryImpl.kt index 09be85c8..844b886f 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/RelationshipRepositoryImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/RelationshipRepositoryImpl.kt @@ -50,6 +50,7 @@ class RelationshipRepositoryImpl : RelationshipRepository { } } + override suspend fun findByUserIdAndTargetUserId(actorId: Long, targetActorId: Long): Relationship? { return Relationships.select { (Relationships.actorId eq actorId) @@ -57,6 +58,12 @@ class RelationshipRepositoryImpl : RelationshipRepository { }.singleOrNull() ?.toRelationships() } + + override suspend fun deleteByActorIdOrTargetActorId(actorId: Long, targetActorId: Long) { + Relationships.deleteWhere { + Relationships.actorId.eq(actorId).or(Relationships.targetActorId.eq(targetActorId)) + } + } } fun ResultRow.toRelationships(): Relationship = Relationship( diff --git a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedquery/DeletedActorQueryServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedquery/DeletedActorQueryServiceImpl.kt new file mode 100644 index 00000000..f412b310 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedquery/DeletedActorQueryServiceImpl.kt @@ -0,0 +1,23 @@ +package dev.usbharu.hideout.core.infrastructure.exposedquery + +import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException +import dev.usbharu.hideout.core.domain.model.deletedActor.DeletedActor +import dev.usbharu.hideout.core.infrastructure.exposedrepository.DeletedActors +import dev.usbharu.hideout.core.infrastructure.exposedrepository.toDeletedActor +import dev.usbharu.hideout.core.query.DeletedActorQueryService +import dev.usbharu.hideout.util.singleOr +import org.jetbrains.exposed.sql.and +import org.jetbrains.exposed.sql.select +import org.springframework.stereotype.Repository + +@Repository +class DeletedActorQueryServiceImpl : DeletedActorQueryService { + override suspend fun findByNameAndDomain(name: String, domain: String): DeletedActor { + return DeletedActors + .select { DeletedActors.name eq name and (DeletedActors.domain eq domain) } + .singleOr { + FailedToGetResourcesException("name: $name domain: $domain was not exist or duplicate.", it) + } + .toDeletedActor() + } +} diff --git a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/DeletedActorRepositoryImpl.kt b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/DeletedActorRepositoryImpl.kt new file mode 100644 index 00000000..440c1a36 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/DeletedActorRepositoryImpl.kt @@ -0,0 +1,71 @@ +package dev.usbharu.hideout.core.infrastructure.exposedrepository + +import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException +import dev.usbharu.hideout.core.domain.model.deletedActor.DeletedActor +import dev.usbharu.hideout.core.domain.model.deletedActor.DeletedActorRepository +import dev.usbharu.hideout.util.singleOr +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 +class DeletedActorRepositoryImpl : DeletedActorRepository { + override suspend fun save(deletedActor: DeletedActor): DeletedActor { + val singleOrNull = DeletedActors.select { DeletedActors.id eq deletedActor.id }.singleOrNull() + + if (singleOrNull == null) { + DeletedActors.insert { + it[DeletedActors.id] = deletedActor.id + it[DeletedActors.name] = deletedActor.name + it[DeletedActors.domain] = deletedActor.domain + it[DeletedActors.publicKey] = deletedActor.publicKey + it[DeletedActors.deletedAt] = deletedActor.deletedAt + } + } else { + DeletedActors.update({ DeletedActors.id eq deletedActor.id }) { + it[DeletedActors.name] = deletedActor.name + it[DeletedActors.domain] = deletedActor.domain + it[DeletedActors.publicKey] = deletedActor.publicKey + it[DeletedActors.deletedAt] = deletedActor.deletedAt + } + } + return deletedActor + } + + override suspend fun delete(deletedActor: DeletedActor) { + DeletedActors.deleteWhere { DeletedActors.id eq deletedActor.id } + } + + override suspend fun findById(id: Long): DeletedActor { + val singleOr = DeletedActors.select { DeletedActors.id eq id } + .singleOr { FailedToGetResourcesException("id: $id was not exist or duplicate", it) } + + return deletedActor(singleOr) + } +} + +fun ResultRow.toDeletedActor(): DeletedActor = deletedActor(this) + +private fun deletedActor(singleOr: ResultRow): DeletedActor { + return DeletedActor( + singleOr[DeletedActors.id], + singleOr[DeletedActors.name], + singleOr[DeletedActors.domain], + singleOr[DeletedActors.publicKey], + singleOr[DeletedActors.deletedAt] + ) +} + +object DeletedActors : Table("deleted_actors") { + val id = long("id") + val name = varchar("name", 300) + val domain = varchar("domain", 255) + val publicKey = varchar("public_key", 10000).uniqueIndex() + val deletedAt = timestamp("deleted_at") + override val primaryKey: PrimaryKey = PrimaryKey(id) + + init { + uniqueIndex(name, domain) + } +} diff --git a/src/main/kotlin/dev/usbharu/hideout/core/query/DeletedActorQueryService.kt b/src/main/kotlin/dev/usbharu/hideout/core/query/DeletedActorQueryService.kt new file mode 100644 index 00000000..de7fcc53 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/core/query/DeletedActorQueryService.kt @@ -0,0 +1,7 @@ +package dev.usbharu.hideout.core.query + +import dev.usbharu.hideout.core.domain.model.deletedActor.DeletedActor + +interface DeletedActorQueryService { + suspend fun findByNameAndDomain(name: String, domain: String): DeletedActor +} diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/post/PostServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/post/PostServiceImpl.kt index e35e0c40..4a0a7cff 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/service/post/PostServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/post/PostServiceImpl.kt @@ -41,11 +41,17 @@ class PostServiceImpl( } override suspend fun deleteLocal(post: Post) { + if (post.delted) { + return + } reactionRepository.deleteByPostId(post.id) postRepository.save(post.delete()) } override suspend fun deleteRemote(post: Post) { + if (post.delted) { + return + } reactionRepository.deleteByPostId(post.id) postRepository.save(post.delete()) } diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/user/UserService.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/user/UserService.kt index 06715df5..29bb738d 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/service/user/UserService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/user/UserService.kt @@ -13,4 +13,8 @@ interface UserService { suspend fun createRemoteUser(user: RemoteUserCreateDto): Actor suspend fun updateUser(userId: Long, updateUserDto: UpdateUserDto) + + suspend fun deleteRemoteActor(actorId: Long) + + suspend fun deleteLocalUser(userId: Long) } diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/user/UserServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/user/UserServiceImpl.kt index 442b676b..988cac62 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/service/user/UserServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/user/UserServiceImpl.kt @@ -1,11 +1,17 @@ package dev.usbharu.hideout.core.service.user 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.deletedActor.DeletedActor +import dev.usbharu.hideout.core.domain.model.deletedActor.DeletedActorRepository +import dev.usbharu.hideout.core.domain.model.reaction.ReactionRepository +import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository import dev.usbharu.hideout.core.domain.model.userdetails.UserDetail import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailRepository import dev.usbharu.hideout.core.query.ActorQueryService +import dev.usbharu.hideout.core.query.DeletedActorQueryService import dev.usbharu.hideout.core.service.instance.InstanceService import org.jetbrains.exposed.exceptions.ExposedSQLException import org.slf4j.LoggerFactory @@ -21,7 +27,12 @@ class UserServiceImpl( private val actorBuilder: Actor.UserBuilder, private val applicationConfig: ApplicationConfig, private val instanceService: InstanceService, - private val userDetailRepository: UserDetailRepository + private val userDetailRepository: UserDetailRepository, + private val deletedActorRepository: DeletedActorRepository, + private val deletedActorQueryService: DeletedActorQueryService, + private val reactionRepository: ReactionRepository, + private val relationshipRepository: RelationshipRepository + ) : UserService { @@ -60,6 +71,14 @@ class UserServiceImpl( @Transactional override suspend fun createRemoteUser(user: RemoteUserCreateDto): Actor { logger.info("START Create New remote user. name: {} url: {}", user.name, user.url) + + try { + deletedActorQueryService.findByNameAndDomain(user.name, user.domain) + logger.warn("FAILED Deleted actor. user: ${user.name} domain: ${user.domain}") + throw IllegalStateException("Cannot create Deleted actor.") + } catch (_: FailedToGetResourcesException) { + } + @Suppress("TooGenericExceptionCaught") val instance = try { instanceService.fetchInstance(user.url, user.sharedInbox) @@ -117,6 +136,26 @@ class UserServiceImpl( ) } + override suspend fun deleteRemoteActor(actorId: Long) { + val actor = actorQueryService.findById(actorId) + val deletedActor = DeletedActor( + actor.id, + actor.name, + actor.domain, + actor.publicKey, + Instant.now() + ) + relationshipRepository.deleteByActorIdOrTargetActorId(actorId, actorId) + + reactionRepository.deleteByActorId(actorId) + + deletedActorRepository.save(deletedActor) + } + + override suspend fun deleteLocalUser(userId: Long) { + TODO("Not yet implemented") + } + companion object { private val logger = LoggerFactory.getLogger(UserServiceImpl::class.java) } diff --git a/src/main/resources/db/migration/V1__Init_DB.sql b/src/main/resources/db/migration/V1__Init_DB.sql index f680516b..741844d4 100644 --- a/src/main/resources/db/migration/V1__Init_DB.sql +++ b/src/main/resources/db/migration/V1__Init_DB.sql @@ -202,3 +202,13 @@ create table if not exists relationships insert into actors (id, name, domain, screen_name, description, inbox, outbox, url, public_key, private_key, created_at, key_id, following, followers, instance, locked) values (0, 'ghost', '', '', '', '', '', '', '', null, 0, '', '', '', null, true); + +create table if not exists deleted_actors +( + id bigint primary key, + "name" varchar(300) not null, + domain varchar(255) not null, + public_key varchar(10000) not null, + deleted_at timestamp not null, + unique ("name", domain) +)