refactor: Mastodon APIでAccountの箇所を修正

This commit is contained in:
usbharu 2023-12-14 00:55:40 +09:00
parent 77d79e2279
commit bdd69d258c
5 changed files with 65 additions and 88 deletions

View File

@ -52,10 +52,10 @@ data class Actor private constructor(
followers: String? = null, followers: String? = null,
instance: Long? = null, instance: Long? = null,
locked: Boolean, locked: Boolean,
followersCount: Int, followersCount: Int = 0,
followingCount: Int, followingCount: Int = 0,
postsCount: Int, postsCount: Int = 0,
lastPostDate: Instant? lastPostDate: Instant? = null
): Actor { ): Actor {
// idは0未満ではいけない // idは0未満ではいけない
require(id >= 0) { "id must be greater than or equal to 0." } require(id >= 0) { "id must be greater than or equal to 0." }

View File

@ -2,6 +2,7 @@ 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.core.domain.exception.UserNotFoundException import dev.usbharu.hideout.core.domain.exception.UserNotFoundException
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.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
@ -35,7 +36,9 @@ 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 createdPost = internalCreate(post, false) val actor =
actorRepository.findById(post.actorId) ?: throw UserNotFoundException("${post.actorId} was not found.")
val createdPost = internalCreate(post, false, actor)
logger.info("SUCCESS Create Remote Post url: {}", createdPost.url) logger.info("SUCCESS Create Remote Post url: {}", createdPost.url)
return createdPost return createdPost
} }
@ -46,6 +49,10 @@ class PostServiceImpl(
} }
reactionRepository.deleteByPostId(post.id) reactionRepository.deleteByPostId(post.id)
postRepository.save(post.delete()) postRepository.save(post.delete())
val actor = actorRepository.findById(post.actorId)
?: throw IllegalStateException("actor: ${post.actorId} was not found.")
actorRepository.save(actor.decrementPostsCount())
} }
override suspend fun deleteRemote(post: Post) { override suspend fun deleteRemote(post: Post) {
@ -54,17 +61,28 @@ class PostServiceImpl(
} }
reactionRepository.deleteByPostId(post.id) reactionRepository.deleteByPostId(post.id)
postRepository.save(post.delete()) postRepository.save(post.delete())
val actor = actorRepository.findById(post.actorId)
?: throw IllegalStateException("actor: ${post.actorId} was not found.")
actorRepository.save(actor.decrementPostsCount())
} }
override suspend fun deleteByActor(actorId: Long) { override suspend fun deleteByActor(actorId: Long) {
postQueryService.findByActorId(actorId).filterNot { it.delted }.forEach { postRepository.save(it.delete()) } postQueryService.findByActorId(actorId).filterNot { it.delted }.forEach { postRepository.save(it.delete()) }
val actor = actorRepository.findById(actorId)
?: throw IllegalStateException("actor: ${actorId} was not found.")
actorRepository.save(actor.copy(postsCount = 0, lastPostDate = null))
} }
private suspend fun internalCreate(post: Post, isLocal: Boolean): Post { private suspend fun internalCreate(post: Post, isLocal: Boolean, actor: Actor): Post {
return try { return try {
if (postRepository.save(post)) { if (postRepository.save(post)) {
try { try {
timelineService.publishTimeline(post, isLocal) timelineService.publishTimeline(post, isLocal)
actorRepository.save(actor.incrementPostsCount())
} catch (e: DuplicateKeyException) { } catch (e: DuplicateKeyException) {
logger.trace("Timeline already exists.", e) logger.trace("Timeline already exists.", e)
} }
@ -91,7 +109,7 @@ class PostServiceImpl(
replyId = post.repolyId, replyId = post.repolyId,
repostId = post.repostId, repostId = post.repostId,
) )
return internalCreate(createPost, isLocal) return internalCreate(createPost, isLocal, user)
} }
companion object { companion object {

View File

@ -2,81 +2,35 @@ package dev.usbharu.hideout.mastodon.infrastructure.exposedquery
import dev.usbharu.hideout.application.config.ApplicationConfig import dev.usbharu.hideout.application.config.ApplicationConfig
import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException
import dev.usbharu.hideout.core.domain.model.relationship.Relationships
import dev.usbharu.hideout.core.infrastructure.exposedrepository.Actors import dev.usbharu.hideout.core.infrastructure.exposedrepository.Actors
import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts
import dev.usbharu.hideout.domain.mastodon.model.generated.Account import dev.usbharu.hideout.domain.mastodon.model.generated.Account
import dev.usbharu.hideout.mastodon.query.AccountQueryService import dev.usbharu.hideout.mastodon.query.AccountQueryService
import dev.usbharu.hideout.util.singleOr import dev.usbharu.hideout.util.singleOr
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.ResultRow
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.select
import org.springframework.stereotype.Repository import org.springframework.stereotype.Repository
import java.time.Instant import java.time.Instant
@Repository @Repository
class AccountQueryServiceImpl(private val applicationConfig: ApplicationConfig) : AccountQueryService { class AccountQueryServiceImpl(private val applicationConfig: ApplicationConfig) : AccountQueryService {
override suspend fun findById(accountId: Long): Account { override suspend fun findById(accountId: Long): Account {
val followingCount = Count(Relationships.actorId.eq(Actors.id), true).alias("following_count")
val followersCount = Count(Relationships.targetActorId.eq(Actors.id), true).alias("followers_count") val query = Actors.select { Actors.id eq accountId }
val postsCount = Posts.id.countDistinct().alias("posts_count")
val lastCreated = Posts.createdAt.max().alias("last_created")
val query = Actors
.join(Relationships, JoinType.LEFT) {
Actors.id eq Relationships.actorId or (Actors.id eq Relationships.targetActorId)
}
.leftJoin(Posts)
.slice(
followingCount,
followersCount,
*(Actors.realFields.toTypedArray()),
lastCreated,
postsCount
)
.select {
(Actors.id.eq(accountId)).and(
Relationships.following.eq(true).or(Relationships.following.isNull())
)
}
.groupBy(Actors.id)
return query return query
.singleOr { FailedToGetResourcesException("accountId: $accountId wad not exist or duplicate", it) } .singleOr { FailedToGetResourcesException("accountId: $accountId wad not exist or duplicate", it) }
.let { toAccount(it, followingCount, followersCount, postsCount, lastCreated) } .let { toAccount(it) }
} }
override suspend fun findByIds(accountIds: List<Long>): List<Account> { override suspend fun findByIds(accountIds: List<Long>): List<Account> {
val followingCount = Count(Relationships.actorId.eq(Actors.id), true).alias("following_count") val query = Actors.select { Actors.id inList accountIds }
val followersCount = Count(Relationships.targetActorId.eq(Actors.id), true).alias("followers_count")
val postsCount = Posts.id.countDistinct().alias("posts_count")
val lastCreated = Posts.createdAt.max().alias("last_created")
val query = Actors
.join(Relationships, JoinType.LEFT) {
Actors.id eq Relationships.actorId or (Actors.id eq Relationships.targetActorId)
}
.leftJoin(Posts)
.slice(
followingCount,
followersCount,
*(Actors.realFields.toTypedArray()),
lastCreated,
postsCount
)
.select {
Actors.id.inList(accountIds)
.and(Relationships.following.eq(true).or(Relationships.following.isNull()))
}
.groupBy(Actors.id)
return query return query
.map { toAccount(it, followingCount, followersCount, postsCount, lastCreated) } .map { toAccount(it) }
} }
private fun toAccount( private fun toAccount(
resultRow: ResultRow, resultRow: ResultRow
followingCount: ExpressionAlias<Long>,
followersCount: ExpressionAlias<Long>,
postsCount: ExpressionAlias<Long>,
lastCreated: ExpressionAlias<Long?>
): Account { ): Account {
val userUrl = "${applicationConfig.url}/users/${resultRow[Actors.id]}" val userUrl = "${applicationConfig.url}/users/${resultRow[Actors.id]}"
@ -98,10 +52,10 @@ class AccountQueryServiceImpl(private val applicationConfig: ApplicationConfig)
group = false, group = false,
discoverable = true, discoverable = true,
createdAt = Instant.ofEpochMilli(resultRow[Actors.createdAt]).toString(), createdAt = Instant.ofEpochMilli(resultRow[Actors.createdAt]).toString(),
lastStatusAt = resultRow[lastCreated]?.let { Instant.ofEpochMilli(it).toString() }, lastStatusAt = resultRow[Actors.lastPostAt]?.toString(),
statusesCount = resultRow[postsCount].toInt(), statusesCount = resultRow[Actors.postsCount],
followersCount = resultRow[followersCount].toInt(), followersCount = resultRow[Actors.followersCount],
followingCount = resultRow[followingCount].toInt(), followingCount = resultRow[Actors.followingCount],
) )
} }
} }

View File

@ -151,17 +151,17 @@ private fun toStatus(it: ResultRow) = Status(
avatarStatic = it[Actors.url] + "/icon.jpg", avatarStatic = it[Actors.url] + "/icon.jpg",
header = it[Actors.url] + "/header.jpg", header = it[Actors.url] + "/header.jpg",
headerStatic = it[Actors.url] + "/header.jpg", headerStatic = it[Actors.url] + "/header.jpg",
locked = false, locked = it[Actors.locked],
fields = emptyList(), fields = emptyList(),
emojis = emptyList(), emojis = emptyList(),
bot = false, bot = false,
group = false, group = false,
discoverable = true, discoverable = true,
createdAt = Instant.ofEpochMilli(it[Actors.createdAt]).toString(), createdAt = Instant.ofEpochMilli(it[Actors.createdAt]).toString(),
lastStatusAt = Instant.ofEpochMilli(it[Actors.createdAt]).toString(), lastStatusAt = it[Actors.lastPostAt]?.toString(),
statusesCount = 0, statusesCount = it[Actors.postsCount],
followersCount = 0, followersCount = it[Actors.followersCount],
followingCount = 0, followingCount = it[Actors.followingCount],
noindex = false, noindex = false,
moved = false, moved = false,
suspendex = false, suspendex = false,

View File

@ -31,6 +31,10 @@ create table if not exists actors
followers varchar(1000) null, followers varchar(1000) null,
"instance" bigint null, "instance" bigint null,
locked boolean not null, locked boolean not null,
following_count int not null,
followers_count int not null,
posts_count int not null,
last_post_at timestamp null default null,
unique ("name", "domain"), unique ("name", "domain"),
constraint fk_actors_instance__id foreign key ("instance") references instance (id) on delete restrict on update restrict constraint fk_actors_instance__id foreign key ("instance") references instance (id) on delete restrict on update restrict
); );
@ -200,8 +204,9 @@ create table if not exists relationships
); );
insert into actors (id, name, domain, screen_name, description, inbox, outbox, url, public_key, private_key, created_at, insert into actors (id, name, domain, screen_name, description, inbox, outbox, url, public_key, private_key, created_at,
key_id, following, followers, instance, locked) key_id, following, followers, instance, locked, following_count, followers_count, posts_count,
values (0, 'ghost', '', '', '', '', '', '', '', null, 0, '', '', '', null, true); last_post_at)
values (0, 'ghost', '', '', '', '', '', '', '', null, 0, '', '', '', null, true, 0, 0, 0, null);
create table if not exists deleted_actors create table if not exists deleted_actors
( (