diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/AccountQueryServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/AccountQueryServiceImpl.kt new file mode 100644 index 00000000..13daaf89 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/AccountQueryServiceImpl.kt @@ -0,0 +1,100 @@ +package dev.usbharu.hideout.mastodon.infrastructure.exposedquery + +import dev.usbharu.hideout.application.config.ApplicationConfig +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.Posts +import dev.usbharu.hideout.domain.mastodon.model.generated.Account +import dev.usbharu.hideout.mastodon.query.AccountQueryService +import dev.usbharu.hideout.util.singleOr +import org.jetbrains.exposed.sql.* +import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq +import org.springframework.stereotype.Repository +import java.time.Instant + +@Repository +class AccountQueryServiceImpl(private val applicationConfig: ApplicationConfig) : AccountQueryService { + 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 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 + .singleOr { FailedToGetResourcesException("accountId: $accountId wad not exist or duplicate", it) } + .let { toAccount(it, followingCount, followersCount, postsCount, lastCreated) } + } + + override suspend fun findByIds(accountIds: List): List { + 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 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 + .map { toAccount(it, followingCount, followersCount, postsCount, lastCreated) } + } + + private fun toAccount( + resultRow: ResultRow, + followingCount: ExpressionAlias, + followersCount: ExpressionAlias, + postsCount: ExpressionAlias, + lastCreated: ExpressionAlias + ): Account { + val userUrl = "${applicationConfig.url}/users/${resultRow[Actors.id]}" + + return Account( + id = resultRow[Actors.id].toString(), + username = resultRow[Actors.name], + acct = "${resultRow[Actors.name]}@${resultRow[Actors.domain]}", + url = resultRow[Actors.url], + displayName = resultRow[Actors.screenName], + note = resultRow[Actors.description], + avatar = userUrl + "/icon.jpg", + avatarStatic = userUrl + "/icon.jpg", + header = userUrl + "/header.jpg", + headerStatic = userUrl + "/header.jpg", + locked = resultRow[Actors.locked], + fields = emptyList(), + emojis = emptyList(), + bot = false, + group = false, + discoverable = true, + createdAt = Instant.ofEpochMilli(resultRow[Actors.createdAt]).toString(), + lastStatusAt = resultRow[lastCreated]?.let { Instant.ofEpochMilli(it).toString() }, + statusesCount = resultRow[postsCount].toInt(), + followersCount = resultRow[followersCount].toInt(), + followingCount = resultRow[followingCount].toInt(), + ) + } +} diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/query/AccountQueryService.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/query/AccountQueryService.kt new file mode 100644 index 00000000..37eb2d98 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/query/AccountQueryService.kt @@ -0,0 +1,8 @@ +package dev.usbharu.hideout.mastodon.query + +import dev.usbharu.hideout.domain.mastodon.model.generated.Account + +interface AccountQueryService { + suspend fun findById(accountId: Long): Account + suspend fun findByIds(accountIds: List): List +} diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountService.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountService.kt index 9904e6ce..88cb1f88 100644 --- a/src/main/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountService.kt @@ -1,9 +1,7 @@ package dev.usbharu.hideout.mastodon.service.account -import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.core.domain.model.actor.Actor -import dev.usbharu.hideout.core.query.ActorQueryService import dev.usbharu.hideout.domain.mastodon.model.generated.Account +import dev.usbharu.hideout.mastodon.query.AccountQueryService import org.springframework.stereotype.Service @Service @@ -14,41 +12,13 @@ interface AccountService { @Service class AccountServiceImpl( - private val actorQueryService: ActorQueryService, - private val applicationConfig: ApplicationConfig + private val accountQueryService: AccountQueryService ) : AccountService { override suspend fun findById(id: Long): Account { - val findById = actorQueryService.findById(id) - return toAccount(findById) + return accountQueryService.findById(id) } - private fun toAccount(findById: Actor): Account { - val userUrl = applicationConfig.url.toString() + "/users/" + findById.id.toString() - - return Account( - id = findById.id.toString(), - username = findById.name, - acct = "${findById.name}@${findById.domain}", - url = findById.url, - displayName = findById.screenName, - note = findById.description, - avatar = "$userUrl/icon.jpg", - avatarStatic = "$userUrl/icon.jpg", - header = "$userUrl/header.jpg", - headerStatic = "$userUrl/header.jpg", - locked = findById.locked, - fields = emptyList(), - emojis = emptyList(), - bot = false, - group = false, - discoverable = false, - createdAt = findById.createdAt.toString(), - lastStatusAt = findById.createdAt.toString(), - statusesCount = 0, - followersCount = 0, - ) + override suspend fun findByIds(ids: List): List { + return accountQueryService.findByIds(ids) } - - override suspend fun findByIds(ids: List): List = - actorQueryService.findByIds(ids).map { toAccount(it) } }