diff --git a/src/main/kotlin/dev/usbharu/hideout/application/infrastructure/exposed/ExposedPaginationExtension.kt b/src/main/kotlin/dev/usbharu/hideout/application/infrastructure/exposed/ExposedPaginationExtension.kt index 48047aac..796ab0c6 100644 --- a/src/main/kotlin/dev/usbharu/hideout/application/infrastructure/exposed/ExposedPaginationExtension.kt +++ b/src/main/kotlin/dev/usbharu/hideout/application/infrastructure/exposed/ExposedPaginationExtension.kt @@ -1,5 +1,6 @@ package dev.usbharu.hideout.application.infrastructure.exposed +import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.sql.ExpressionWithColumnType import org.jetbrains.exposed.sql.Query import org.jetbrains.exposed.sql.SortOrder @@ -7,11 +8,24 @@ import org.jetbrains.exposed.sql.andWhere fun Query.pagination(page: Page, exp: ExpressionWithColumnType): Query { if (page.minId != null) { - page.maxId?.let { andWhere { exp.lessEq(it) } } - page.minId?.let { andWhere { exp.greaterEq(it) } } + page.maxId?.let { andWhere { exp.less(it) } } + page.minId?.let { andWhere { exp.greater(it) } } } else { - page.maxId?.let { andWhere { exp.lessEq(it) } } - page.sinceId?.let { andWhere { exp.greaterEq(it) } } + page.maxId?.let { andWhere { exp.less(it) } } + page.sinceId?.let { andWhere { exp.greater(it) } } + this.orderBy(exp, SortOrder.DESC) + } + page.limit?.let { limit(it) } + return this +} + +fun Query.pagination(page: Page, exp: ExpressionWithColumnType>): Query { + if (page.minId != null) { + page.maxId?.let { andWhere { exp.less(it) } } + page.minId?.let { andWhere { exp.greater(it) } } + } else { + page.maxId?.let { andWhere { exp.less(it) } } + page.sinceId?.let { andWhere { exp.greater(it) } } this.orderBy(exp, SortOrder.DESC) } page.limit?.let { limit(it) } diff --git a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/RelationshipRepository.kt b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/RelationshipRepository.kt index 843e9eac..fa6666c4 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/RelationshipRepository.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/RelationshipRepository.kt @@ -1,5 +1,8 @@ package dev.usbharu.hideout.core.domain.model.relationship +import dev.usbharu.hideout.application.infrastructure.exposed.Page +import dev.usbharu.hideout.application.infrastructure.exposed.PaginationList + /** * [Relationship]の永続化 * @@ -43,6 +46,13 @@ interface RelationshipRepository { ignoreFollowRequest: Boolean ): List + suspend fun findByTargetIdAndFollowRequestAndIgnoreFollowRequest( + targetIdLong: Long, + followRequest: Boolean, + ignoreFollowRequest: Boolean, + page: Page.PageByMaxId + ): PaginationList + @Suppress("FunctionMaxLength") suspend fun findByActorIdAntMutingAndMaxIdAndSinceId( actorId: Long, @@ -51,4 +61,10 @@ interface RelationshipRepository { sinceId: Long?, limit: Int ): List + + suspend fun findByActorIdAndMuting( + actorId: Long, + muting: Boolean, + page: Page.PageByMaxId + ): PaginationList } diff --git a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/RelationshipRepositoryImpl.kt b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/RelationshipRepositoryImpl.kt index 6aa2fd70..b2aa6bd5 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/RelationshipRepositoryImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/RelationshipRepositoryImpl.kt @@ -1,5 +1,8 @@ package dev.usbharu.hideout.core.domain.model.relationship +import dev.usbharu.hideout.application.infrastructure.exposed.Page +import dev.usbharu.hideout.application.infrastructure.exposed.PaginationList +import dev.usbharu.hideout.application.infrastructure.exposed.pagination import dev.usbharu.hideout.core.infrastructure.exposedrepository.AbstractRepository import dev.usbharu.hideout.core.infrastructure.exposedrepository.Actors import org.jetbrains.exposed.dao.id.LongIdTable @@ -97,6 +100,26 @@ class RelationshipRepositoryImpl : RelationshipRepository, AbstractRepository() return@query query.map { it.toRelationships() } } + override suspend fun findByTargetIdAndFollowRequestAndIgnoreFollowRequest( + targetId: Long, + followRequest: Boolean, + ignoreFollowRequest: Boolean, + page: Page.PageByMaxId + ): PaginationList = query { + val query = Relationships.select { + Relationships.targetActorId.eq(targetId).and(Relationships.followRequest.eq(followRequest)) + .and(Relationships.ignoreFollowRequestFromTarget.eq(ignoreFollowRequest)) + } + + val resultRowList = query.pagination(page, Relationships.id).toList() + + return@query PaginationList( + query.map { it.toRelationships() }, + resultRowList.lastOrNull()?.getOrNull(Relationships.id)?.value, + resultRowList.firstOrNull()?.getOrNull(Relationships.id)?.value + ) + } + override suspend fun findByActorIdAntMutingAndMaxIdAndSinceId( actorId: Long, muting: Boolean, @@ -119,6 +142,24 @@ class RelationshipRepositoryImpl : RelationshipRepository, AbstractRepository() return@query query.map { it.toRelationships() } } + override suspend fun findByActorIdAndMuting( + actorId: Long, + muting: Boolean, + page: Page.PageByMaxId + ): PaginationList = query { + val query = Relationships.select { + Relationships.actorId.eq(actorId).and(Relationships.muting.eq(muting)) + } + + val resultRowList = query.pagination(page, Relationships.id).toList() + + return@query PaginationList( + query.map { it.toRelationships() }, + resultRowList.lastOrNull()?.getOrNull(Relationships.id)?.value, + resultRowList.firstOrNull()?.getOrNull(Relationships.id)?.value + ) + } + companion object { private val logger = LoggerFactory.getLogger(RelationshipRepositoryImpl::class.java) } diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/account/MastodonAccountApiController.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/account/MastodonAccountApiController.kt index 463e1b06..269943d2 100644 --- a/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/account/MastodonAccountApiController.kt +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/account/MastodonAccountApiController.kt @@ -1,6 +1,9 @@ package dev.usbharu.hideout.mastodon.interfaces.api.account +import dev.usbharu.hideout.application.config.ApplicationConfig import dev.usbharu.hideout.application.external.Transaction +import dev.usbharu.hideout.application.infrastructure.exposed.Page +import dev.usbharu.hideout.application.infrastructure.exposed.toHttpHeader import dev.usbharu.hideout.controller.mastodon.generated.AccountApi import dev.usbharu.hideout.core.infrastructure.springframework.security.LoginUserContextHolder import dev.usbharu.hideout.core.service.user.UserCreateDto @@ -19,12 +22,12 @@ import java.net.URI class MastodonAccountApiController( private val accountApiService: AccountApiService, private val transaction: Transaction, - private val loginUserContextHolder: LoginUserContextHolder + private val loginUserContextHolder: LoginUserContextHolder, + private val applicationConfig: ApplicationConfig ) : AccountApi { override suspend fun apiV1AccountsIdFollowPost( - id: String, - followRequestBody: FollowRequestBody? + id: String, followRequestBody: FollowRequestBody? ): ResponseEntity { val userid = loginUserContextHolder.getLoginUserId() @@ -35,17 +38,11 @@ class MastodonAccountApiController( ResponseEntity.ok(accountApiService.account(id.toLong())) override suspend fun apiV1AccountsVerifyCredentialsGet(): ResponseEntity = ResponseEntity( - accountApiService.verifyCredentials(loginUserContextHolder.getLoginUserId()), - HttpStatus.OK + accountApiService.verifyCredentials(loginUserContextHolder.getLoginUserId()), HttpStatus.OK ) override suspend fun apiV1AccountsPost( - username: String, - password: String, - email: String?, - agreement: Boolean?, - locale: Boolean?, - reason: String? + username: String, password: String, email: String?, agreement: Boolean?, locale: Boolean?, reason: String? ): ResponseEntity { transaction.transaction { accountApiService.registerAccount(UserCreateDto(username, username, "", password)) @@ -85,8 +82,7 @@ class MastodonAccountApiController( } override fun apiV1AccountsRelationshipsGet( - id: List?, - withSuspended: Boolean + id: List?, withSuspended: Boolean ): ResponseEntity> = runBlocking { val userid = loginUserContextHolder.getLoginUserId() @@ -128,8 +124,7 @@ class MastodonAccountApiController( return ResponseEntity.ok(removeFromFollowers) } - override suspend fun apiV1AccountsUpdateCredentialsPatch(updateCredentials: UpdateCredentials?): - ResponseEntity { + override suspend fun apiV1AccountsUpdateCredentialsPatch(updateCredentials: UpdateCredentials?): ResponseEntity { val userid = loginUserContextHolder.getLoginUserId() val removeFromFollowers = accountApiService.updateProfile(userid, updateCredentials) @@ -157,10 +152,23 @@ class MastodonAccountApiController( runBlocking { val userid = loginUserContextHolder.getLoginUserId() - val accountFlow = - accountApiService.followRequests(userid, maxId?.toLong(), sinceId?.toLong(), limit ?: 20, false) - .asFlow() - ResponseEntity.ok(accountFlow) + val followRequests = accountApiService.followRequests( + userid, false, Page.PageByMaxId( + maxId?.toLongOrNull(), sinceId?.toLongOrNull(), limit?.coerceIn(0, 80) ?: 40 + ) + + ) + + val httpHeader = followRequests.toHttpHeader( + { "${applicationConfig.url}/api/v1/follow_requests?max_id=$it" }, + { "${applicationConfig.url}/api/v1/follow_requests?min_id=$it" }, + ) + + if (httpHeader != null) { + return@runBlocking ResponseEntity.ok().header("Link", httpHeader).body(followRequests.asFlow()) + } + + ResponseEntity.ok(followRequests.asFlow()) } override suspend fun apiV1AccountsIdMutePost(id: String): ResponseEntity { diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountApiService.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountApiService.kt index a69e0474..3139576e 100644 --- a/src/main/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountApiService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountApiService.kt @@ -1,6 +1,8 @@ package dev.usbharu.hideout.mastodon.service.account import dev.usbharu.hideout.application.external.Transaction +import dev.usbharu.hideout.application.infrastructure.exposed.Page +import dev.usbharu.hideout.application.infrastructure.exposed.PaginationList import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository import dev.usbharu.hideout.core.service.media.MediaService import dev.usbharu.hideout.core.service.relationship.RelationshipService @@ -58,11 +60,18 @@ interface AccountApiService { withIgnore: Boolean ): List + suspend fun followRequests( + loginUser: Long, + withIgnore: Boolean, + pageByMaxId: Page.PageByMaxId + ): PaginationList + suspend fun acceptFollowRequest(loginUser: Long, target: Long): Relationship suspend fun rejectFollowRequest(loginUser: Long, target: Long): Relationship suspend fun mute(userid: Long, target: Long): Relationship suspend fun unmute(userid: Long, target: Long): Relationship suspend fun mutesAccount(userid: Long, maxId: Long?, sinceId: Long?, limit: Int): List + suspend fun mutesAccount(userid: Long, pageByMaxId: Page.PageByMaxId): PaginationList } @Service @@ -236,6 +245,23 @@ class AccountApiServiceImpl( return@transaction accountService.findByIds(actorIdList) } + override suspend fun followRequests( + loginUser: Long, + withIgnore: Boolean, + pageByMaxId: Page.PageByMaxId + ): PaginationList = transaction.transaction { + val request = + relationshipRepository.findByTargetIdAndFollowRequestAndIgnoreFollowRequest( + loginUser, + true, + withIgnore, + pageByMaxId + ) + val actorIds = request.map { it.actorId } + + return@transaction PaginationList(accountService.findByIds(actorIds), request.next, request.prev) + } + override suspend fun acceptFollowRequest(loginUser: Long, target: Long): Relationship = transaction.transaction { relationshipService.acceptFollowRequest(loginUser, target) @@ -267,6 +293,16 @@ class AccountApiServiceImpl( return accountService.findByIds(mutedAccounts.map { it.targetActorId }) } + override suspend fun mutesAccount(userid: Long, pageByMaxId: Page.PageByMaxId): PaginationList { + val mutedAccounts = relationshipRepository.findByActorIdAndMuting(userid, true, pageByMaxId) + + return PaginationList( + accountService.findByIds(mutedAccounts.map { it.targetActorId }), + mutedAccounts.next, + mutedAccounts.prev + ) + } + private fun from(account: Account): CredentialAccount { return CredentialAccount( id = account.id,