mirror of https://github.com/usbharu/Hideout.git
feat: ページングが動くように
This commit is contained in:
parent
d53cdca9e5
commit
11e2eb1e10
|
@ -19,6 +19,7 @@ import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.toList
|
import kotlinx.coroutines.flow.toList
|
||||||
|
import org.springframework.data.annotation.Id
|
||||||
import org.springframework.data.domain.Sort
|
import org.springframework.data.domain.Sort
|
||||||
import org.springframework.data.mongodb.core.MongoTemplate
|
import org.springframework.data.mongodb.core.MongoTemplate
|
||||||
import org.springframework.data.mongodb.core.mapping.Document
|
import org.springframework.data.mongodb.core.mapping.Document
|
||||||
|
@ -61,6 +62,22 @@ class MongoInternalTimelineObjectRepository(
|
||||||
springDataMongoTimelineObjectRepository.deleteByTimelineId(timelineId.value)
|
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(
|
override suspend fun findByTimelineId(
|
||||||
timelineId: TimelineId,
|
timelineId: TimelineId,
|
||||||
internalTimelineObjectOption: InternalTimelineObjectOption?,
|
internalTimelineObjectOption: InternalTimelineObjectOption?,
|
||||||
|
@ -70,12 +87,12 @@ class MongoInternalTimelineObjectRepository(
|
||||||
|
|
||||||
if (page?.minId != null) {
|
if (page?.minId != null) {
|
||||||
query.with(Sort.by(Sort.Direction.ASC, "postCreatedAt"))
|
query.with(Sort.by(Sort.Direction.ASC, "postCreatedAt"))
|
||||||
page.minId?.let { query.addCriteria(Criteria.where("id").gt(it)) }
|
page.minId?.let { query.addCriteria(Criteria.where("postId").gt(it)) }
|
||||||
page.maxId?.let { query.addCriteria(Criteria.where("id").lt(it)) }
|
page.maxId?.let { query.addCriteria(Criteria.where("postId").lt(it)) }
|
||||||
} else {
|
} else {
|
||||||
query.with(Sort.by(Sort.Direction.DESC, "postCreatedAt"))
|
query.with(Sort.by(Sort.Direction.DESC, "postCreatedAt"))
|
||||||
page?.sinceId?.let { query.addCriteria(Criteria.where("id").gt(it)) }
|
page?.sinceId?.let { query.addCriteria(Criteria.where("postId").gt(it)) }
|
||||||
page?.maxId?.let { query.addCriteria(Criteria.where("id").lt(it)) }
|
page?.maxId?.let { query.addCriteria(Criteria.where("postId").lt(it)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
page?.limit?.let { query.limit(it) }
|
page?.limit?.let { query.limit(it) }
|
||||||
|
@ -83,16 +100,23 @@ class MongoInternalTimelineObjectRepository(
|
||||||
val timelineObjects =
|
val timelineObjects =
|
||||||
mongoTemplate.find(query, SpringDataMongoTimelineObject::class.java).map { it.toTimelineObject() }
|
mongoTemplate.find(query, SpringDataMongoTimelineObject::class.java).map { it.toTimelineObject() }
|
||||||
|
|
||||||
|
val objectList = if (page?.minId != null) {
|
||||||
|
timelineObjects.reversed()
|
||||||
|
} else {
|
||||||
|
timelineObjects
|
||||||
|
}
|
||||||
|
|
||||||
return PaginationList(
|
return PaginationList(
|
||||||
timelineObjects,
|
objectList,
|
||||||
timelineObjects.lastOrNull()?.postId,
|
objectList.lastOrNull()?.postId,
|
||||||
timelineObjects.firstOrNull()?.postId
|
objectList.firstOrNull()?.postId
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Document
|
@Document
|
||||||
data class SpringDataMongoTimelineObject(
|
data class SpringDataMongoTimelineObject(
|
||||||
|
@Id
|
||||||
val id: Long,
|
val id: Long,
|
||||||
val userDetailId: Long,
|
val userDetailId: Long,
|
||||||
val timelineId: Long,
|
val timelineId: Long,
|
||||||
|
@ -194,4 +218,14 @@ interface SpringDataMongoTimelineObjectRepository : CoroutineCrudRepository<Spri
|
||||||
suspend fun deleteByTimelineId(timelineId: Long)
|
suspend fun deleteByTimelineId(timelineId: Long)
|
||||||
|
|
||||||
suspend fun findByTimelineId(timelineId: TimelineId): Flow<SpringDataMongoTimelineObject>
|
suspend fun findByTimelineId(timelineId: TimelineId): Flow<SpringDataMongoTimelineObject>
|
||||||
|
|
||||||
|
suspend fun findFirstByTimelineIdAndPostIdGreaterThanOrderByIdAsc(
|
||||||
|
timelineId: Long,
|
||||||
|
postId: Long
|
||||||
|
): SpringDataMongoTimelineObject?
|
||||||
|
|
||||||
|
suspend fun findFirstByTimelineIdAndPostIdLessThanOrderByIdDesc(
|
||||||
|
timelineId: Long,
|
||||||
|
postId: Long
|
||||||
|
): SpringDataMongoTimelineObject?
|
||||||
}
|
}
|
||||||
|
|
|
@ -205,6 +205,11 @@ abstract class AbstractTimelineStore(private val idGenerateService: IdGenerateSe
|
||||||
removeTimelineObject(timeline.id)
|
removeTimelineObject(timeline.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected abstract suspend fun getNextPaging(
|
||||||
|
timelineId: TimelineId,
|
||||||
|
page: Page?
|
||||||
|
): PaginationList<TimelineObjectDetail, PostId>
|
||||||
|
|
||||||
override suspend fun readTimeline(
|
override suspend fun readTimeline(
|
||||||
timeline: Timeline,
|
timeline: Timeline,
|
||||||
option: ReadTimelineOption?,
|
option: ReadTimelineOption?,
|
||||||
|
@ -212,6 +217,9 @@ abstract class AbstractTimelineStore(private val idGenerateService: IdGenerateSe
|
||||||
principal: Principal
|
principal: Principal
|
||||||
): PaginationList<TimelineObjectDetail, PostId> {
|
): PaginationList<TimelineObjectDetail, PostId> {
|
||||||
val timelineObjectList = getTimelineObject(timeline.id, option, page)
|
val timelineObjectList = getTimelineObject(timeline.id, option, page)
|
||||||
|
if (timelineObjectList.isEmpty()) {
|
||||||
|
return getNextPaging(timeline.id, page)
|
||||||
|
}
|
||||||
val lastUpdatedAt = timelineObjectList.minBy { it.lastUpdatedAt }.lastUpdatedAt
|
val lastUpdatedAt = timelineObjectList.minBy { it.lastUpdatedAt }.lastUpdatedAt
|
||||||
|
|
||||||
val newerFilters = getNewerFilters(timeline.userDetailId, lastUpdatedAt)
|
val newerFilters = getNewerFilters(timeline.userDetailId, lastUpdatedAt)
|
||||||
|
|
|
@ -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.Page
|
||||||
import dev.usbharu.hideout.core.domain.model.support.page.PaginationList
|
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.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.Timeline
|
||||||
import dev.usbharu.hideout.core.domain.model.timeline.TimelineId
|
import dev.usbharu.hideout.core.domain.model.timeline.TimelineId
|
||||||
import dev.usbharu.hideout.core.domain.model.timeline.TimelineRepository
|
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<TimelineObjectDetail, PostId> {
|
||||||
|
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<ActorId>): Map<ActorId, Actor> {
|
override suspend fun getActors(actorIds: List<ActorId>): Map<ActorId, Actor> {
|
||||||
return actorRepository.findAllById(actorIds).associateBy { it.id }
|
return actorRepository.findAllById(actorIds).associateBy { it.id }
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,17 @@ interface InternalTimelineObjectRepository {
|
||||||
suspend fun deleteByTimelineIdAndActorId(timelineId: TimelineId, actorId: ActorId)
|
suspend fun deleteByTimelineIdAndActorId(timelineId: TimelineId, actorId: ActorId)
|
||||||
|
|
||||||
suspend fun deleteByTimelineId(timelineId: TimelineId)
|
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(
|
suspend fun findByTimelineId(
|
||||||
timelineId: TimelineId,
|
timelineId: TimelineId,
|
||||||
internalTimelineObjectOption: InternalTimelineObjectOption? = null,
|
internalTimelineObjectOption: InternalTimelineObjectOption? = null,
|
||||||
|
|
|
@ -20,7 +20,12 @@ class TimelineController(
|
||||||
private val transaction: Transaction
|
private val transaction: Transaction
|
||||||
) {
|
) {
|
||||||
@GetMapping("/home")
|
@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 principal = springSecurityFormLoginPrincipalContextHolder.getPrincipal()
|
||||||
val userDetail = transaction.transaction {
|
val userDetail = transaction.transaction {
|
||||||
userDetailRepository.findByActorId(principal.actorId.id)
|
userDetailRepository.findByActorId(principal.actorId.id)
|
||||||
|
@ -36,7 +41,9 @@ class TimelineController(
|
||||||
remoteOnly = false,
|
remoteOnly = false,
|
||||||
page = Page.of(
|
page = Page.of(
|
||||||
maxId = maxId?.toLongOrNull(),
|
maxId = maxId?.toLongOrNull(),
|
||||||
sinceId = sinceId?.toLongOrNull()
|
sinceId = sinceId?.toLongOrNull(),
|
||||||
|
minId = minId?.toLongOrNull(),
|
||||||
|
limit = 20
|
||||||
)
|
)
|
||||||
), principal
|
), principal
|
||||||
)
|
)
|
||||||
|
|
|
@ -6,12 +6,14 @@
|
||||||
</Console>
|
</Console>
|
||||||
</Appenders>
|
</Appenders>
|
||||||
<Loggers>
|
<Loggers>
|
||||||
<Root level="INFO">
|
<Root level="DEBUG">
|
||||||
<AppenderRef ref="Console"/>
|
<AppenderRef ref="Console"/>
|
||||||
</Root>
|
</Root>
|
||||||
<Logger name="dev.usbharu.owl.broker.service.QueuedTaskAssignerImpl" level="TRACE"/>
|
<Logger name="dev.usbharu.owl.broker.service.QueuedTaskAssignerImpl" level="TRACE"/>
|
||||||
<Logger name="org.mongodb.driver.cluster" level="WARN"/>
|
<!-- <Logger name="org.mongodb.driver.cluster" level=""/>-->
|
||||||
<Logger name="org.apache.tomcat.util.net.NioEndpoint" level="INFO"/>
|
<Logger name="org.apache.tomcat.util.net.NioEndpoint" level="INFO"/>
|
||||||
<Logger name="Exposed" level="DEBUG"/>
|
<Logger name="Exposed" level="DEBUG"/>
|
||||||
|
<Logger name="sun.rmi" level="INFO"/>
|
||||||
|
<Logger name="javax.management.remote.rmi" level="INFO"/>
|
||||||
</Loggers>
|
</Loggers>
|
||||||
</Configuration>
|
</Configuration>
|
|
@ -1,6 +1,8 @@
|
||||||
common.audio=\u30AA\u30FC\u30C7\u30A3\u30AA
|
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.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.media-original-link=\u30AA\u30EA\u30B8\u30CA\u30EB
|
||||||
|
common.paging-load=\u3082\u3063\u3068\u898B\u308B
|
||||||
common.thumbnail=\u30B5\u30E0\u30CD\u30A4\u30EB
|
common.thumbnail=\u30B5\u30E0\u30CD\u30A4\u30EB
|
||||||
common.unknwon-file-type=\u4E0D\u660E\u306A\u30D5\u30A1\u30A4\u30EB\u5F62\u5F0F
|
common.unknwon-file-type=\u4E0D\u660E\u306A\u30D5\u30A1\u30A4\u30EB\u5F62\u5F0F
|
||||||
common.video=\u52D5\u753B
|
common.video=\u52D5\u753B
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
common.audio=Audio
|
common.audio=Audio
|
||||||
common.audio-download-link=Download the audio.
|
common.audio-download-link=Download the audio.
|
||||||
|
common.empty=Empty
|
||||||
common.media-original-link=original
|
common.media-original-link=original
|
||||||
|
common.paging-load=Show more
|
||||||
common.thumbnail=thumbnail
|
common.thumbnail=thumbnail
|
||||||
common.unknwon-file-type=Unknown filetype
|
common.unknwon-file-type=Unknown filetype
|
||||||
common.video=Video
|
common.video=Video
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
common.audio=\u30AA\u30FC\u30C7\u30A3\u30AA
|
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.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.media-original-link=\u30AA\u30EA\u30B8\u30CA\u30EB
|
||||||
|
common.paging-load=\u3082\u3063\u3068\u898B\u308B
|
||||||
common.thumbnail=\u30B5\u30E0\u30CD\u30A4\u30EB
|
common.thumbnail=\u30B5\u30E0\u30CD\u30A4\u30EB
|
||||||
common.unknwon-file-type=\u4E0D\u660E\u306A\u30D5\u30A1\u30A4\u30EB\u5F62\u5F0F
|
common.unknwon-file-type=\u4E0D\u660E\u306A\u30D5\u30A1\u30A4\u30EB\u5F62\u5F0F
|
||||||
common.video=\u52D5\u753B
|
common.video=\u52D5\u753B
|
||||||
|
|
|
@ -7,14 +7,16 @@
|
||||||
<body>
|
<body>
|
||||||
<th:block th:fragment="simple-timline(timelineObject,href)">
|
<th:block th:fragment="simple-timline(timelineObject,href)">
|
||||||
<!--/*@thymesVar id="timelineObject" type="dev.usbharu.hideout.core.domain.model.support.page.PaginationList<dev.usbharu.hideout.core.application.post.PostDetail,dev.usbharu.hideout.core.domain.model.post.PostId>"*/-->
|
<!--/*@thymesVar id="timelineObject" type="dev.usbharu.hideout.core.domain.model.support.page.PaginationList<dev.usbharu.hideout.core.application.post.PostDetail,dev.usbharu.hideout.core.domain.model.post.PostId>"*/-->
|
||||||
<div th:if="${timelineObject.prev == null}">
|
<div th:if="${timelineObject.prev != null}">
|
||||||
<a th:href="${href + '/?maxId=' + timelineObject.prev}"></a>
|
<a th:href="${href + '?minId=' + timelineObject.prev.id}" th:text="#{common.paging-load}">Show more</a>
|
||||||
</div>
|
</div>
|
||||||
|
<div th:if="${timelineObject.isEmpty()}" th:text="#{common.empty}"></div>
|
||||||
<div th:each="postDetail : ${timelineObject}">
|
<div th:each="postDetail : ${timelineObject}">
|
||||||
<th:block th:replace="fragments-post :: single-simple-post(${postDetail})"></th:block>
|
<th:block th:replace="~{fragments-post :: single-simple-post(${postDetail})}"></th:block>
|
||||||
|
<th:block th:replace="~{fragments-post :: single-post-controller(${postDetail})}"></th:block>
|
||||||
</div>
|
</div>
|
||||||
<div th:if="${timelineObject.next == null}">
|
<div th:if="${timelineObject.next != null}">
|
||||||
<a th:href="${href + '/?sinceId=' + timelineObject.next}"></a>
|
<a th:href="${href + '?maxId=' + timelineObject.next.id}" th:text="#{common.paging-load}">Show more</a>
|
||||||
</div>
|
</div>
|
||||||
</th:block>
|
</th:block>
|
||||||
</body>
|
</body>
|
||||||
|
|
Loading…
Reference in New Issue