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

@ -15,22 +15,26 @@ create table if not exists instance
); );
create table if not exists actors create table if not exists actors
( (
id bigint primary key, id bigint primary key,
"name" varchar(300) not null, "name" varchar(300) not null,
"domain" varchar(1000) not null, "domain" varchar(1000) not null,
screen_name varchar(300) not null, screen_name varchar(300) not null,
description varchar(10000) not null, description varchar(10000) not null,
inbox varchar(1000) not null unique, inbox varchar(1000) not null unique,
outbox varchar(1000) not null unique, outbox varchar(1000) not null unique,
url varchar(1000) not null unique, url varchar(1000) not null unique,
public_key varchar(10000) not null, public_key varchar(10000) not null,
private_key varchar(10000) null, private_key varchar(10000) null,
created_at bigint not null, created_at bigint not null,
key_id varchar(1000) not null, key_id varchar(1000) not null,
"following" varchar(1000) null, "following" varchar(1000) null,
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
( (