mirror of https://github.com/usbharu/Hideout.git
feat: Mastodonでホームタイムラインを読めるように
This commit is contained in:
parent
3612a78115
commit
3b57786984
|
@ -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<TimelineObject, PostId> {
|
||||
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)) }
|
||||
|
|
|
@ -14,8 +14,8 @@
|
|||
</noscript>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<a th:href="${nsUrl}">No Script</a>
|
||||
</noscript>
|
||||
|
||||
<a th:href="${nsUrl}">No Script</a>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -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
|
||||
) {
|
||||
|
||||
}
|
|
@ -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<MastodonReadTimeline, PaginationList<Status, Long>>(transaction, logger) {
|
||||
override suspend fun internalExecute(
|
||||
command: MastodonReadTimeline,
|
||||
principal: Principal
|
||||
): PaginationList<Status, Long> {
|
||||
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)
|
||||
}
|
||||
}
|
|
@ -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<Flow<Status>> = 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()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2020,7 +2020,7 @@ components:
|
|||
type: object
|
||||
properties:
|
||||
filter:
|
||||
$ref: "#/components/schemas/FilterResult"
|
||||
$ref: "#/components/schemas/Filter"
|
||||
keyword_matches:
|
||||
type: array
|
||||
items:
|
||||
|
|
Loading…
Reference in New Issue