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 2e66bbbd..c6dec873 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 @@ -25,6 +25,7 @@ import org.springframework.data.mongodb.core.MongoTemplate import org.springframework.data.mongodb.core.mapping.Document import org.springframework.data.mongodb.core.query.Criteria import org.springframework.data.mongodb.core.query.Query +import org.springframework.data.mongodb.core.query.isEqualTo import org.springframework.data.repository.kotlin.CoroutineCrudRepository import org.springframework.stereotype.Repository import java.time.Instant @@ -84,6 +85,8 @@ class MongoInternalTimelineObjectRepository( ): PaginationList { val query = Query() + query.addCriteria(Criteria.where("timelineId").isEqualTo(timelineId.value)) + if (page?.minId != null) { query.with(Sort.by(Sort.Direction.ASC, "postCreatedAt")) page.minId?.let { query.addCriteria(Criteria.where("postId").gt(it)) } diff --git a/hideout-core/src/main/resources/templates/index.html b/hideout-core/src/main/resources/templates/index.html index 06321a91..f739ba1a 100644 --- a/hideout-core/src/main/resources/templates/index.html +++ b/hideout-core/src/main/resources/templates/index.html @@ -14,8 +14,8 @@ - + +No Script + \ No newline at end of file diff --git a/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/timeline/MastodonReadTimeline.kt b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/timeline/MastodonReadTimeline.kt new file mode 100644 index 00000000..4d76bda5 --- /dev/null +++ b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/timeline/MastodonReadTimeline.kt @@ -0,0 +1,13 @@ +package dev.usbharu.hideout.mastodon.application.timeline + +import dev.usbharu.hideout.core.domain.model.support.page.Page + +class MastodonReadTimeline( + val timelineId: Long, + val mediaOnly: Boolean, + val localOnly: Boolean, + val remoteOnly: Boolean, + val page: Page +) { + +} diff --git a/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/timeline/MastodonReadTimelineApplicationService.kt b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/timeline/MastodonReadTimelineApplicationService.kt new file mode 100644 index 00000000..9858228b --- /dev/null +++ b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/timeline/MastodonReadTimelineApplicationService.kt @@ -0,0 +1,113 @@ +package dev.usbharu.hideout.mastodon.application.timeline + +import dev.usbharu.hideout.core.application.shared.AbstractApplicationService +import dev.usbharu.hideout.core.application.shared.Transaction +import dev.usbharu.hideout.core.domain.model.post.Visibility.* +import dev.usbharu.hideout.core.domain.model.support.acct.Acct +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.timeline.TimelineId +import dev.usbharu.hideout.core.domain.model.timeline.TimelineRepository +import dev.usbharu.hideout.core.external.timeline.ReadTimelineOption +import dev.usbharu.hideout.core.external.timeline.TimelineStore +import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.Account +import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.MediaAttachment +import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.Status +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Service + +@Service +class MastodonReadTimelineApplicationService( + transaction: Transaction, + private val timelineRepository: TimelineRepository, + private val timelineStore: TimelineStore +) : + AbstractApplicationService>(transaction, logger) { + override suspend fun internalExecute( + command: MastodonReadTimeline, + principal: Principal + ): PaginationList { + val timeline = timelineRepository.findById(TimelineId(command.timelineId)) + ?: throw IllegalArgumentException("Timeline ${command.timelineId} not found.") + + val readTimelineOption = ReadTimelineOption( + command.mediaOnly, + command.localOnly, + command.remoteOnly + ) + + val readTimeline = timelineStore.readTimeline(timeline, readTimelineOption, command.page, principal) + + return PaginationList(readTimeline.map { + Status( + it.postId.id.toString(), + it.post.url.toString(), + it.post.createdAt.toString(), + account = Account( + id = it.postActor.id.id.toString(), + username = it.postActor.name.name, + acct = Acct(it.postActor.name.name, it.postActor.domain.domain).toString(), + url = it.postActor.url.toString(), + displayName = it.postActor.screenName.screenName, + note = it.postActor.description.description, + avatar = it.postActorIconMedia?.url.toString(), + avatarStatic = it.postActorIconMedia?.thumbnailUrl.toString(), + header = "", + headerStatic = "", + locked = false, + fields = emptyList(), + emojis = emptyList(), + bot = false, + group = false, + discoverable = true, + createdAt = it.postActor.createdAt.toString(), + statusesCount = it.postActor.postsCount.postsCount, + noindex = true, + moved = it.postActor.moveTo != null, + suspended = it.postActor.suspend, + limited = false, + lastStatusAt = it.postActor.lastPostAt?.toString(), + followersCount = it.postActor.followersCount?.relationshipCount, + followingCount = it.postActor.followingCount?.relationshipCount, + source = null + ), + content = it.post.content.content, + visibility = when (it.post.visibility) { + PUBLIC -> Status.Visibility.public + UNLISTED -> Status.Visibility.unlisted + FOLLOWERS -> Status.Visibility.private + DIRECT -> Status.Visibility.direct + }, + sensitive = it.post.sensitive, + spoilerText = it.post.overview?.overview.orEmpty(), + mediaAttachments = it.postMedias.map { MediaAttachment(it.id.id.toString()) }, + mentions = emptyList(), + tags = emptyList(), + emojis = emptyList(), + reblogsCount = 0, + favouritesCount = it.reactionsList.sumOf { it.count }, + repliesCount = 0, + url = it.post.url.toString(), + text = it.post.content.text, + application = null, + inReplyToId = it.replyPost?.id?.toString(), + inReplyToAccountId = it.replyPostActor?.id?.toString(), + reblog = null, + poll = null, + card = null, + language = null, + editedAt = null, + favourited = it.favourited, + reblogged = false, + muted = false, + bookmarked = false, + pinned = false, + filtered = emptyList(), + ) + }, readTimeline.next?.id, readTimeline.prev?.id) + } + + companion object { + private val logger = LoggerFactory.getLogger(MastodonReadTimelineApplicationService::class.java) + } +} \ No newline at end of file diff --git a/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/SpringTimelineApi.kt b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/SpringTimelineApi.kt index 2a27d263..9f259736 100644 --- a/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/SpringTimelineApi.kt +++ b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/SpringTimelineApi.kt @@ -16,8 +16,59 @@ package dev.usbharu.hideout.mastodon.interfaces.api +import dev.usbharu.hideout.core.application.exception.InternalServerException +import dev.usbharu.hideout.core.application.shared.Transaction +import dev.usbharu.hideout.core.domain.model.support.page.Page +import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailRepository +import dev.usbharu.hideout.core.infrastructure.springframework.oauth2.SpringSecurityOauth2PrincipalContextHolder +import dev.usbharu.hideout.mastodon.application.timeline.MastodonReadTimeline +import dev.usbharu.hideout.mastodon.application.timeline.MastodonReadTimelineApplicationService import dev.usbharu.hideout.mastodon.interfaces.api.generated.TimelineApi +import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.Status +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.asFlow +import kotlinx.coroutines.runBlocking +import org.springframework.http.ResponseEntity import org.springframework.stereotype.Controller @Controller -class SpringTimelineApi : TimelineApi +class SpringTimelineApi( + private val mastodonReadTimelineApplicationService: MastodonReadTimelineApplicationService, + + private val principalContextHolder: SpringSecurityOauth2PrincipalContextHolder, + private val userDetailRepository: UserDetailRepository, + private val transaction: Transaction, +) : TimelineApi { + override fun apiV1TimelinesHomeGet( + maxId: String?, + sinceId: String?, + minId: String?, + limit: Int? + ): ResponseEntity> = runBlocking { + val principal = principalContextHolder.getPrincipal() + val userDetail = transaction.transaction { + userDetailRepository.findByActorId(principal.actorId.id) + ?: throw InternalServerException("UserDetail not found.") + } + + val homeTimelineId = + userDetail.homeTimelineId ?: throw InternalServerException("HomeTimeline ${userDetail.id} is null.") + + ResponseEntity.ok( + mastodonReadTimelineApplicationService.execute( + MastodonReadTimeline( + timelineId = homeTimelineId.value, + mediaOnly = false, + localOnly = false, + remoteOnly = false, + page = Page.of( + maxId?.toLongOrNull(), + sinceId?.toLongOrNull(), + minId?.toLongOrNull(), + limit + ) + ), principal + ).asFlow() + ) + } +} diff --git a/hideout-mastodon/src/main/resources/openapi/mastodon.yaml b/hideout-mastodon/src/main/resources/openapi/mastodon.yaml index 76d8f5aa..1fb96267 100644 --- a/hideout-mastodon/src/main/resources/openapi/mastodon.yaml +++ b/hideout-mastodon/src/main/resources/openapi/mastodon.yaml @@ -2020,7 +2020,7 @@ components: type: object properties: filter: - $ref: "#/components/schemas/FilterResult" + $ref: "#/components/schemas/Filter" keyword_matches: type: array items: