Merge branch 'develop' into renovate/detekt

This commit is contained in:
usbharu 2024-09-09 18:48:09 +09:00 committed by GitHub
commit 4c6c8f5938
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 238 additions and 34 deletions

View File

@ -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)) }

View File

@ -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>

View File

@ -16,9 +16,9 @@
package dev.usbharu.hideout.mastodon.application.accounts
import dev.usbharu.hideout.core.application.shared.LocalUserAbstractApplicationService
import dev.usbharu.hideout.core.application.shared.AbstractApplicationService
import dev.usbharu.hideout.core.application.shared.Transaction
import dev.usbharu.hideout.core.domain.model.support.principal.LocalUser
import dev.usbharu.hideout.core.domain.model.support.principal.Principal
import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.Account
import dev.usbharu.hideout.mastodon.query.AccountQueryService
import org.slf4j.LoggerFactory
@ -26,11 +26,11 @@ import org.springframework.stereotype.Service
@Service
class GetAccountApplicationService(private val accountQueryService: AccountQueryService, transaction: Transaction) :
LocalUserAbstractApplicationService<GetAccount, Account>(
AbstractApplicationService<GetAccount, Account>(
transaction,
logger
) {
override suspend fun internalExecute(command: GetAccount, principal: LocalUser): Account {
override suspend fun internalExecute(command: GetAccount, principal: Principal): Account {
return accountQueryService.findById(command.accountId.toLong())
?: throw IllegalArgumentException("Account ${command.accountId} not found")
}

View File

@ -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
) {
}

View File

@ -0,0 +1,112 @@
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,
),
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)
}
}

View File

@ -53,10 +53,10 @@ class ExposedAccountQueryServiceImpl(private val applicationConfig: ApplicationC
url = resultRow[Actors.url],
displayName = resultRow[Actors.screenName],
note = resultRow[Actors.description],
avatar = userUrl + "/icon.jpg",
avatarStatic = userUrl + "/icon.jpg",
header = userUrl + "/header.jpg",
headerStatic = userUrl + "/header.jpg",
avatar = "$userUrl/icon.jpg",
avatarStatic = "$userUrl/icon.jpg",
header = "$userUrl/header.jpg",
headerStatic = "$userUrl/header.jpg",
locked = resultRow[Actors.locked],
fields = emptyList(),
emojis = emptyList(),
@ -68,6 +68,10 @@ class ExposedAccountQueryServiceImpl(private val applicationConfig: ApplicationC
statusesCount = resultRow[Actors.postsCount],
followersCount = resultRow[Actors.followersCount],
followingCount = resultRow[Actors.followingCount],
noindex = false,
moved = false,
suspended = resultRow[Actors.suspend],
limited = false,
)
}
}

View File

@ -39,11 +39,16 @@ import dev.usbharu.hideout.core.application.relationship.unfollow.Unfollow
import dev.usbharu.hideout.core.application.relationship.unfollow.UserUnfollowApplicationService
import dev.usbharu.hideout.core.application.relationship.unmute.Unmute
import dev.usbharu.hideout.core.application.relationship.unmute.UserUnmuteApplicationService
import dev.usbharu.hideout.core.domain.model.support.principal.Principal
import dev.usbharu.hideout.core.infrastructure.springframework.oauth2.SpringSecurityOauth2PrincipalContextHolder
import dev.usbharu.hideout.mastodon.application.accounts.GetAccount
import dev.usbharu.hideout.mastodon.application.accounts.GetAccountApplicationService
import dev.usbharu.hideout.mastodon.interfaces.api.generated.AccountApi
import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.runBlocking
import org.springframework.http.ResponseEntity
import org.springframework.stereotype.Controller
@ -66,27 +71,30 @@ class SpringAccountApi(
) : AccountApi {
override suspend fun apiV1AccountsIdBlockPost(id: String): ResponseEntity<Relationship> {
userBlockApplicationService.execute(Block(id.toLong()), principalContextHolder.getPrincipal())
return fetchRelationship(id)
val principal = principalContextHolder.getPrincipal()
userBlockApplicationService.execute(Block(id.toLong()), principal)
return fetchRelationship(id, principal)
}
override suspend fun apiV1AccountsIdFollowPost(
id: String,
followRequestBody: FollowRequestBody?,
): ResponseEntity<Relationship> {
val principal = principalContextHolder.getPrincipal()
userFollowRequestApplicationService.execute(
FollowRequest(id.toLong()),
principalContextHolder.getPrincipal()
principal
)
return fetchRelationship(id)
return fetchRelationship(id, principal)
}
private suspend fun fetchRelationship(
id: String,
principal: Principal
): ResponseEntity<Relationship> {
val relationship = getRelationshipApplicationService.execute(
GetRelationship(id.toLong()),
principalContextHolder.getPrincipal()
principal
)
return ResponseEntity.ok(
Relationship(
@ -117,43 +125,56 @@ class SpringAccountApi(
}
override suspend fun apiV1AccountsIdMutePost(id: String): ResponseEntity<Relationship> {
val principal = principalContextHolder.getPrincipal()
userMuteApplicationService.execute(
Mute(id.toLong()),
principalContextHolder.getPrincipal()
principal
)
return fetchRelationship(id)
return fetchRelationship(id, principal)
}
override suspend fun apiV1AccountsIdRemoveFromFollowersPost(id: String): ResponseEntity<Relationship> {
val principal = principalContextHolder.getPrincipal()
userRemoveFromFollowersApplicationService.execute(
RemoveFromFollowers(id.toLong()),
principalContextHolder.getPrincipal()
principal
)
return fetchRelationship(id)
return fetchRelationship(id, principal)
}
override suspend fun apiV1AccountsIdUnblockPost(id: String): ResponseEntity<Relationship> {
val principal = principalContextHolder.getPrincipal()
userUnblockApplicationService.execute(
Unblock(id.toLong()),
principalContextHolder.getPrincipal()
principal
)
return fetchRelationship(id)
return fetchRelationship(id, principal)
}
override suspend fun apiV1AccountsIdUnfollowPost(id: String): ResponseEntity<Relationship> {
val principal = principalContextHolder.getPrincipal()
userUnfollowApplicationService.execute(
Unfollow(id.toLong()),
principalContextHolder.getPrincipal()
principal
)
return fetchRelationship(id)
return fetchRelationship(id, principal)
}
override fun apiV1AccountsRelationshipsGet(
id: List<String>?,
withSuspended: Boolean
): ResponseEntity<Flow<Relationship>> {
val principal = runBlocking { principalContextHolder.getPrincipal() }
return ResponseEntity.ok(id.orEmpty().asFlow().mapNotNull { fetchRelationship(it, principal).body })
}
override suspend fun apiV1AccountsIdUnmutePost(id: String): ResponseEntity<Relationship> {
val principal = principalContextHolder.getPrincipal()
userUnmuteApplicationService.execute(
Unmute(id.toLong()),
principalContextHolder.getPrincipal()
principal
)
return fetchRelationship(id)
return fetchRelationship(id, principal)
}
override suspend fun apiV1AccountsPost(accountsCreateRequest: AccountsCreateRequest): ResponseEntity<Unit> =
@ -220,18 +241,20 @@ class SpringAccountApi(
}
override suspend fun apiV1FollowRequestsAccountIdAuthorizePost(accountId: String): ResponseEntity<Relationship> {
val principal = principalContextHolder.getPrincipal()
userAcceptFollowRequestApplicationService.execute(
AcceptFollowRequest(accountId.toLong()),
principalContextHolder.getPrincipal()
principal
)
return fetchRelationship(accountId)
return fetchRelationship(accountId, principal)
}
override suspend fun apiV1FollowRequestsAccountIdRejectPost(accountId: String): ResponseEntity<Relationship> {
val principal = principalContextHolder.getPrincipal()
userRejectFollowRequestApplicationService.execute(
RejectFollowRequest(accountId.toLong()),
principalContextHolder.getPrincipal()
principal
)
return fetchRelationship(accountId)
return fetchRelationship(accountId, principal)
}
}

View File

@ -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()
)
}
}

View File

@ -1597,8 +1597,6 @@ components:
type: integer
following_count:
type: integer
source:
$ref: "#/components/schemas/AccountSource"
required:
- id
@ -2020,7 +2018,7 @@ components:
type: object
properties:
filter:
$ref: "#/components/schemas/FilterResult"
$ref: "#/components/schemas/Filter"
keyword_matches:
type: array
items: