From 6f579486da7c2c8ea73847dc84bc38fa8252a4ec Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Wed, 13 Dec 2023 15:02:17 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=89=8A=E9=99=A4=E3=81=95=E3=82=8C?= =?UTF-8?q?=E3=81=9F=E6=8A=95=E7=A8=BF=E3=81=AF=E3=83=AC=E3=82=B3=E3=83=BC?= =?UTF-8?q?=E3=83=89=E3=82=92=E5=AE=8C=E5=85=A8=E3=81=AB=E5=89=8A=E9=99=A4?= =?UTF-8?q?=E3=81=9B=E3=81=9A=E3=80=81=E8=BF=94=E4=BF=A1=E3=81=AA=E3=81=A9?= =?UTF-8?q?=E3=81=AE=E6=83=85=E5=A0=B1=E3=82=92=E7=B6=AD=E6=8C=81=E3=81=97?= =?UTF-8?q?=E3=81=9F=E3=81=BE=E3=81=BE=E5=89=8A=E9=99=A4=E3=81=95=E3=82=8C?= =?UTF-8?q?=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../activity/delete/APDeleteProcessor.kt | 6 +-- .../delete/APDeliverDeleteJobProcessor.kt | 23 +++++++++ .../activity/delete/APSendDeleteService.kt | 50 +++++++++++++++++++ .../hideout/core/domain/model/post/Post.kt | 50 ++++++++++++++++++- .../model/reaction/ReactionRepository.kt | 2 + .../core/external/job/DeliverDeleteJob.kt | 36 +++++++++++++ .../exposed/PostResultRowMapper.kt | 11 ++++ .../exposedrepository/PostRepositoryImpl.kt | 1 + .../ReactionRepositoryImpl.kt | 18 ++++++- .../hideout/core/service/post/PostService.kt | 2 + .../core/service/post/PostServiceImpl.kt | 14 +++++- .../resources/db/migration/V1__Init_DB.sql | 11 ++-- 12 files changed, 213 insertions(+), 11 deletions(-) create mode 100644 src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/delete/APDeliverDeleteJobProcessor.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/delete/APSendDeleteService.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/core/external/job/DeliverDeleteJob.kt diff --git a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/delete/APDeleteProcessor.kt b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/delete/APDeleteProcessor.kt index efb37938..c628e491 100644 --- a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/delete/APDeleteProcessor.kt +++ b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/delete/APDeleteProcessor.kt @@ -8,15 +8,15 @@ 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.PostQueryService +import dev.usbharu.hideout.core.service.post.PostService import org.springframework.stereotype.Service @Service class APDeleteProcessor( transaction: Transaction, private val postQueryService: PostQueryService, - private val postRepository: PostRepository + private val postService: PostService ) : AbstractActivityPubProcessor(transaction) { override suspend fun internalProcess(activity: ActivityPubProcessContext) { @@ -33,7 +33,7 @@ class APDeleteProcessor( return } - postRepository.delete(post.id) + postService.deleteRemote(post) } override fun isSupported(activityType: ActivityType): Boolean = activityType == ActivityType.Delete diff --git a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/delete/APDeliverDeleteJobProcessor.kt b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/delete/APDeliverDeleteJobProcessor.kt new file mode 100644 index 00000000..5c72c304 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/delete/APDeliverDeleteJobProcessor.kt @@ -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 { + override suspend fun process(param: DeliverDeleteJobParam): Unit = transaction.transaction { + apRequestService.apPost(param.inbox, param.delete, actorQueryService.findById(param.signer)) + } + + override fun job(): DeliverDeleteJob = deliverDeleteJob +} diff --git a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/delete/APSendDeleteService.kt b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/delete/APSendDeleteService.kt new file mode 100644 index 00000000..2cb0f86c --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/delete/APSendDeleteService.kt @@ -0,0 +1,50 @@ +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.application.config.ApplicationConfig +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) +} + +@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) + } + } + +} diff --git a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/Post.kt b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/Post.kt index 3a5989ab..6cbc765e 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/Post.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/Post.kt @@ -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 = emptyList() + val mediaIds: List = emptyList(), + val delted: Boolean = false ) { @Component @@ -71,8 +73,52 @@ data class Post private constructor( replyId = replyId, sensitive = sensitive, apId = apId, - mediaIds = mediaIds + mediaIds = mediaIds, + delted = false + ) + } + + 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 + ) + } } diff --git a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/reaction/ReactionRepository.kt b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/reaction/ReactionRepository.kt index 5bfae20e..64b324ff 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/reaction/ReactionRepository.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/reaction/ReactionRepository.kt @@ -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 } diff --git a/src/main/kotlin/dev/usbharu/hideout/core/external/job/DeliverDeleteJob.kt b/src/main/kotlin/dev/usbharu/hideout/core/external/job/DeliverDeleteJob.kt new file mode 100644 index 00000000..9029f6b4 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/core/external/job/DeliverDeleteJob.kt @@ -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("DeliverDeleteJob") { + + val delete = string("delete") + val inbox = string("inbox") + val signer = long("signer") + + override fun convert(value: DeliverDeleteJobParam): ScheduleContext.(DeliverDeleteJob) -> Unit = { + props[delete] = objectMapper.writeValueAsString(value.delete) + props[inbox] = value.inbox + props[signer] = value.signer + } + + override fun convert(props: JobProps): DeliverDeleteJobParam = DeliverDeleteJobParam( + objectMapper.readValue(props[delete]), + props[inbox], + props[signer] + ) +} diff --git a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/PostResultRowMapper.kt b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/PostResultRowMapper.kt index 88b4e50c..22faf1b2 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/PostResultRowMapper.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/PostResultRowMapper.kt @@ -10,6 +10,17 @@ import org.springframework.stereotype.Component @Component class PostResultRowMapper(private val postBuilder: Post.PostBuilder) : ResultRowMapper { override fun map(resultRow: ResultRow): Post { + if (resultRow[Posts.deleted]) { + return postBuilder.deleteOf( + resultRow[Posts.id], + 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], diff --git a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/PostRepositoryImpl.kt b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/PostRepositoryImpl.kt index d71f0cab..f64e02d7 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/PostRepositoryImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/PostRepositoryImpl.kt @@ -85,6 +85,7 @@ object Posts : Table() { val replyId: Column = long("reply_id").references(id).nullable() val sensitive: Column = bool("sensitive").default(false) val apId: Column = varchar("ap_id", 100).uniqueIndex() + val deleted = bool("deleted").default(false) override val primaryKey: PrimaryKey = PrimaryKey(id) } diff --git a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ReactionRepositoryImpl.kt b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ReactionRepositoryImpl.kt index e2b8cef7..0b14d787 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ReactionRepositoryImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ReactionRepositoryImpl.kt @@ -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("emoji_id") - val postId: Column = long("post_id").references(Posts.id) - val actorId: Column = long("actor_id").references(Actors.id) + val postId: Column = long("post_id") + .references(Posts.id, onDelete = ReferenceOption.CASCADE, onUpdate = ReferenceOption.CASCADE) + val actorId: Column = long("actor_id") + .references(Actors.id, onDelete = ReferenceOption.CASCADE, onUpdate = ReferenceOption.CASCADE) init { uniqueIndex(emojiId, postId, actorId) diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/post/PostService.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/post/PostService.kt index 47c4974d..a20f0a66 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/service/post/PostService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/post/PostService.kt @@ -7,4 +7,6 @@ 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) } 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 a5bb1d47..e35e0c40 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 @@ -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,16 @@ class PostServiceImpl( return createdPost } + override suspend fun deleteLocal(post: Post) { + reactionRepository.deleteByPostId(post.id) + postRepository.save(post.delete()) + } + + override suspend fun deleteRemote(post: Post) { + reactionRepository.deleteByPostId(post.id) + postRepository.save(post.delete()) + } + private suspend fun internalCreate(post: Post, isLocal: Boolean): Post { return try { if (postRepository.save(post)) { diff --git a/src/main/resources/db/migration/V1__Init_DB.sql b/src/main/resources/db/migration/V1__Init_DB.sql index 48223f60..f680516b 100644 --- a/src/main/resources/db/migration/V1__Init_DB.sql +++ b/src/main/resources/db/migration/V1__Init_DB.sql @@ -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,8 @@ 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);