feat: Repositoryを修正

This commit is contained in:
usbharu 2024-05-17 23:12:58 +09:00
parent 84b038f472
commit 2e7efd5d6d
10 changed files with 100 additions and 32 deletions

View File

@ -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.post.PostService
import dev.usbharu.hideout.core.service.reaction.ReactionService import dev.usbharu.hideout.core.service.reaction.ReactionService
import dev.usbharu.hideout.core.service.relationship.RelationshipService import dev.usbharu.hideout.core.service.relationship.RelationshipService
import dev.usbharu.hideout.core.service.user.UserService
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
@Service @Service
@ -41,7 +42,8 @@ class APUndoProcessor(
private val reactionService: ReactionService, private val reactionService: ReactionService,
private val actorRepository: ActorRepository, private val actorRepository: ActorRepository,
private val postRepository: PostRepository, private val postRepository: PostRepository,
private val postService: PostService private val postService: PostService,
private val userService: UserService,
) : AbstractActivityPubProcessor<Undo>(transaction) { ) : AbstractActivityPubProcessor<Undo>(transaction) {
override suspend fun internalProcess(activity: ActivityPubProcessContext<Undo>) { override suspend fun internalProcess(activity: ActivityPubProcessContext<Undo>) {
val undo = activity.activity val undo = activity.activity
@ -132,7 +134,9 @@ class APUndoProcessor(
private suspend fun delete(undo: Undo) { private suspend fun delete(undo: Undo) {
val announce = undo.apObject as Delete 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 override fun isSupported(activityType: ActivityType): Boolean = activityType == ActivityType.Undo

View File

@ -215,21 +215,10 @@ data class Post private constructor(
this.repostId != null this.repostId != null
fun delete(): Post { fun delete(): Post {
return Post( return copy(deleted = true)
id = this.id, }
actorId = actorId,
overview = overview, fun restore(): Post {
content = content, return copy(deleted = false)
text = text,
createdAt = createdAt,
visibility = visibility,
url = url,
repostId = repostId,
replyId = replyId,
sensitive = sensitive,
apId = apId,
mediaIds = mediaIds,
deleted = true
)
} }
} }

View File

@ -23,6 +23,7 @@ import org.springframework.stereotype.Repository
interface PostRepository { interface PostRepository {
suspend fun generateId(): Long suspend fun generateId(): Long
suspend fun save(post: Post): Post suspend fun save(post: Post): Post
suspend fun saveAll(posts: List<Post>)
suspend fun delete(id: Long) suspend fun delete(id: Long)
suspend fun findById(id: Long): Post? suspend fun findById(id: Long): Post?
suspend fun findByUrl(url: String): Post? suspend fun findByUrl(url: String): Post?
@ -30,6 +31,7 @@ interface PostRepository {
suspend fun findByApId(apId: String): Post? suspend fun findByApId(apId: String): Post?
suspend fun existByApIdWithLock(apId: String): Boolean suspend fun existByApIdWithLock(apId: String): Boolean
suspend fun findByActorId(actorId: Long): List<Post> suspend fun findByActorId(actorId: Long): List<Post>
suspend fun findByActorIdAndDeleted(actorId: Long, deleted: Boolean): List<Post>
suspend fun countByActorId(actorId: Long): Int suspend fun countByActorId(actorId: Long): Int
} }

View File

@ -39,6 +39,7 @@ class DeletedActorRepositoryImpl : DeletedActorRepository, AbstractRepository()
it[id] = deletedActor.id it[id] = deletedActor.id
it[name] = deletedActor.name it[name] = deletedActor.name
it[domain] = deletedActor.domain it[domain] = deletedActor.domain
it[apId] = deletedActor.apiId
it[publicKey] = deletedActor.publicKey it[publicKey] = deletedActor.publicKey
it[deletedAt] = deletedActor.deletedAt it[deletedAt] = deletedActor.deletedAt
} }
@ -46,6 +47,7 @@ class DeletedActorRepositoryImpl : DeletedActorRepository, AbstractRepository()
DeletedActors.update({ DeletedActors.id eq deletedActor.id }) { DeletedActors.update({ DeletedActors.id eq deletedActor.id }) {
it[name] = deletedActor.name it[name] = deletedActor.name
it[domain] = deletedActor.domain it[domain] = deletedActor.domain
it[apId] = deletedActor.apiId
it[publicKey] = deletedActor.publicKey it[publicKey] = deletedActor.publicKey
it[deletedAt] = deletedActor.deletedAt it[deletedAt] = deletedActor.deletedAt
} }
@ -84,6 +86,7 @@ private fun deletedActor(singleOr: ResultRow): DeletedActor {
singleOr[DeletedActors.name], singleOr[DeletedActors.name],
singleOr[DeletedActors.domain], singleOr[DeletedActors.domain],
singleOr[DeletedActors.publicKey], singleOr[DeletedActors.publicKey],
singleOr[DeletedActors.apId],
singleOr[DeletedActors.deletedAt] singleOr[DeletedActors.deletedAt]
) )
} }
@ -92,6 +95,7 @@ object DeletedActors : Table("deleted_actors") {
val id = long("id") val id = long("id")
val name = varchar("name", 300) val name = varchar("name", 300)
val domain = varchar("domain", 255) val domain = varchar("domain", 255)
val apId = varchar("ap_id", 255).uniqueIndex()
val publicKey = varchar("public_key", 10000).uniqueIndex() val publicKey = varchar("public_key", 10000).uniqueIndex()
val deletedAt = timestamp("deleted_at") val deletedAt = timestamp("deleted_at")
override val primaryKey: PrimaryKey = PrimaryKey(id) override val primaryKey: PrimaryKey = PrimaryKey(id)

View File

@ -20,6 +20,19 @@ import dev.usbharu.hideout.application.infrastructure.exposed.QueryMapper
import dev.usbharu.hideout.application.service.id.IdGenerateService 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.Post
import dev.usbharu.hideout.core.domain.model.post.PostRepository 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.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.slf4j.Logger import org.slf4j.Logger
@ -29,7 +42,7 @@ import org.springframework.stereotype.Repository
@Repository @Repository
class PostRepositoryImpl( class PostRepositoryImpl(
private val idGenerateService: IdGenerateService, private val idGenerateService: IdGenerateService,
private val postQueryMapper: QueryMapper<Post> private val postQueryMapper: QueryMapper<Post>,
) : PostRepository, AbstractRepository() { ) : PostRepository, AbstractRepository() {
override val logger: Logger override val logger: Logger
get() = Companion.logger get() = Companion.logger
@ -37,7 +50,7 @@ class PostRepositoryImpl(
override suspend fun generateId(): Long = idGenerateService.generateId() override suspend fun generateId(): Long = idGenerateService.generateId()
override suspend fun save(post: Post): Post = query { 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) { if (singleOrNull == null) {
Posts.insert { Posts.insert {
it[id] = post.id it[id] = post.id
@ -77,7 +90,7 @@ class PostRepositoryImpl(
this[PostsEmojis.postId] = post.id this[PostsEmojis.postId] = post.id
this[PostsEmojis.emojiId] = it this[PostsEmojis.emojiId] = it
} }
Posts.update({ Posts.id eq post.id }) { Posts.update({ id eq post.id }) {
it[actorId] = post.actorId it[actorId] = post.actorId
it[overview] = post.overview it[overview] = post.overview
it[content] = post.content it[content] = post.content
@ -95,6 +108,39 @@ class PostRepositoryImpl(
return@query post return@query post
} }
override suspend fun saveAll(posts: List<Post>) {
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 { override suspend fun findById(id: Long): Post? = query {
return@query Posts return@query Posts
.leftJoin(PostsMedia) .leftJoin(PostsMedia)
@ -133,6 +179,10 @@ class PostRepositoryImpl(
.selectAll().where { Posts.actorId eq actorId }.let(postQueryMapper::map) .selectAll().where { Posts.actorId eq actorId }.let(postQueryMapper::map)
} }
override suspend fun findByActorIdAndDeleted(actorId: Long, deleted: Boolean): List<Post> {
TODO("Not yet implemented")
}
override suspend fun countByActorId(actorId: Long): Int = query { override suspend fun countByActorId(actorId: Long): Int = query {
return@query Posts return@query Posts
.selectAll() .selectAll()
@ -168,13 +218,13 @@ object Posts : Table() {
} }
object PostsMedia : Table("posts_media") { 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) val mediaId = long("media_id").references(Media.id, ReferenceOption.CASCADE, ReferenceOption.CASCADE)
override val primaryKey = PrimaryKey(postId, mediaId) override val primaryKey = PrimaryKey(postId, mediaId)
} }
object PostsEmojis : Table("posts_emojis") { 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) val emojiId = long("emoji_id").references(CustomEmojis.id)
override val primaryKey: PrimaryKey = PrimaryKey(postId, emojiId) override val primaryKey: PrimaryKey = PrimaryKey(postId, emojiId)
} }

View File

@ -26,4 +26,5 @@ interface PostService {
suspend fun deleteLocal(post: Post) suspend fun deleteLocal(post: Post)
suspend fun deleteRemote(post: Post) suspend fun deleteRemote(post: Post)
suspend fun deleteByActor(actorId: Long) suspend fun deleteByActor(actorId: Long)
suspend fun restoreByRemoteActor(actorId: Long)
} }

View File

@ -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.create.ApSendCreateService
import dev.usbharu.hideout.activitypub.service.activity.delete.APSendDeleteService 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.DuplicateException
import dev.usbharu.hideout.core.domain.exception.resource.PostNotFoundException 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.actor.ActorRepository
import dev.usbharu.hideout.core.domain.model.post.Post 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.post.PostRepository
@ -38,7 +38,7 @@ class PostServiceImpl(
private val postBuilder: Post.PostBuilder, private val postBuilder: Post.PostBuilder,
private val apSendCreateService: ApSendCreateService, private val apSendCreateService: ApSendCreateService,
private val reactionRepository: ReactionRepository, private val reactionRepository: ReactionRepository,
private val apSendDeleteService: APSendDeleteService private val apSendDeleteService: APSendDeleteService,
) : PostService { ) : PostService {
override suspend fun createLocal(post: PostCreateDto): Post { override suspend fun createLocal(post: PostCreateDto): Post {
@ -52,7 +52,7 @@ class PostServiceImpl(
override suspend fun createRemote(post: Post): Post { override suspend fun createRemote(post: Post): Post {
logger.info("START Create Remote Post user: {}, remote url: {}", post.actorId, post.apId) logger.info("START Create Remote Post user: {}, remote url: {}", post.actorId, post.apId)
val actor = 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) val createdPost = internalCreate(post, false)
logger.info("SUCCESS Create Remote Post url: {}", createdPost.url) logger.info("SUCCESS Create Remote Post url: {}", createdPost.url)
return createdPost return createdPost
@ -86,14 +86,25 @@ class PostServiceImpl(
} }
override suspend fun deleteByActor(actorId: Long) { override suspend fun deleteByActor(actorId: Long) {
postRepository.findByActorId(actorId).filterNot { it.deleted }.forEach { postRepository.save(it.delete()) }
val actor = actorRepository.findById(actorId) val actor = actorRepository.findById(actorId)
?: throw IllegalStateException("actor: $actorId was not found.") ?: 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)) 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 { private suspend fun internalCreate(post: Post, isLocal: Boolean): Post {
return try { return try {
val save = postRepository.save(post) val save = postRepository.save(post)

View File

@ -154,7 +154,7 @@ class APNoteServiceImplTest {
) )
val apUserService = mock<APUserService> { val apUserService = mock<APUserService> {
onBlocking { fetchPersonWithEntity(eq(note.attributedTo), isNull()) } doReturn (person to user) onBlocking { fetchPersonWithEntity(eq(note.attributedTo), isNull(), anyOrNull()) } doReturn (person to user)
} }
val postRepository = mock<PostRepository> { val postRepository = mock<PostRepository> {
onBlocking { generateId() } doReturn TwitterSnowflakeIdGenerateService.generateId() onBlocking { generateId() } doReturn TwitterSnowflakeIdGenerateService.generateId()
@ -255,7 +255,7 @@ class APNoteServiceImplTest {
followers = user.followers followers = user.followers
) )
val apUserService = mock<APUserService> { val apUserService = mock<APUserService> {
onBlocking { fetchPersonWithEntity(eq(user.url), anyOrNull()) } doReturn (person to user) onBlocking { fetchPersonWithEntity(eq(user.url), anyOrNull(), anyOrNull()) } doReturn (person to user)
} }
val postService = mock<PostService>() val postService = mock<PostService>()
val noteQueryService = mock<NoteQueryService> { val noteQueryService = mock<NoteQueryService> {

View File

@ -69,7 +69,8 @@ class ActorServiceTest {
relationshipRepository = mock(), relationshipRepository = mock(),
postService = mock(), postService = mock(),
apSendDeleteService = mock(), apSendDeleteService = mock(),
postRepository = mock() postRepository = mock(),
owlProducer = mock()
) )
userService.createLocalUser(UserCreateDto("test", "testUser", "XXXXXXXXXXXXX", "test")) userService.createLocalUser(UserCreateDto("test", "testUser", "XXXXXXXXXXXXX", "test"))
verify(actorRepository, times(1)).save(any()) verify(actorRepository, times(1)).save(any())
@ -112,7 +113,8 @@ class ActorServiceTest {
relationshipRepository = mock(), relationshipRepository = mock(),
postService = mock(), postService = mock(),
apSendDeleteService = mock(), apSendDeleteService = mock(),
postRepository = mock() postRepository = mock(),
owlProducer = mock()
) )
assertThrows<IllegalStateException> { assertThrows<IllegalStateException> {
@ -162,7 +164,8 @@ class ActorServiceTest {
relationshipRepository = mock(), relationshipRepository = mock(),
postService = mock(), postService = mock(),
apSendDeleteService = mock(), apSendDeleteService = mock(),
postRepository = mock() postRepository = mock(),
owlProducer = mock()
) )
val user = RemoteUserCreateDto( val user = RemoteUserCreateDto(
name = "test", name = "test",

View File

@ -20,6 +20,7 @@ import dev.usbharu.hideout.activitypub.service.objects.user.APUserService
import dev.usbharu.hideout.application.external.Transaction import dev.usbharu.hideout.application.external.Transaction
import dev.usbharu.hideout.core.external.job.UpdateActorTask import dev.usbharu.hideout.core.external.job.UpdateActorTask
import dev.usbharu.hideout.core.external.job.UpdateActorTaskDef 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.AbstractTaskRunner
import dev.usbharu.owl.consumer.TaskRequest import dev.usbharu.owl.consumer.TaskRequest
import dev.usbharu.owl.consumer.TaskResult import dev.usbharu.owl.consumer.TaskResult
@ -29,10 +30,13 @@ import org.springframework.stereotype.Component
class UpdateActorWorker( class UpdateActorWorker(
private val transaction: Transaction, private val transaction: Transaction,
private val apUserService: APUserService, private val apUserService: APUserService,
private val postService: PostService,
) : AbstractTaskRunner<UpdateActorTask, UpdateActorTaskDef>(UpdateActorTaskDef) { ) : AbstractTaskRunner<UpdateActorTask, UpdateActorTaskDef>(UpdateActorTaskDef) {
override suspend fun typedRun(typedParam: UpdateActorTask, taskRequest: TaskRequest): TaskResult { override suspend fun typedRun(typedParam: UpdateActorTask, taskRequest: TaskRequest): TaskResult {
transaction.transaction { transaction.transaction {
apUserService.fetchPerson(typedParam.apId, idOverride = typedParam.id) apUserService.fetchPerson(typedParam.apId, idOverride = typedParam.id)
postService.restoreByRemoteActor(typedParam.id)
} }
return TaskResult.ok() return TaskResult.ok()