diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/timeline/ExposedGenerateTimelineService.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/timeline/ExposedGenerateTimelineService.kt index 7d83a9ac..d4f8e1af 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/service/timeline/ExposedGenerateTimelineService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/timeline/ExposedGenerateTimelineService.kt @@ -1,5 +1,8 @@ package dev.usbharu.hideout.core.service.timeline +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.Timelines import dev.usbharu.hideout.domain.mastodon.model.generated.Status import dev.usbharu.hideout.mastodon.interfaces.api.status.StatusQuery @@ -52,4 +55,40 @@ class ExposedGenerateTimelineService(private val statusQueryService: StatusQuery return statusQueryService.findByPostIdsWithMediaIds(statusQueries) } + + override suspend fun getTimeline( + forUserId: Long?, + localOnly: Boolean, + mediaOnly: Boolean, + page: Page + ): PaginationList { + val query = Timelines.selectAll() + + if (forUserId != null) { + query.andWhere { Timelines.userId eq forUserId } + } + if (localOnly) { + query.andWhere { Timelines.isLocal eq true } + } + query.pagination(page, Timelines.id) + val result = query + .orderBy(Timelines.createdAt, SortOrder.DESC) + + val statusQueries = result.map { + StatusQuery( + it[Timelines.postId], + it[Timelines.replyId], + it[Timelines.repostId], + it[Timelines.mediaIds].split(",").mapNotNull { s -> s.toLongOrNull() }, + it[Timelines.emojiIds].split(",").mapNotNull { s -> s.toLongOrNull() } + ) + } + + val findByPostIdsWithMediaIds = statusQueryService.findByPostIdsWithMediaIds(statusQueries) + return PaginationList( + findByPostIdsWithMediaIds, + findByPostIdsWithMediaIds.lastOrNull()?.id?.toLongOrNull(), + findByPostIdsWithMediaIds.firstOrNull()?.id?.toLongOrNull() + ) + } } diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/timeline/GenerateTimelineService.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/timeline/GenerateTimelineService.kt index 7684a97f..4e21c285 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/service/timeline/GenerateTimelineService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/timeline/GenerateTimelineService.kt @@ -1,5 +1,7 @@ package dev.usbharu.hideout.core.service.timeline +import dev.usbharu.hideout.application.infrastructure.exposed.Page +import dev.usbharu.hideout.application.infrastructure.exposed.PaginationList import dev.usbharu.hideout.domain.mastodon.model.generated.Status import org.springframework.stereotype.Service @@ -15,4 +17,11 @@ interface GenerateTimelineService { sinceId: Long? = null, limit: Int = 20 ): List + + suspend fun getTimeline( + forUserId: Long? = null, + localOnly: Boolean = false, + mediaOnly: Boolean = false, + page: Page + ): PaginationList } diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/timeline/MongoGenerateTimelineService.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/timeline/MongoGenerateTimelineService.kt index 541f24c3..7cc7938f 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/service/timeline/MongoGenerateTimelineService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/timeline/MongoGenerateTimelineService.kt @@ -1,5 +1,7 @@ package dev.usbharu.hideout.core.service.timeline +import dev.usbharu.hideout.application.infrastructure.exposed.Page +import dev.usbharu.hideout.application.infrastructure.exposed.PaginationList import dev.usbharu.hideout.core.domain.model.timeline.Timeline import dev.usbharu.hideout.domain.mastodon.model.generated.Status import dev.usbharu.hideout.mastodon.interfaces.api.status.StatusQuery @@ -63,4 +65,54 @@ class MongoGenerateTimelineService( } ) } + + override suspend fun getTimeline( + forUserId: Long?, + localOnly: Boolean, + mediaOnly: Boolean, + page: Page + ): PaginationList { + val query = Query() + + if (forUserId != null) { + val criteria = Criteria.where("userId").`is`(forUserId) + query.addCriteria(criteria) + } + if (localOnly) { + val criteria = Criteria.where("isLocal").`is`(true) + query.addCriteria(criteria) + } + + if (page.minId != null) { + page.minId?.let { query.addCriteria(Criteria.where("id").gt(it)) } + page.maxId?.let { query.addCriteria(Criteria.where("id").lt(it)) } + } else { + query.with(Sort.by(Sort.Direction.DESC, "createdAt")) + page.sinceId?.let { query.addCriteria(Criteria.where("id").gt(it)) } + page.maxId?.let { query.addCriteria(Criteria.where("id").lt(it)) } + } + + page.limit?.let { query.limit(it) } + + query.with(Sort.by(Sort.Direction.DESC, "createdAt")) + + val timelines = mongoTemplate.find(query, Timeline::class.java) + + val statuses = statusQueryService.findByPostIdsWithMediaIds( + timelines.map { + StatusQuery( + it.postId, + it.replyId, + it.repostId, + it.mediaIds, + it.emojiIds + ) + } + ) + return PaginationList( + statuses, + statuses.lastOrNull()?.id?.toLongOrNull(), + statuses.firstOrNull()?.id?.toLongOrNull() + ) + } } diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/timeline/MastodonTimelineApiController.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/timeline/MastodonTimelineApiController.kt index b7ddddae..aaec0252 100644 --- a/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/timeline/MastodonTimelineApiController.kt +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/timeline/MastodonTimelineApiController.kt @@ -1,5 +1,8 @@ package dev.usbharu.hideout.mastodon.interfaces.api.timeline +import dev.usbharu.hideout.application.config.ApplicationConfig +import dev.usbharu.hideout.application.infrastructure.exposed.Page +import dev.usbharu.hideout.application.infrastructure.exposed.toHttpHeader import dev.usbharu.hideout.controller.mastodon.generated.TimelineApi import dev.usbharu.hideout.core.infrastructure.springframework.security.LoginUserContextHolder import dev.usbharu.hideout.domain.mastodon.model.generated.Status @@ -14,7 +17,8 @@ import org.springframework.stereotype.Controller @Controller class MastodonTimelineApiController( private val timelineApiService: TimelineApiService, - private val loginUserContextHolder: LoginUserContextHolder + private val loginUserContextHolder: LoginUserContextHolder, + private val applicationConfig: ApplicationConfig, ) : TimelineApi { override fun apiV1TimelinesHomeGet( maxId: String?, @@ -24,10 +28,12 @@ class MastodonTimelineApiController( ): ResponseEntity> = runBlocking { val homeTimeline = timelineApiService.homeTimeline( userId = loginUserContextHolder.getLoginUserId(), - maxId = maxId?.toLongOrNull(), - minId = minId?.toLongOrNull(), - sinceId = sinceId?.toLongOrNull(), - limit = limit ?: 20 + page = Page.of( + maxId = maxId?.toLongOrNull(), + minId = minId?.toLongOrNull(), + sinceId = sinceId?.toLongOrNull(), + limit = limit?.coerceIn(0, 80) ?: 40 + ) ) ResponseEntity(homeTimeline.asFlow(), HttpStatus.OK) } @@ -45,11 +51,21 @@ class MastodonTimelineApiController( localOnly = local ?: false, remoteOnly = remote ?: false, mediaOnly = onlyMedia ?: false, - maxId = maxId?.toLongOrNull(), - minId = minId?.toLongOrNull(), - sinceId = sinceId?.toLongOrNull(), - limit = limit ?: 20 + page = Page.of( + maxId = maxId?.toLongOrNull(), + minId = minId?.toLongOrNull(), + sinceId = sinceId?.toLongOrNull(), + limit = limit?.coerceIn(0, 80) ?: 40 + ) ) - ResponseEntity(publicTimeline.asFlow(), HttpStatus.OK) + + val httpHeader = publicTimeline.toHttpHeader( + { "${applicationConfig.url}/api/v1/public?max_id=$it" }, + { "${applicationConfig.url}/api/v1/public?min_id=$it" } + ) ?: return@runBlocking ResponseEntity( + publicTimeline.asFlow(), + HttpStatus.OK + ) + ResponseEntity.ok().header("Link", httpHeader).body(publicTimeline.asFlow()) } } diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/service/timeline/TimelineApiService.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/service/timeline/TimelineApiService.kt index 29fb0e7b..9d11b8ac 100644 --- a/src/main/kotlin/dev/usbharu/hideout/mastodon/service/timeline/TimelineApiService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/service/timeline/TimelineApiService.kt @@ -1,6 +1,8 @@ package dev.usbharu.hideout.mastodon.service.timeline 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.service.timeline.GenerateTimelineService import dev.usbharu.hideout.domain.mastodon.model.generated.Status import org.springframework.stereotype.Service @@ -17,6 +19,13 @@ interface TimelineApiService { limit: Int = 20 ): List + suspend fun publicTimeline( + localOnly: Boolean = false, + remoteOnly: Boolean = false, + mediaOnly: Boolean = false, + page: Page + ): PaginationList + suspend fun homeTimeline( userId: Long, maxId: Long?, @@ -24,6 +33,11 @@ interface TimelineApiService { sinceId: Long?, limit: Int = 20 ): List + + suspend fun homeTimeline( + userId: Long, + page: Page + ): PaginationList } @Service @@ -51,6 +65,13 @@ class TimelineApiServiceImpl( ) } + override suspend fun publicTimeline( + localOnly: Boolean, + remoteOnly: Boolean, + mediaOnly: Boolean, + page: Page + ): PaginationList = generateTimelineService.getTimeline(forUserId = 0, localOnly, mediaOnly, page) + override suspend fun homeTimeline( userId: Long, maxId: Long?, @@ -66,4 +87,7 @@ class TimelineApiServiceImpl( limit = limit ) } + + override suspend fun homeTimeline(userId: Long, page: Page): PaginationList = + generateTimelineService.getTimeline(forUserId = userId, page = page) }