Merge pull request #215 from usbharu/feature/delete

Feature/delete
This commit is contained in:
usbharu 2023-12-13 17:48:26 +09:00 committed by GitHub
commit e0368ab7c5
28 changed files with 523 additions and 31 deletions

View File

@ -6,9 +6,9 @@ open class Person
@Suppress("LongParameterList")
constructor(
type: List<String> = emptyList(),
override val name: String,
val name: String?,
override val id: String,
var preferredUsername: String?,
var preferredUsername: String,
var summary: String?,
var inbox: String,
var outbox: String,
@ -19,7 +19,7 @@ constructor(
var followers: String?,
var following: String?,
val manuallyApprovesFollowers: Boolean? = false
) : Object(add(type, "Person")), HasId, HasName {
) : Object(add(type, "Person")), HasId {
@Suppress("CyclomaticComplexMethod", "CognitiveComplexMethod")
override fun equals(other: Any?): Boolean {

View File

@ -3,37 +3,50 @@ package dev.usbharu.hideout.activitypub.service.activity.delete
import dev.usbharu.hideout.activitypub.domain.exception.IllegalActivityPubObjectException
import dev.usbharu.hideout.activitypub.domain.model.Delete
import dev.usbharu.hideout.activitypub.domain.model.HasId
import dev.usbharu.hideout.activitypub.domain.model.objects.ObjectValue
import dev.usbharu.hideout.activitypub.service.common.AbstractActivityPubProcessor
import dev.usbharu.hideout.activitypub.service.common.ActivityPubProcessContext
import dev.usbharu.hideout.activitypub.service.common.ActivityType
import dev.usbharu.hideout.application.external.Transaction
import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException
import dev.usbharu.hideout.core.domain.model.post.PostRepository
import dev.usbharu.hideout.core.query.ActorQueryService
import dev.usbharu.hideout.core.query.PostQueryService
import dev.usbharu.hideout.core.service.post.PostService
import dev.usbharu.hideout.core.service.user.UserService
import org.springframework.stereotype.Service
@Service
class APDeleteProcessor(
transaction: Transaction,
private val postQueryService: PostQueryService,
private val postRepository: PostRepository
private val actorQueryService: ActorQueryService,
private val userService: UserService,
private val postService: PostService
) :
AbstractActivityPubProcessor<Delete>(transaction) {
override suspend fun internalProcess(activity: ActivityPubProcessContext<Delete>) {
val value = activity.activity.apObject
if (value !is HasId) {
throw IllegalActivityPubObjectException("object hasn't id")
val deleteId = if (value is HasId) {
value.id
} else if (value is ObjectValue) {
value.`object`
} else {
throw IllegalActivityPubObjectException("object hasn't id or object")
}
val deleteId = value.id
val post = try {
postQueryService.findByApId(deleteId)
try {
val actor = actorQueryService.findByUrl(deleteId)
userService.deleteRemoteActor(actor.id)
} catch (e: Exception) {
logger.warn("FAILED delete id: {} is not found.", deleteId, e)
}
try {
val post = postQueryService.findByApId(deleteId)
postService.deleteRemote(post)
} catch (e: FailedToGetResourcesException) {
logger.warn("FAILED delete id: {} is not found.", deleteId, e)
return
}
postRepository.delete(post.id)
}
override fun isSupported(activityType: ActivityType): Boolean = activityType == ActivityType.Delete

View File

@ -0,0 +1,23 @@
package dev.usbharu.hideout.activitypub.service.activity.delete
import dev.usbharu.hideout.activitypub.service.common.APRequestService
import dev.usbharu.hideout.application.external.Transaction
import dev.usbharu.hideout.core.external.job.DeliverDeleteJob
import dev.usbharu.hideout.core.external.job.DeliverDeleteJobParam
import dev.usbharu.hideout.core.query.ActorQueryService
import dev.usbharu.hideout.core.service.job.JobProcessor
import org.springframework.stereotype.Service
@Service
class APDeliverDeleteJobProcessor(
private val apRequestService: APRequestService,
private val actorQueryService: ActorQueryService,
private val transaction: Transaction,
private val deliverDeleteJob: DeliverDeleteJob
) : JobProcessor<DeliverDeleteJobParam, DeliverDeleteJob> {
override suspend fun process(param: DeliverDeleteJobParam): Unit = transaction.transaction {
apRequestService.apPost(param.inbox, param.delete, actorQueryService.findById(param.signer))
}
override fun job(): DeliverDeleteJob = deliverDeleteJob
}

View File

@ -0,0 +1,69 @@
package dev.usbharu.hideout.activitypub.service.activity.delete
import dev.usbharu.hideout.activitypub.domain.model.Delete
import dev.usbharu.hideout.activitypub.domain.model.Tombstone
import dev.usbharu.hideout.activitypub.domain.model.objects.ObjectValue
import dev.usbharu.hideout.application.config.ApplicationConfig
import dev.usbharu.hideout.core.domain.model.actor.Actor
import dev.usbharu.hideout.core.domain.model.post.Post
import dev.usbharu.hideout.core.external.job.DeliverDeleteJob
import dev.usbharu.hideout.core.external.job.DeliverDeleteJobParam
import dev.usbharu.hideout.core.query.ActorQueryService
import dev.usbharu.hideout.core.query.FollowerQueryService
import dev.usbharu.hideout.core.service.job.JobQueueParentService
import org.springframework.stereotype.Service
import java.time.Instant
interface APSendDeleteService {
suspend fun sendDeleteNote(deletedPost: Post)
suspend fun sendDeleteActor(deletedActor: Actor)
}
@Service
class APSendDeleteServiceImpl(
private val jobQueueParentService: JobQueueParentService,
private val delverDeleteJob: DeliverDeleteJob,
private val followerQueryService: FollowerQueryService,
private val actorQueryService: ActorQueryService,
private val applicationConfig: ApplicationConfig
) : APSendDeleteService {
override suspend fun sendDeleteNote(deletedPost: Post) {
val actor = actorQueryService.findById(deletedPost.actorId)
val followersById = followerQueryService.findFollowersById(deletedPost.actorId)
val delete = Delete(
actor = actor.url,
id = "${applicationConfig.url}/delete/note/${deletedPost.id}",
published = Instant.now().toString(),
`object` = Tombstone(id = deletedPost.apId)
)
followersById.forEach {
val jobProps = DeliverDeleteJobParam(
delete,
it.inbox,
actor.id
)
jobQueueParentService.scheduleTypeSafe(delverDeleteJob, jobProps)
}
}
override suspend fun sendDeleteActor(deletedActor: Actor) {
val followers = followerQueryService.findFollowersById(deletedActor.id)
val delete = Delete(
actor = deletedActor.url,
`object` = ObjectValue(emptyList(), `object` = deletedActor.url),
id = "${applicationConfig.url}/delete/actor/${deletedActor.id}",
published = Instant.now().toString()
)
followers.forEach {
DeliverDeleteJobParam(
delete = delete,
it.inbox,
deletedActor.id
)
}
}
}

View File

@ -96,7 +96,7 @@ class APUserServiceImpl(
name = person.preferredUsername
?: throw IllegalActivityPubObjectException("preferredUsername is null"),
domain = id.substringAfter("://").substringBefore("/"),
screenName = person.name,
screenName = person.name ?: person.preferredUsername,
description = person.summary.orEmpty(),
inbox = person.inbox,
outbox = person.outbox,

View File

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

View File

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

View File

@ -2,6 +2,7 @@ package dev.usbharu.hideout.core.domain.model.post
import dev.usbharu.hideout.application.config.CharacterLimit
import org.springframework.stereotype.Component
import java.time.Instant
data class Post private constructor(
val id: Long,
@ -15,7 +16,8 @@ data class Post private constructor(
val replyId: Long? = null,
val sensitive: Boolean = false,
val apId: String = url,
val mediaIds: List<Long> = emptyList()
val mediaIds: List<Long> = emptyList(),
val delted: Boolean = false
) {
@Component
@ -71,8 +73,53 @@ data class Post private constructor(
replyId = replyId,
sensitive = sensitive,
apId = apId,
mediaIds = mediaIds
mediaIds = mediaIds,
delted = false
)
}
@Suppress("LongParameterList")
fun deleteOf(
id: Long,
visibility: Visibility,
url: String,
repostId: Long?,
replyId: Long?,
apId: String
): Post {
return Post(
id = id,
actorId = 0,
overview = null,
text = "",
createdAt = Instant.EPOCH.toEpochMilli(),
visibility = visibility,
url = url,
repostId = repostId,
replyId = replyId,
sensitive = false,
apId = apId,
mediaIds = emptyList(),
delted = true
)
}
}
fun delete(): Post {
return Post(
id = this.id,
actorId = 0,
overview = null,
text = "",
createdAt = Instant.EPOCH.toEpochMilli(),
visibility = visibility,
url = url,
repostId = repostId,
replyId = replyId,
sensitive = false,
apId = apId,
mediaIds = emptyList(),
delted = true
)
}
}

View File

@ -7,4 +7,6 @@ interface ReactionRepository {
suspend fun generateId(): Long
suspend fun save(reaction: Reaction): Reaction
suspend fun delete(reaction: Reaction): Reaction
suspend fun deleteByPostId(postId: Long): Int
suspend fun deleteByActorId(actorId: Long): Int
}

View File

@ -28,4 +28,6 @@ interface RelationshipRepository {
* @return 取得された[Relationship] 存在しない場合nullが返ります
*/
suspend fun findByUserIdAndTargetUserId(actorId: Long, targetActorId: Long): Relationship?
suspend fun deleteByActorIdOrTargetActorId(actorId: Long, targetActorId: Long)
}

View File

@ -57,6 +57,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(

View File

@ -0,0 +1,36 @@
package dev.usbharu.hideout.core.external.job
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import dev.usbharu.hideout.activitypub.domain.model.Delete
import kjob.core.dsl.ScheduleContext
import kjob.core.job.JobProps
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.stereotype.Component
data class DeliverDeleteJobParam(
val delete: Delete,
val inbox: String,
val signer: Long
)
@Component
class DeliverDeleteJob(@Qualifier("activitypub") private val objectMapper: ObjectMapper) :
HideoutJob<DeliverDeleteJobParam, DeliverDeleteJob>("DeliverDeleteJob") {
val delete = string("delete")
val inbox = string("inbox")
val signer = long("signer")
override fun convert(value: DeliverDeleteJobParam): ScheduleContext<DeliverDeleteJob>.(DeliverDeleteJob) -> Unit = {
props[delete] = objectMapper.writeValueAsString(value.delete)
props[inbox] = value.inbox
props[signer] = value.signer
}
override fun convert(props: JobProps<DeliverDeleteJob>): DeliverDeleteJobParam = DeliverDeleteJobParam(
objectMapper.readValue(props[delete]),
props[inbox],
props[signer]
)
}

View File

@ -10,6 +10,17 @@ import org.springframework.stereotype.Component
@Component
class PostResultRowMapper(private val postBuilder: Post.PostBuilder) : ResultRowMapper<Post> {
override fun map(resultRow: ResultRow): Post {
if (resultRow[Posts.deleted]) {
return postBuilder.deleteOf(
id = resultRow[Posts.id],
visibility = Visibility.values().first { it.ordinal == resultRow[Posts.visibility] },
url = resultRow[Posts.url],
repostId = resultRow[Posts.repostId],
replyId = resultRow[Posts.replyId],
apId = resultRow[Posts.apId]
)
}
return postBuilder.of(
id = resultRow[Posts.id],
actorId = resultRow[Posts.actorId],

View File

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

View File

@ -33,4 +33,7 @@ class PostQueryServiceImpl(
.select { Posts.apId eq string }
.let(postQueryMapper::map)
.singleOr { FailedToGetResourcesException("apId: $string is duplicate or does not exist.", it) }
override suspend fun findByActorId(actorId: Long): List<Post> =
Posts.leftJoin(PostsMedia).select { Posts.actorId eq actorId }.let(postQueryMapper::map)
}

View File

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

View File

@ -33,6 +33,7 @@ class PostRepositoryImpl(
it[replyId] = post.replyId
it[sensitive] = post.sensitive
it[apId] = post.apId
it[deleted] = post.delted
}
PostsMedia.batchInsert(post.mediaIds) {
this[PostsMedia.postId] = post.id
@ -57,6 +58,7 @@ class PostRepositoryImpl(
it[replyId] = post.replyId
it[sensitive] = post.sensitive
it[apId] = post.apId
it[deleted] = post.delted
}
}
return singleOrNull == null
@ -85,6 +87,7 @@ object Posts : Table() {
val replyId: Column<Long?> = long("reply_id").references(id).nullable()
val sensitive: Column<Boolean> = bool("sensitive").default(false)
val apId: Column<String> = varchar("ap_id", 100).uniqueIndex()
val deleted = bool("deleted").default(false)
override val primaryKey: PrimaryKey = PrimaryKey(id)
}

View File

@ -42,6 +42,18 @@ class ReactionRepositoryImpl(
}
return reaction
}
override suspend fun deleteByPostId(postId: Long): Int {
return Reactions.deleteWhere {
Reactions.postId eq postId
}
}
override suspend fun deleteByActorId(actorId: Long): Int {
return Reactions.deleteWhere {
Reactions.actorId eq actorId
}
}
}
fun ResultRow.toReaction(): Reaction {
@ -55,8 +67,10 @@ fun ResultRow.toReaction(): Reaction {
object Reactions : LongIdTable("reactions") {
val emojiId: Column<Long> = long("emoji_id")
val postId: Column<Long> = long("post_id").references(Posts.id)
val actorId: Column<Long> = long("actor_id").references(Actors.id)
val postId: Column<Long> = long("post_id")
.references(Posts.id, onDelete = ReferenceOption.CASCADE, onUpdate = ReferenceOption.CASCADE)
val actorId: Column<Long> = long("actor_id")
.references(Actors.id, onDelete = ReferenceOption.CASCADE, onUpdate = ReferenceOption.CASCADE)
init {
uniqueIndex(emojiId, postId, actorId)

View File

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

View File

@ -8,4 +8,5 @@ interface PostQueryService {
suspend fun findById(id: Long): Post
suspend fun findByUrl(url: String): Post
suspend fun findByApId(string: String): Post
suspend fun findByActorId(actorId: Long): List<Post>
}

View File

@ -6,7 +6,7 @@ interface RelationshipQueryService {
suspend fun findByTargetIdAndFollowing(targetId: Long, following: Boolean): List<Relationship>
@Suppress("LongParameterList")
@Suppress("LongParameterList", "FunctionMaxLength")
suspend fun findByTargetIdAndFollowRequestAndIgnoreFollowRequest(
maxId: Long?,
sinceId: Long?,

View File

@ -7,4 +7,7 @@ import org.springframework.stereotype.Service
interface PostService {
suspend fun createLocal(post: PostCreateDto): Post
suspend fun createRemote(post: Post): Post
suspend fun deleteLocal(post: Post)
suspend fun deleteRemote(post: Post)
suspend fun deleteByActor(actorId: Long)
}

View File

@ -5,6 +5,7 @@ import dev.usbharu.hideout.core.domain.exception.UserNotFoundException
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.domain.model.post.Post
import dev.usbharu.hideout.core.domain.model.post.PostRepository
import dev.usbharu.hideout.core.domain.model.reaction.ReactionRepository
import dev.usbharu.hideout.core.query.PostQueryService
import dev.usbharu.hideout.core.service.timeline.TimelineService
import org.jetbrains.exposed.exceptions.ExposedSQLException
@ -20,7 +21,8 @@ class PostServiceImpl(
private val timelineService: TimelineService,
private val postQueryService: PostQueryService,
private val postBuilder: Post.PostBuilder,
private val apSendCreateService: ApSendCreateService
private val apSendCreateService: ApSendCreateService,
private val reactionRepository: ReactionRepository
) : PostService {
override suspend fun createLocal(post: PostCreateDto): Post {
@ -38,6 +40,26 @@ class PostServiceImpl(
return createdPost
}
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())
}
override suspend fun deleteByActor(actorId: Long) {
postQueryService.findByActorId(actorId).filterNot { it.delted }.forEach { postRepository.save(it.delete()) }
}
private suspend fun internalCreate(post: Post, isLocal: Boolean): Post {
return try {
if (postRepository.save(post)) {

View File

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

View File

@ -1,12 +1,20 @@
package dev.usbharu.hideout.core.service.user
import dev.usbharu.hideout.activitypub.service.activity.delete.APSendDeleteService
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 dev.usbharu.hideout.core.service.post.PostService
import org.jetbrains.exposed.exceptions.ExposedSQLException
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
@ -14,6 +22,7 @@ import org.springframework.transaction.annotation.Transactional
import java.time.Instant
@Service
@Suppress("LongParameterList")
class UserServiceImpl(
private val actorRepository: ActorRepository,
private val userAuthService: UserAuthService,
@ -21,7 +30,14 @@ 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,
private val postService: PostService,
private val apSendDeleteService: APSendDeleteService
) :
UserService {
@ -60,6 +76,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 +141,47 @@ 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)
postService.deleteByActor(actorId)
actorRepository.delete(actor.id)
deletedActorRepository.save(deletedActor)
}
override suspend fun deleteLocalUser(userId: Long) {
val actor = actorQueryService.findById(userId)
apSendDeleteService.sendDeleteActor(actor)
val deletedActor = DeletedActor(
actor.id,
actor.name,
actor.domain,
actor.publicKey,
Instant.now()
)
relationshipRepository.deleteByActorIdOrTargetActorId(userId, userId)
reactionRepository.deleteByActorId(actor.id)
postService.deleteByActor(actor.id)
actorRepository.delete(actor.id)
val userDetail =
userDetailRepository.findByActorId(actor.id) ?: throw IllegalStateException("user detail not found.")
userDetailRepository.delete(userDetail)
deletedActorRepository.save(deletedActor)
}
companion object {
private val logger = LoggerFactory.getLogger(UserServiceImpl::class.java)
}

View File

@ -67,7 +67,7 @@ create table if not exists meta_info
create table if not exists posts
(
id bigint primary key,
actor_id bigint not null,
actor_id bigint not null,
overview varchar(100) null,
text varchar(3000) not null,
created_at bigint not null,
@ -76,7 +76,8 @@ create table if not exists posts
repost_id bigint null,
reply_id bigint null,
"sensitive" boolean default false not null,
ap_id varchar(100) not null unique
ap_id varchar(100) not null unique,
deleted boolean default false not null
);
alter table posts
add constraint fk_posts_actor_id__id foreign key (actor_id) references actors (id) on delete restrict on update restrict;
@ -196,4 +197,18 @@ create table if not exists relationships
constraint fk_relationships_actor_id__id foreign key (actor_id) references actors (id) on delete restrict on update restrict,
constraint fk_relationships_target_actor_id__id foreign key (target_actor_id) references actors (id) on delete restrict on update restrict,
unique (actor_id, target_actor_id)
);
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)
)

View File

@ -5,6 +5,7 @@ import dev.usbharu.hideout.application.config.CharacterLimit
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.domain.model.post.Post
import dev.usbharu.hideout.core.domain.model.post.PostRepository
import dev.usbharu.hideout.core.domain.model.reaction.ReactionRepository
import dev.usbharu.hideout.core.query.PostQueryService
import dev.usbharu.hideout.core.service.timeline.TimelineService
import kotlinx.coroutines.test.runTest
@ -45,6 +46,9 @@ class PostServiceImplTest {
@Mock
private lateinit var apSendCreateService: ApSendCreateService
@Mock
private lateinit var reactionRepository: ReactionRepository
@InjectMocks
private lateinit var postServiceImpl: PostServiceImpl

View File

@ -4,9 +4,11 @@ package dev.usbharu.hideout.core.service.user
import dev.usbharu.hideout.application.config.ApplicationConfig
import dev.usbharu.hideout.application.config.CharacterLimit
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.post.Post
import dev.usbharu.hideout.core.query.DeletedActorQueryService
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
@ -34,13 +36,19 @@ class ActorServiceTest {
}
val userService =
UserServiceImpl(
actorRepository,
userAuthService,
mock(),
actorBuilder,
testApplicationConfig,
mock(),
mock()
actorRepository = actorRepository,
userAuthService = userAuthService,
actorQueryService = mock(),
actorBuilder = actorBuilder,
applicationConfig = testApplicationConfig,
instanceService = mock(),
userDetailRepository = mock(),
deletedActorRepository = mock(),
deletedActorQueryService = mock(),
reactionRepository = mock(),
relationshipRepository = mock(),
postService = mock(),
apSendDeleteService = mock()
)
userService.createLocalUser(UserCreateDto("test", "testUser", "XXXXXXXXXXXXX", "test"))
verify(actorRepository, times(1)).save(any())
@ -65,8 +73,30 @@ class ActorServiceTest {
val actorRepository = mock<ActorRepository> {
onBlocking { nextId() } doReturn 113345L
}
val deletedActorQueryService = mock<DeletedActorQueryService> {
onBlocking {
findByNameAndDomain(
eq("test"),
eq("remote.example.com")
)
} doAnswer { throw FailedToGetResourcesException() }
}
val userService =
UserServiceImpl(actorRepository, mock(), mock(), actorBuilder, testApplicationConfig, mock(), mock())
UserServiceImpl(
actorRepository = actorRepository,
userAuthService = mock(),
actorQueryService = mock(),
actorBuilder = actorBuilder,
applicationConfig = testApplicationConfig,
instanceService = mock(),
userDetailRepository = mock(),
deletedActorRepository = mock(),
deletedActorQueryService = deletedActorQueryService,
reactionRepository = mock(),
relationshipRepository = mock(),
postService = mock(),
apSendDeleteService = mock()
)
val user = RemoteUserCreateDto(
name = "test",
domain = "remote.example.com",