From 2e7efd5d6d7677c7cb4c9594ab16be753cc0b189 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Fri, 17 May 2024 23:12:58 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20Repository=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/activity/undo/APUndoProcessor.kt | 6 +- .../hideout/core/domain/model/post/Post.kt | 21 ++----- .../core/domain/model/post/PostRepository.kt | 2 + .../DeletedActorRepositoryImpl.kt | 4 ++ .../exposedrepository/PostRepositoryImpl.kt | 60 +++++++++++++++++-- .../hideout/core/service/post/PostService.kt | 1 + .../core/service/post/PostServiceImpl.kt | 21 +++++-- .../objects/note/APNoteServiceImplTest.kt | 4 +- .../core/service/user/ActorServiceTest.kt | 9 ++- .../hideout/worker/UpdateActorWorker.kt | 4 ++ 10 files changed, 100 insertions(+), 32 deletions(-) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/undo/APUndoProcessor.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/undo/APUndoProcessor.kt index 46c35a24..e788929e 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/undo/APUndoProcessor.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/undo/APUndoProcessor.kt @@ -31,6 +31,7 @@ import dev.usbharu.hideout.core.domain.model.post.PostRepository import dev.usbharu.hideout.core.service.post.PostService import dev.usbharu.hideout.core.service.reaction.ReactionService import dev.usbharu.hideout.core.service.relationship.RelationshipService +import dev.usbharu.hideout.core.service.user.UserService import org.springframework.stereotype.Service @Service @@ -41,7 +42,8 @@ class APUndoProcessor( private val reactionService: ReactionService, private val actorRepository: ActorRepository, private val postRepository: PostRepository, - private val postService: PostService + private val postService: PostService, + private val userService: UserService, ) : AbstractActivityPubProcessor(transaction) { override suspend fun internalProcess(activity: ActivityPubProcessContext) { val undo = activity.activity @@ -132,7 +134,9 @@ class APUndoProcessor( private suspend fun delete(undo: Undo) { val announce = undo.apObject as Delete + val actor = actorRepository.findByUrl(announce.actor) ?: throw UserNotFoundException.withUrl(announce.actor) + userService.restorationRemoteActor(actor.id) } override fun isSupported(activityType: ActivityType): Boolean = activityType == ActivityType.Undo diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/Post.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/Post.kt index 91d19e05..6e11792e 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/Post.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/Post.kt @@ -215,21 +215,10 @@ data class Post private constructor( this.repostId != null fun delete(): Post { - return Post( - id = this.id, - actorId = actorId, - overview = overview, - content = content, - text = text, - createdAt = createdAt, - visibility = visibility, - url = url, - repostId = repostId, - replyId = replyId, - sensitive = sensitive, - apId = apId, - mediaIds = mediaIds, - deleted = true - ) + return copy(deleted = true) + } + + fun restore(): Post { + return copy(deleted = false) } } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/PostRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/PostRepository.kt index 390b8d0b..b6ced0ac 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/PostRepository.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/PostRepository.kt @@ -23,6 +23,7 @@ import org.springframework.stereotype.Repository interface PostRepository { suspend fun generateId(): Long suspend fun save(post: Post): Post + suspend fun saveAll(posts: List) suspend fun delete(id: Long) suspend fun findById(id: Long): Post? suspend fun findByUrl(url: String): Post? @@ -30,6 +31,7 @@ interface PostRepository { suspend fun findByApId(apId: String): Post? suspend fun existByApIdWithLock(apId: String): Boolean suspend fun findByActorId(actorId: Long): List + suspend fun findByActorIdAndDeleted(actorId: Long, deleted: Boolean): List suspend fun countByActorId(actorId: Long): Int } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/DeletedActorRepositoryImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/DeletedActorRepositoryImpl.kt index 6fc8f792..52398cf4 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/DeletedActorRepositoryImpl.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/DeletedActorRepositoryImpl.kt @@ -39,6 +39,7 @@ class DeletedActorRepositoryImpl : DeletedActorRepository, AbstractRepository() it[id] = deletedActor.id it[name] = deletedActor.name it[domain] = deletedActor.domain + it[apId] = deletedActor.apiId it[publicKey] = deletedActor.publicKey it[deletedAt] = deletedActor.deletedAt } @@ -46,6 +47,7 @@ class DeletedActorRepositoryImpl : DeletedActorRepository, AbstractRepository() DeletedActors.update({ DeletedActors.id eq deletedActor.id }) { it[name] = deletedActor.name it[domain] = deletedActor.domain + it[apId] = deletedActor.apiId it[publicKey] = deletedActor.publicKey it[deletedAt] = deletedActor.deletedAt } @@ -84,6 +86,7 @@ private fun deletedActor(singleOr: ResultRow): DeletedActor { singleOr[DeletedActors.name], singleOr[DeletedActors.domain], singleOr[DeletedActors.publicKey], + singleOr[DeletedActors.apId], singleOr[DeletedActors.deletedAt] ) } @@ -92,6 +95,7 @@ object DeletedActors : Table("deleted_actors") { val id = long("id") val name = varchar("name", 300) val domain = varchar("domain", 255) + val apId = varchar("ap_id", 255).uniqueIndex() val publicKey = varchar("public_key", 10000).uniqueIndex() val deletedAt = timestamp("deleted_at") override val primaryKey: PrimaryKey = PrimaryKey(id) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/PostRepositoryImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/PostRepositoryImpl.kt index 119af8f0..c2dbcd28 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/PostRepositoryImpl.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/PostRepositoryImpl.kt @@ -20,6 +20,19 @@ import dev.usbharu.hideout.application.infrastructure.exposed.QueryMapper import dev.usbharu.hideout.application.service.id.IdGenerateService import dev.usbharu.hideout.core.domain.model.post.Post import dev.usbharu.hideout.core.domain.model.post.PostRepository +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.actorId +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.apId +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.content +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.createdAt +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.deleted +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.id +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.overview +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.replyId +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.repostId +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.sensitive +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.text +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.url +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.visibility import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.slf4j.Logger @@ -29,7 +42,7 @@ import org.springframework.stereotype.Repository @Repository class PostRepositoryImpl( private val idGenerateService: IdGenerateService, - private val postQueryMapper: QueryMapper + private val postQueryMapper: QueryMapper, ) : PostRepository, AbstractRepository() { override val logger: Logger get() = Companion.logger @@ -37,7 +50,7 @@ class PostRepositoryImpl( override suspend fun generateId(): Long = idGenerateService.generateId() override suspend fun save(post: Post): Post = query { - val singleOrNull = Posts.selectAll().where { Posts.id eq post.id }.forUpdate().singleOrNull() + val singleOrNull = Posts.selectAll().where { id eq post.id }.forUpdate().singleOrNull() if (singleOrNull == null) { Posts.insert { it[id] = post.id @@ -77,7 +90,7 @@ class PostRepositoryImpl( this[PostsEmojis.postId] = post.id this[PostsEmojis.emojiId] = it } - Posts.update({ Posts.id eq post.id }) { + Posts.update({ id eq post.id }) { it[actorId] = post.actorId it[overview] = post.overview it[content] = post.content @@ -95,6 +108,39 @@ class PostRepositoryImpl( return@query post } + override suspend fun saveAll(posts: List) { + Posts.batchUpsert( + posts, id, + ) { + this[id] = it.id + this[actorId] = it.actorId + this[overview] = it.overview + this[content] = it.content + this[text] = it.text + this[createdAt] = it.createdAt + this[visibility] = it.visibility.ordinal + this[url] = it.url + this[repostId] = it.repostId + this[replyId] = it.replyId + this[sensitive] = it.sensitive + this[apId] = it.apId + this[deleted] = it.deleted + } + val mediaIds = posts.flatMap { post -> post.mediaIds.map { post.id to it } } + PostsMedia.batchUpsert( + mediaIds, PostsMedia.postId + ) { + this[PostsMedia.postId] = it.first + this[PostsMedia.mediaId] = it.second + } + + val emojiIds = posts.flatMap { post -> post.emojiIds.map { post.id to it } } + PostsEmojis.batchUpsert(emojiIds, PostsEmojis.postId) { + this[PostsEmojis.postId] = it.first + this[PostsEmojis.emojiId] = it.second + } + } + override suspend fun findById(id: Long): Post? = query { return@query Posts .leftJoin(PostsMedia) @@ -133,6 +179,10 @@ class PostRepositoryImpl( .selectAll().where { Posts.actorId eq actorId }.let(postQueryMapper::map) } + override suspend fun findByActorIdAndDeleted(actorId: Long, deleted: Boolean): List { + TODO("Not yet implemented") + } + override suspend fun countByActorId(actorId: Long): Int = query { return@query Posts .selectAll() @@ -168,13 +218,13 @@ object Posts : Table() { } object PostsMedia : Table("posts_media") { - val postId = long("post_id").references(Posts.id, ReferenceOption.CASCADE, ReferenceOption.CASCADE) + val postId = long("post_id").references(id, ReferenceOption.CASCADE, ReferenceOption.CASCADE) val mediaId = long("media_id").references(Media.id, ReferenceOption.CASCADE, ReferenceOption.CASCADE) override val primaryKey = PrimaryKey(postId, mediaId) } object PostsEmojis : Table("posts_emojis") { - val postId = long("post_id").references(Posts.id) + val postId = long("post_id").references(id) val emojiId = long("emoji_id").references(CustomEmojis.id) override val primaryKey: PrimaryKey = PrimaryKey(postId, emojiId) } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/post/PostService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/post/PostService.kt index 8bc6a1d6..1a177666 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/post/PostService.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/post/PostService.kt @@ -26,4 +26,5 @@ interface PostService { suspend fun deleteLocal(post: Post) suspend fun deleteRemote(post: Post) suspend fun deleteByActor(actorId: Long) + suspend fun restoreByRemoteActor(actorId: Long) } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/post/PostServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/post/PostServiceImpl.kt index 63675779..eb5c2dff 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/post/PostServiceImpl.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/post/PostServiceImpl.kt @@ -18,9 +18,9 @@ package dev.usbharu.hideout.core.service.post import dev.usbharu.hideout.activitypub.service.activity.create.ApSendCreateService import dev.usbharu.hideout.activitypub.service.activity.delete.APSendDeleteService -import dev.usbharu.hideout.core.domain.exception.UserNotFoundException import dev.usbharu.hideout.core.domain.exception.resource.DuplicateException import dev.usbharu.hideout.core.domain.exception.resource.PostNotFoundException +import dev.usbharu.hideout.core.domain.exception.resource.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 @@ -38,7 +38,7 @@ class PostServiceImpl( private val postBuilder: Post.PostBuilder, private val apSendCreateService: ApSendCreateService, private val reactionRepository: ReactionRepository, - private val apSendDeleteService: APSendDeleteService + private val apSendDeleteService: APSendDeleteService, ) : PostService { override suspend fun createLocal(post: PostCreateDto): Post { @@ -52,7 +52,7 @@ class PostServiceImpl( override suspend fun createRemote(post: Post): Post { logger.info("START Create Remote Post user: {}, remote url: {}", post.actorId, post.apId) val actor = - actorRepository.findById(post.actorId) ?: throw UserNotFoundException("${post.actorId} was not found.") + actorRepository.findById(post.actorId) ?: throw UserNotFoundException.withId(post.actorId) val createdPost = internalCreate(post, false) logger.info("SUCCESS Create Remote Post url: {}", createdPost.url) return createdPost @@ -86,14 +86,25 @@ class PostServiceImpl( } override suspend fun deleteByActor(actorId: Long) { - postRepository.findByActorId(actorId).filterNot { it.deleted }.forEach { postRepository.save(it.delete()) } - val actor = actorRepository.findById(actorId) ?: throw IllegalStateException("actor: $actorId was not found.") + postRepository.findByActorId(actorId).filterNot { it.deleted }.forEach { postRepository.save(it.delete()) } + + actorRepository.save(actor.copy(postsCount = 0, lastPostDate = null)) } + override suspend fun restoreByRemoteActor(actorId: Long) { + val actor = actorRepository.findById(actorId) ?: throw UserNotFoundException.withId(actorId) + + val postList = postRepository.findByActorIdAndDeleted(actorId, true).map { it.restore() } + + postRepository.saveAll(postList) + + actorRepository.save(actor.copy(postsCount = actor.postsCount.plus(postList.size))) + } + private suspend fun internalCreate(post: Post, isLocal: Boolean): Post { return try { val save = postRepository.save(post) diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/APNoteServiceImplTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/APNoteServiceImplTest.kt index b5301461..3520c53a 100644 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/APNoteServiceImplTest.kt +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/APNoteServiceImplTest.kt @@ -154,7 +154,7 @@ class APNoteServiceImplTest { ) val apUserService = mock { - onBlocking { fetchPersonWithEntity(eq(note.attributedTo), isNull()) } doReturn (person to user) + onBlocking { fetchPersonWithEntity(eq(note.attributedTo), isNull(), anyOrNull()) } doReturn (person to user) } val postRepository = mock { onBlocking { generateId() } doReturn TwitterSnowflakeIdGenerateService.generateId() @@ -255,7 +255,7 @@ class APNoteServiceImplTest { followers = user.followers ) val apUserService = mock { - onBlocking { fetchPersonWithEntity(eq(user.url), anyOrNull()) } doReturn (person to user) + onBlocking { fetchPersonWithEntity(eq(user.url), anyOrNull(), anyOrNull()) } doReturn (person to user) } val postService = mock() val noteQueryService = mock { diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/user/ActorServiceTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/user/ActorServiceTest.kt index 7a22a57b..834c3650 100644 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/user/ActorServiceTest.kt +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/user/ActorServiceTest.kt @@ -69,7 +69,8 @@ class ActorServiceTest { relationshipRepository = mock(), postService = mock(), apSendDeleteService = mock(), - postRepository = mock() + postRepository = mock(), + owlProducer = mock() ) userService.createLocalUser(UserCreateDto("test", "testUser", "XXXXXXXXXXXXX", "test")) verify(actorRepository, times(1)).save(any()) @@ -112,7 +113,8 @@ class ActorServiceTest { relationshipRepository = mock(), postService = mock(), apSendDeleteService = mock(), - postRepository = mock() + postRepository = mock(), + owlProducer = mock() ) assertThrows { @@ -162,7 +164,8 @@ class ActorServiceTest { relationshipRepository = mock(), postService = mock(), apSendDeleteService = mock(), - postRepository = mock() + postRepository = mock(), + owlProducer = mock() ) val user = RemoteUserCreateDto( name = "test", diff --git a/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/UpdateActorWorker.kt b/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/UpdateActorWorker.kt index 6021db6f..f25b8dce 100644 --- a/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/UpdateActorWorker.kt +++ b/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/UpdateActorWorker.kt @@ -20,6 +20,7 @@ import dev.usbharu.hideout.activitypub.service.objects.user.APUserService import dev.usbharu.hideout.application.external.Transaction import dev.usbharu.hideout.core.external.job.UpdateActorTask import dev.usbharu.hideout.core.external.job.UpdateActorTaskDef +import dev.usbharu.hideout.core.service.post.PostService import dev.usbharu.owl.consumer.AbstractTaskRunner import dev.usbharu.owl.consumer.TaskRequest import dev.usbharu.owl.consumer.TaskResult @@ -29,10 +30,13 @@ import org.springframework.stereotype.Component class UpdateActorWorker( private val transaction: Transaction, private val apUserService: APUserService, + private val postService: PostService, ) : AbstractTaskRunner(UpdateActorTaskDef) { override suspend fun typedRun(typedParam: UpdateActorTask, taskRequest: TaskRequest): TaskResult { transaction.transaction { apUserService.fetchPerson(typedParam.apId, idOverride = typedParam.id) + + postService.restoreByRemoteActor(typedParam.id) } return TaskResult.ok()