diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/mongorepository/MongoInternalTimelineObjectRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/mongorepository/MongoInternalTimelineObjectRepository.kt index 6f77882c..f289ec5c 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/mongorepository/MongoInternalTimelineObjectRepository.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/mongorepository/MongoInternalTimelineObjectRepository.kt @@ -19,6 +19,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.toList +import org.springframework.data.annotation.Id import org.springframework.data.domain.Sort import org.springframework.data.mongodb.core.MongoTemplate import org.springframework.data.mongodb.core.mapping.Document @@ -61,6 +62,22 @@ class MongoInternalTimelineObjectRepository( springDataMongoTimelineObjectRepository.deleteByTimelineId(timelineId.value) } + override suspend fun findByTimelineIdAndPostIdGT(timelineId: TimelineId, postId: PostId): TimelineObject? { + return springDataMongoTimelineObjectRepository.findFirstByTimelineIdAndPostIdGreaterThanOrderByIdAsc( + timelineId.value, + postId.id + ) + ?.toTimelineObject() + } + + override suspend fun findByTimelineIdAndPostIdLT(timelineId: TimelineId, postId: PostId): TimelineObject? { + return springDataMongoTimelineObjectRepository.findFirstByTimelineIdAndPostIdLessThanOrderByIdDesc( + timelineId.value, + postId.id + ) + ?.toTimelineObject() + } + override suspend fun findByTimelineId( timelineId: TimelineId, internalTimelineObjectOption: InternalTimelineObjectOption?, @@ -70,12 +87,12 @@ class MongoInternalTimelineObjectRepository( if (page?.minId != null) { query.with(Sort.by(Sort.Direction.ASC, "postCreatedAt")) - page.minId?.let { query.addCriteria(Criteria.where("id").gt(it)) } - page.maxId?.let { query.addCriteria(Criteria.where("id").lt(it)) } + page.minId?.let { query.addCriteria(Criteria.where("postId").gt(it)) } + page.maxId?.let { query.addCriteria(Criteria.where("postId").lt(it)) } } else { query.with(Sort.by(Sort.Direction.DESC, "postCreatedAt")) - page?.sinceId?.let { query.addCriteria(Criteria.where("id").gt(it)) } - page?.maxId?.let { query.addCriteria(Criteria.where("id").lt(it)) } + page?.sinceId?.let { query.addCriteria(Criteria.where("postId").gt(it)) } + page?.maxId?.let { query.addCriteria(Criteria.where("postId").lt(it)) } } page?.limit?.let { query.limit(it) } @@ -83,16 +100,23 @@ class MongoInternalTimelineObjectRepository( val timelineObjects = mongoTemplate.find(query, SpringDataMongoTimelineObject::class.java).map { it.toTimelineObject() } + val objectList = if (page?.minId != null) { + timelineObjects.reversed() + } else { + timelineObjects + } + return PaginationList( - timelineObjects, - timelineObjects.lastOrNull()?.postId, - timelineObjects.firstOrNull()?.postId + objectList, + objectList.lastOrNull()?.postId, + objectList.firstOrNull()?.postId ) } } @Document data class SpringDataMongoTimelineObject( + @Id val id: Long, val userDetailId: Long, val timelineId: Long, @@ -194,4 +218,14 @@ interface SpringDataMongoTimelineObjectRepository : CoroutineCrudRepository + + suspend fun findFirstByTimelineIdAndPostIdGreaterThanOrderByIdAsc( + timelineId: Long, + postId: Long + ): SpringDataMongoTimelineObject? + + suspend fun findFirstByTimelineIdAndPostIdLessThanOrderByIdDesc( + timelineId: Long, + postId: Long + ): SpringDataMongoTimelineObject? } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/timeline/AbstractTimelineStore.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/timeline/AbstractTimelineStore.kt index 149ffb26..f0be54f8 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/timeline/AbstractTimelineStore.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/timeline/AbstractTimelineStore.kt @@ -205,6 +205,11 @@ abstract class AbstractTimelineStore(private val idGenerateService: IdGenerateSe removeTimelineObject(timeline.id) } + protected abstract suspend fun getNextPaging( + timelineId: TimelineId, + page: Page? + ): PaginationList + override suspend fun readTimeline( timeline: Timeline, option: ReadTimelineOption?, @@ -212,6 +217,9 @@ abstract class AbstractTimelineStore(private val idGenerateService: IdGenerateSe principal: Principal ): PaginationList { val timelineObjectList = getTimelineObject(timeline.id, option, page) + if (timelineObjectList.isEmpty()) { + return getNextPaging(timeline.id, page) + } val lastUpdatedAt = timelineObjectList.minBy { it.lastUpdatedAt }.lastUpdatedAt val newerFilters = getNewerFilters(timeline.userDetailId, lastUpdatedAt) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/timeline/DefaultTimelineStore.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/timeline/DefaultTimelineStore.kt index 974ad7ec..182ed506 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/timeline/DefaultTimelineStore.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/timeline/DefaultTimelineStore.kt @@ -18,6 +18,7 @@ import dev.usbharu.hideout.core.domain.model.post.Visibility import dev.usbharu.hideout.core.domain.model.support.page.Page import dev.usbharu.hideout.core.domain.model.support.page.PaginationList import dev.usbharu.hideout.core.domain.model.support.principal.Principal +import dev.usbharu.hideout.core.domain.model.support.timelineobjectdetail.TimelineObjectDetail import dev.usbharu.hideout.core.domain.model.timeline.Timeline import dev.usbharu.hideout.core.domain.model.timeline.TimelineId import dev.usbharu.hideout.core.domain.model.timeline.TimelineRepository @@ -135,6 +136,30 @@ open class DefaultTimelineStore( ) } + override suspend fun getNextPaging( + timelineId: TimelineId, + page: Page? + ): PaginationList { + if (page?.maxId != null) { + return PaginationList( + emptyList(), + null, + internalTimelineObjectRepository.findByTimelineIdAndPostIdLT(timelineId, PostId(page.maxId!!))?.postId + ?: PostId(0) + ) + } else if (page?.minId != null) { + return PaginationList( + emptyList(), + internalTimelineObjectRepository.findByTimelineIdAndPostIdGT(timelineId, PostId(page.minId!!))?.postId + ?: PostId(Long.MAX_VALUE), + null + ) + } + return PaginationList(emptyList(), page?.maxId?.let { PostId(it) }, page?.minId?.let { PostId(it) }) + + + } + override suspend fun getActors(actorIds: List): Map { return actorRepository.findAllById(actorIds).associateBy { it.id } } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/timeline/InternalTimelineObjectRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/timeline/InternalTimelineObjectRepository.kt index 3f26cbab..c0cf4ac6 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/timeline/InternalTimelineObjectRepository.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/timeline/InternalTimelineObjectRepository.kt @@ -19,6 +19,17 @@ interface InternalTimelineObjectRepository { suspend fun deleteByTimelineIdAndActorId(timelineId: TimelineId, actorId: ActorId) suspend fun deleteByTimelineId(timelineId: TimelineId) + + /** + * 指定したTimelineIdより大きく、近いものを返す + */ + suspend fun findByTimelineIdAndPostIdGT(timelineId: TimelineId, postId: PostId): TimelineObject? + + /** + * 指定したTimelineIdより小さく、近いものを返す + */ + suspend fun findByTimelineIdAndPostIdLT(timelineId: TimelineId, postId: PostId): TimelineObject? + suspend fun findByTimelineId( timelineId: TimelineId, internalTimelineObjectOption: InternalTimelineObjectOption? = null, diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/web/timeline/TimelineController.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/web/timeline/TimelineController.kt index ebb3b86f..8b6da2ad 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/web/timeline/TimelineController.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/web/timeline/TimelineController.kt @@ -20,7 +20,12 @@ class TimelineController( private val transaction: Transaction ) { @GetMapping("/home") - suspend fun homeTimeline(model: Model, @RequestParam sinceId: String?, @RequestParam maxId: String?): String { + suspend fun homeTimeline( + model: Model, + @RequestParam sinceId: String?, + @RequestParam maxId: String?, + @RequestParam minId: String? + ): String { val principal = springSecurityFormLoginPrincipalContextHolder.getPrincipal() val userDetail = transaction.transaction { userDetailRepository.findByActorId(principal.actorId.id) @@ -36,7 +41,9 @@ class TimelineController( remoteOnly = false, page = Page.of( maxId = maxId?.toLongOrNull(), - sinceId = sinceId?.toLongOrNull() + sinceId = sinceId?.toLongOrNull(), + minId = minId?.toLongOrNull(), + limit = 20 ) ), principal ) diff --git a/hideout-core/src/main/resources/log4j2.xml b/hideout-core/src/main/resources/log4j2.xml index 3a225230..978d338a 100644 --- a/hideout-core/src/main/resources/log4j2.xml +++ b/hideout-core/src/main/resources/log4j2.xml @@ -6,12 +6,14 @@ - + - + + + \ No newline at end of file diff --git a/hideout-core/src/main/resources/messages/hideout-web-messages.properties b/hideout-core/src/main/resources/messages/hideout-web-messages.properties index 6bc6f623..c22c2992 100644 --- a/hideout-core/src/main/resources/messages/hideout-web-messages.properties +++ b/hideout-core/src/main/resources/messages/hideout-web-messages.properties @@ -1,6 +1,8 @@ common.audio=\u30AA\u30FC\u30C7\u30A3\u30AA common.audio-download-link=\u97F3\u58F0\u30D5\u30A1\u30A4\u30EB\u3092\u30C0\u30A6\u30F3\u30ED\u30FC\u30C9 +common.empty=\u8868\u793A\u3059\u308B\u3082\u306E\u304C\u3042\u308A\u307E\u305B\u3093 common.media-original-link=\u30AA\u30EA\u30B8\u30CA\u30EB +common.paging-load=\u3082\u3063\u3068\u898B\u308B common.thumbnail=\u30B5\u30E0\u30CD\u30A4\u30EB common.unknwon-file-type=\u4E0D\u660E\u306A\u30D5\u30A1\u30A4\u30EB\u5F62\u5F0F common.video=\u52D5\u753B diff --git a/hideout-core/src/main/resources/messages/hideout-web-messages_en_US.properties b/hideout-core/src/main/resources/messages/hideout-web-messages_en_US.properties index 1f1223cb..f32efecd 100644 --- a/hideout-core/src/main/resources/messages/hideout-web-messages_en_US.properties +++ b/hideout-core/src/main/resources/messages/hideout-web-messages_en_US.properties @@ -1,6 +1,8 @@ common.audio=Audio common.audio-download-link=Download the audio. +common.empty=Empty common.media-original-link=original +common.paging-load=Show more common.thumbnail=thumbnail common.unknwon-file-type=Unknown filetype common.video=Video diff --git a/hideout-core/src/main/resources/messages/hideout-web-messages_ja_JP.properties b/hideout-core/src/main/resources/messages/hideout-web-messages_ja_JP.properties index 6bc6f623..c22c2992 100644 --- a/hideout-core/src/main/resources/messages/hideout-web-messages_ja_JP.properties +++ b/hideout-core/src/main/resources/messages/hideout-web-messages_ja_JP.properties @@ -1,6 +1,8 @@ common.audio=\u30AA\u30FC\u30C7\u30A3\u30AA common.audio-download-link=\u97F3\u58F0\u30D5\u30A1\u30A4\u30EB\u3092\u30C0\u30A6\u30F3\u30ED\u30FC\u30C9 +common.empty=\u8868\u793A\u3059\u308B\u3082\u306E\u304C\u3042\u308A\u307E\u305B\u3093 common.media-original-link=\u30AA\u30EA\u30B8\u30CA\u30EB +common.paging-load=\u3082\u3063\u3068\u898B\u308B common.thumbnail=\u30B5\u30E0\u30CD\u30A4\u30EB common.unknwon-file-type=\u4E0D\u660E\u306A\u30D5\u30A1\u30A4\u30EB\u5F62\u5F0F common.video=\u52D5\u753B diff --git a/hideout-core/src/main/resources/templates/fragments-timeline.html b/hideout-core/src/main/resources/templates/fragments-timeline.html index f1f2b3cb..f08d6715 100644 --- a/hideout-core/src/main/resources/templates/fragments-timeline.html +++ b/hideout-core/src/main/resources/templates/fragments-timeline.html @@ -7,14 +7,16 @@ -
- + +
- + +
-
- +