feat: 投稿を取得できるように

This commit is contained in:
usbharu 2024-06-07 20:44:12 +09:00
parent 91867d6b83
commit 86daf1041b
15 changed files with 678 additions and 12 deletions

View File

@ -0,0 +1,21 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.post
data class GetPost(
val postId: Long,
)

View File

@ -0,0 +1,40 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.post
import dev.usbharu.hideout.core.application.shared.AbstractApplicationService
import dev.usbharu.hideout.core.application.shared.CommandExecutor
import dev.usbharu.hideout.core.application.shared.Transaction
import dev.usbharu.hideout.core.domain.model.post.PostId
import dev.usbharu.hideout.core.domain.model.post.PostRepository
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
@Service
class GetPostApplicationService(private val postRepository: PostRepository, transaction: Transaction) :
AbstractApplicationService<GetPost, Post>(transaction, logger) {
override suspend fun internalExecute(command: GetPost, executor: CommandExecutor): Post {
val post = postRepository.findById(PostId(command.postId)) ?: throw Exception("Post not found")
return Post.of(post)
}
companion object {
private val logger = LoggerFactory.getLogger(GetPostApplicationService::class.java)
}
}

View File

@ -0,0 +1,58 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.post
import dev.usbharu.hideout.core.domain.model.post.Post
import dev.usbharu.hideout.core.domain.model.post.Visibility
import java.net.URI
import java.time.Instant
data class Post(
val id: Long,
val actorId: Long,
val overview: String?,
val text: String,
val content: String,
val createdAt: Instant,
val visibility: Visibility,
val url: URI,
val repostId: Long?,
val replyId: Long?,
val sensitive: Boolean,
val mediaIds: List<Long>,
val moveTo: Long?,
) {
companion object {
fun of(post: Post): dev.usbharu.hideout.core.application.post.Post {
return Post(
post.id.id,
post.actorId.id,
post.overview?.overview,
post.text,
post.content.content,
post.createdAt,
post.visibility,
post.url,
post.repostId?.id,
post.replyId?.id,
post.sensitive,
post.mediaIds.map { it.id },
post.moveTo?.id
)
}
}
}

View File

@ -19,7 +19,6 @@ package dev.usbharu.hideout.core.application.post
import dev.usbharu.hideout.core.application.shared.AbstractApplicationService import dev.usbharu.hideout.core.application.shared.AbstractApplicationService
import dev.usbharu.hideout.core.application.shared.CommandExecutor import dev.usbharu.hideout.core.application.shared.CommandExecutor
import dev.usbharu.hideout.core.application.shared.Transaction import dev.usbharu.hideout.core.application.shared.Transaction
import dev.usbharu.hideout.core.domain.model.actor.ActorId
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.domain.model.media.MediaId import dev.usbharu.hideout.core.domain.model.media.MediaId
import dev.usbharu.hideout.core.domain.model.post.PostId import dev.usbharu.hideout.core.domain.model.post.PostId
@ -38,13 +37,13 @@ class RegisterLocalPostApplicationService(
private val postRepository: PostRepository, private val postRepository: PostRepository,
private val userDetailRepository: UserDetailRepository, private val userDetailRepository: UserDetailRepository,
transaction: Transaction, transaction: Transaction,
) : AbstractApplicationService<RegisterLocalPost, Unit>(transaction, Companion.logger) { ) : AbstractApplicationService<RegisterLocalPost, Long>(transaction, Companion.logger) {
companion object { companion object {
val logger: Logger = LoggerFactory.getLogger(RegisterLocalPostApplicationService::class.java) val logger: Logger = LoggerFactory.getLogger(RegisterLocalPostApplicationService::class.java)
} }
override suspend fun internalExecute(command: RegisterLocalPost, executor: CommandExecutor) { override suspend fun internalExecute(command: RegisterLocalPost, executor: CommandExecutor): Long {
val actorId = (userDetailRepository.findById(command.userDetailId) val actorId = (userDetailRepository.findById(command.userDetailId)
?: throw IllegalStateException("actor not found")).actorId ?: throw IllegalStateException("actor not found")).actorId
@ -59,5 +58,7 @@ class RegisterLocalPostApplicationService(
command.mediaIds.map { MediaId(it) }) command.mediaIds.map { MediaId(it) })
postRepository.save(post) postRepository.save(post)
return post.id.id
} }
} }

View File

@ -0,0 +1,36 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.infrastructure.springframework
import dev.usbharu.hideout.core.application.shared.CommandExecutor
import dev.usbharu.hideout.core.infrastructure.springframework.oauth2.Oauth2CommandExecutorFactory
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.oauth2.jwt.Jwt
import org.springframework.stereotype.Component
@Component
class DelegateCommandExecutorFactory(
private val oauth2CommandExecutorFactory: Oauth2CommandExecutorFactory,
private val mvcCommandExecutorFactory: SpringMvcCommandExecutorFactory,
) {
fun getCommandExecutor(): CommandExecutor {
if (SecurityContextHolder.getContext().authentication.principal is Jwt) {
return oauth2CommandExecutorFactory.getCommandExecutor()
}
return mvcCommandExecutorFactory.getCommandExecutor()
}
}

View File

@ -86,7 +86,7 @@ create table if not exists media
url varchar(255) not null unique, url varchar(255) not null unique,
remote_url varchar(255) null unique, remote_url varchar(255) null unique,
thumbnail_url varchar(255) null unique, thumbnail_url varchar(255) null unique,
"type" int not null, "type" varchar(100) not null,
blurhash varchar(255) null, blurhash varchar(255) null,
mime_type varchar(255) not null, mime_type varchar(255) not null,
description varchar(4000) null description varchar(4000) null

View File

@ -31,6 +31,7 @@ dependencies {
implementation(libs.jakarta.annotation) implementation(libs.jakarta.annotation)
implementation(libs.jakarta.validation) implementation(libs.jakarta.validation)
implementation(libs.bundles.exposed)
implementation(libs.bundles.openapi) implementation(libs.bundles.openapi)
implementation(libs.bundles.coroutines) implementation(libs.bundles.coroutines)
} }

View File

@ -0,0 +1,21 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.mastodon.application.status
data class GetStatus(
val id: String,
)

View File

@ -0,0 +1,42 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.mastodon.application.status
import dev.usbharu.hideout.core.application.shared.AbstractApplicationService
import dev.usbharu.hideout.core.application.shared.CommandExecutor
import dev.usbharu.hideout.core.application.shared.Transaction
import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.Status
import dev.usbharu.hideout.mastodon.query.StatusQueryService
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
@Service
class GetStatusApplicationService(
private val statusQueryService: StatusQueryService,
transaction: Transaction,
) : AbstractApplicationService<GetStatus, Status>(
transaction,
logger
) {
companion object {
val logger = LoggerFactory.getLogger(GetStatusApplicationService::class.java)!!
}
override suspend fun internalExecute(command: GetStatus, executor: CommandExecutor): Status {
return statusQueryService.findByPostId(command.id.toLong()) ?: throw Exception("Not fount")
}
}

View File

@ -0,0 +1,73 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.mastodon.infrastructure.exposedquery
import dev.usbharu.hideout.core.config.ApplicationConfig
import dev.usbharu.hideout.core.infrastructure.exposedrepository.Actors
import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.Account
import dev.usbharu.hideout.mastodon.query.AccountQueryService
import org.jetbrains.exposed.sql.ResultRow
import org.jetbrains.exposed.sql.selectAll
import org.springframework.stereotype.Repository
@Repository
class AccountQueryServiceImpl(private val applicationConfig: ApplicationConfig) : AccountQueryService {
override suspend fun findById(accountId: Long): Account? {
val query = Actors.selectAll().where { Actors.id eq accountId }
return query
.singleOrNull()
?.let { toAccount(it) }
}
override suspend fun findByIds(accountIds: List<Long>): List<Account> {
val query = Actors.selectAll().where { Actors.id inList accountIds }
return query
.map { toAccount(it) }
}
private fun toAccount(
resultRow: ResultRow,
): Account {
val userUrl = "${applicationConfig.url}/users/${resultRow[Actors.id]}"
return Account(
id = resultRow[Actors.id].toString(),
username = resultRow[Actors.name],
acct = "${resultRow[Actors.name]}@${resultRow[Actors.domain]}",
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",
locked = resultRow[Actors.locked],
fields = emptyList(),
emojis = emptyList(),
bot = false,
group = false,
discoverable = true,
createdAt = resultRow[Actors.createdAt].toString(),
lastStatusAt = resultRow[Actors.lastPostAt]?.toString(),
statusesCount = resultRow[Actors.postsCount],
followersCount = resultRow[Actors.followersCount],
followingCount = resultRow[Actors.followingCount],
)
}
}

View File

@ -0,0 +1,291 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.mastodon.infrastructure.exposedquery
import dev.usbharu.hideout.core.domain.model.emoji.CustomEmoji
import dev.usbharu.hideout.core.domain.model.media.*
import dev.usbharu.hideout.core.domain.model.post.Visibility
import dev.usbharu.hideout.core.infrastructure.exposedrepository.*
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 dev.usbharu.hideout.mastodon.interfaces.api.generated.model.Status.Visibility.*
import dev.usbharu.hideout.mastodon.query.StatusQuery
import dev.usbharu.hideout.mastodon.query.StatusQueryService
import org.jetbrains.exposed.sql.ResultRow
import org.jetbrains.exposed.sql.andWhere
import org.jetbrains.exposed.sql.selectAll
import org.springframework.stereotype.Repository
import java.net.URI
import dev.usbharu.hideout.core.domain.model.media.Media as EntityMedia
import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.CustomEmoji as MastodonEmoji
@Suppress("IncompleteDestructuring")
@Repository
class StatusQueryServiceImpl : StatusQueryService {
override suspend fun findByPostIds(ids: List<Long>): List<Status> = findByPostIdsWithMedia(ids)
override suspend fun findByPostIdsWithMediaIds(statusQueries: List<StatusQuery>): List<Status> {
val postIdSet = mutableSetOf<Long>()
postIdSet.addAll(statusQueries.flatMap { listOfNotNull(it.postId, it.replyId, it.repostId) })
val mediaIdSet = mutableSetOf<Long>()
mediaIdSet.addAll(statusQueries.flatMap { it.mediaIds })
val emojiIdSet = mutableSetOf<Long>()
emojiIdSet.addAll(statusQueries.flatMap { it.emojiIds })
val postMap = Posts
.leftJoin(Actors)
.selectAll().where { Posts.id inList postIdSet }
.associate { it[Posts.id] to toStatus(it) }
val mediaMap = Media.selectAll().where { Media.id inList mediaIdSet }
.associate {
it[Media.id] to it.toMedia().toMediaAttachments()
}
val emojiMap = CustomEmojis.selectAll().where { CustomEmojis.id inList emojiIdSet }.associate {
it[CustomEmojis.id] to it.toCustomEmoji().toMastodonEmoji()
}
return statusQueries.mapNotNull { statusQuery ->
postMap[statusQuery.postId]?.copy(
inReplyToId = statusQuery.replyId?.toString(),
inReplyToAccountId = postMap[statusQuery.replyId]?.account?.id,
reblog = postMap[statusQuery.repostId],
mediaAttachments = statusQuery.mediaIds.mapNotNull { mediaMap[it] },
emojis = statusQuery.emojiIds.mapNotNull { emojiMap[it] }
)
}
}
override suspend fun accountsStatus(
accountId: Long,
onlyMedia: Boolean,
excludeReplies: Boolean,
excludeReblogs: Boolean,
pinned: Boolean,
tagged: String?,
includeFollowers: Boolean,
): List<Status> {
val query = Posts
.leftJoin(PostsMedia)
.leftJoin(Actors)
.leftJoin(Media)
.selectAll().where { Posts.actorId eq accountId }
if (onlyMedia) {
query.andWhere { PostsMedia.mediaId.isNotNull() }
}
if (excludeReplies) {
query.andWhere { Posts.replyId.isNotNull() }
}
if (excludeReblogs) {
query.andWhere { Posts.repostId.isNotNull() }
}
if (includeFollowers) {
query.andWhere { Posts.visibility inList listOf(public.name, unlisted.name, private.name) }
} else {
query.andWhere { Posts.visibility inList listOf(public.name, unlisted.name) }
}
val pairs = query
.groupBy { it[Posts.id] }
.map { it.value }
.map {
toStatus(it.first()).copy(
mediaAttachments = it.mapNotNull { resultRow ->
resultRow.toMediaOrNull()?.toMediaAttachments()
}
) to it.first()[Posts.repostId]
}
val statuses = resolveReplyAndRepost(pairs)
return statuses
}
override suspend fun findByPostId(id: Long): Status? {
val map = Posts
.leftJoin(PostsMedia)
.leftJoin(Actors)
.leftJoin(Media)
.selectAll()
.where { Posts.id eq id }
.groupBy { it[Posts.id] }
.map { it.value }
.map {
toStatus(it.first()).copy(
mediaAttachments = it.mapNotNull { resultRow ->
resultRow.toMediaOrNull()?.toMediaAttachments()
},
emojis = it.mapNotNull { resultRow -> resultRow.toCustomEmojiOrNull()?.toMastodonEmoji() }
) to it.first()[Posts.repostId]
}
return resolveReplyAndRepost(map).singleOrNull()
}
private fun resolveReplyAndRepost(pairs: List<Pair<Status, Long?>>): List<Status> {
val statuses = pairs.map { it.first }
return pairs
.map {
if (it.second != null) {
it.first.copy(reblog = statuses.find { (id) -> id == it.second.toString() })
} else {
it.first
}
}
.map {
if (it.inReplyToId != null) {
println("statuses trace: $statuses")
println("inReplyToId trace: ${it.inReplyToId}")
it.copy(inReplyToAccountId = statuses.find { (id) -> id == it.inReplyToId }?.account?.id)
} else {
it
}
}
}
private suspend fun findByPostIdsWithMedia(ids: List<Long>): List<Status> {
val pairs = Posts
.leftJoin(PostsMedia)
.leftJoin(PostsEmojis)
.leftJoin(CustomEmojis)
.leftJoin(Actors)
.leftJoin(Media)
.selectAll().where { Posts.id inList ids }
.groupBy { it[Posts.id] }
.map { it.value }
.map {
toStatus(it.first()).copy(
mediaAttachments = it.mapNotNull { resultRow ->
resultRow.toMediaOrNull()?.toMediaAttachments()
},
emojis = it.mapNotNull { resultRow -> resultRow.toCustomEmojiOrNull()?.toMastodonEmoji() }
) to it.first()[Posts.repostId]
}
return resolveReplyAndRepost(pairs)
}
}
private fun CustomEmoji.toMastodonEmoji(): MastodonEmoji = MastodonEmoji(
shortcode = this.name,
url = this.url.toString(),
staticUrl = this.url.toString(),
visibleInPicker = true,
category = this.category.orEmpty()
)
private fun toStatus(it: ResultRow) = Status(
id = it[Posts.id].toString(),
uri = it[Posts.apId],
createdAt = it[Posts.createdAt].toString(),
account = Account(
id = it[Actors.id].toString(),
username = it[Actors.name],
acct = "${it[Actors.name]}@${it[Actors.domain]}",
url = it[Actors.url],
displayName = it[Actors.screenName],
note = it[Actors.description],
avatar = it[Actors.url] + "/icon.jpg",
avatarStatic = it[Actors.url] + "/icon.jpg",
header = it[Actors.url] + "/header.jpg",
headerStatic = it[Actors.url] + "/header.jpg",
locked = it[Actors.locked],
fields = emptyList(),
emojis = emptyList(),
bot = false,
group = false,
discoverable = true,
createdAt = it[Actors.createdAt].toString(),
lastStatusAt = it[Actors.lastPostAt]?.toString(),
statusesCount = it[Actors.postsCount],
followersCount = it[Actors.followersCount],
followingCount = it[Actors.followingCount],
noindex = false,
moved = false,
suspendex = false,
limited = false
),
content = it[Posts.text],
visibility = when (Visibility.valueOf(it[Posts.visibility])) {
Visibility.PUBLIC -> public
Visibility.UNLISTED -> unlisted
Visibility.FOLLOWERS -> private
Visibility.DIRECT -> direct
},
sensitive = it[Posts.sensitive],
spoilerText = it[Posts.overview].orEmpty(),
mediaAttachments = emptyList(),
mentions = emptyList(),
tags = emptyList(),
emojis = emptyList(),
reblogsCount = 0,
favouritesCount = 0,
repliesCount = 0,
url = it[Posts.apId],
inReplyToId = it[Posts.replyId]?.toString(),
inReplyToAccountId = null,
language = null,
text = it[Posts.text],
editedAt = null
)
fun ResultRow.toMedia(): EntityMedia {
val fileType = FileType.valueOf(this[Media.type])
val mimeType = this[Media.mimeType]
return EntityMedia(
id = MediaId(this[Media.id]),
name = MediaName(this[Media.name]),
url = URI.create(this[Media.url]),
remoteUrl = this[Media.remoteUrl]?.let { URI.create(it) },
thumbnailUrl = this[Media.thumbnailUrl]?.let { URI.create(it) },
type = fileType,
blurHash = this[Media.blurhash]?.let { MediaBlurHash(it) },
mimeType = MimeType(mimeType.substringBefore("/"), mimeType.substringAfter("/"), fileType),
description = this[Media.description]?.let { MediaDescription(it) }
)
}
fun ResultRow.toMediaOrNull(): EntityMedia? {
val fileType = FileType.valueOf(this.getOrNull(Media.type) ?: return null)
val mimeType = this.getOrNull(Media.mimeType) ?: return null
return EntityMedia(
id = MediaId(this.getOrNull(Media.id) ?: return null),
name = MediaName(this.getOrNull(Media.name) ?: return null),
url = URI.create(this.getOrNull(Media.url) ?: return null),
remoteUrl = this[Media.remoteUrl]?.let { URI.create(it) },
thumbnailUrl = this[Media.thumbnailUrl]?.let { URI.create(it) },
type = FileType.valueOf(this[Media.type]),
blurHash = this[Media.blurhash]?.let { MediaBlurHash(it) },
mimeType = MimeType(mimeType.substringBefore("/"), mimeType.substringAfter("/"), fileType),
description = MediaDescription(this[Media.description] ?: return null)
)
}
fun EntityMedia.toMediaAttachments(): MediaAttachment = MediaAttachment(
id = id.toString(),
type = when (type) {
FileType.Image -> MediaAttachment.Type.image
FileType.Video -> MediaAttachment.Type.video
FileType.Audio -> MediaAttachment.Type.audio
FileType.Unknown -> MediaAttachment.Type.unknown
},
url = url.toString(),
previewUrl = thumbnailUrl?.toString(),
remoteUrl = remoteUrl?.toString(),
description = description?.description,
blurhash = blurHash?.hash,
textUrl = url.toString()
)

View File

@ -19,7 +19,10 @@ package dev.usbharu.hideout.mastodon.interfaces.api
import dev.usbharu.hideout.core.application.post.RegisterLocalPost import dev.usbharu.hideout.core.application.post.RegisterLocalPost
import dev.usbharu.hideout.core.application.post.RegisterLocalPostApplicationService import dev.usbharu.hideout.core.application.post.RegisterLocalPostApplicationService
import dev.usbharu.hideout.core.domain.model.post.Visibility import dev.usbharu.hideout.core.domain.model.post.Visibility
import dev.usbharu.hideout.core.infrastructure.springframework.oauth2.Oauth2CommandExecutorFactory import dev.usbharu.hideout.core.infrastructure.springframework.DelegateCommandExecutorFactory
import dev.usbharu.hideout.core.infrastructure.springframework.oauth2.Oauth2CommandExecutor
import dev.usbharu.hideout.mastodon.application.status.GetStatus
import dev.usbharu.hideout.mastodon.application.status.GetStatusApplicationService
import dev.usbharu.hideout.mastodon.interfaces.api.generated.StatusApi import dev.usbharu.hideout.mastodon.interfaces.api.generated.StatusApi
import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.Status import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.Status
import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.StatusesRequest import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.StatusesRequest
@ -29,8 +32,9 @@ import org.springframework.stereotype.Controller
@Controller @Controller
class SpringStatusApi( class SpringStatusApi(
private val oauth2CommandExecutorFactory: Oauth2CommandExecutorFactory, private val delegateCommandExecutorFactory: DelegateCommandExecutorFactory,
private val registerLocalPostApplicationService: RegisterLocalPostApplicationService, private val registerLocalPostApplicationService: RegisterLocalPostApplicationService,
private val getStatusApplicationService: GetStatusApplicationService,
) : StatusApi { ) : StatusApi {
override suspend fun apiV1StatusesIdEmojiReactionsEmojiDelete(id: String, emoji: String): ResponseEntity<Status> { override suspend fun apiV1StatusesIdEmojiReactionsEmojiDelete(id: String, emoji: String): ResponseEntity<Status> {
return super.apiV1StatusesIdEmojiReactionsEmojiDelete(id, emoji) return super.apiV1StatusesIdEmojiReactionsEmojiDelete(id, emoji)
@ -41,12 +45,18 @@ class SpringStatusApi(
} }
override suspend fun apiV1StatusesIdGet(id: String): ResponseEntity<Status> { override suspend fun apiV1StatusesIdGet(id: String): ResponseEntity<Status> {
return super.apiV1StatusesIdGet(id)
return ResponseEntity.ok(
getStatusApplicationService.execute(
GetStatus(id),
delegateCommandExecutorFactory.getCommandExecutor()
)
)
} }
override suspend fun apiV1StatusesPost(statusesRequest: StatusesRequest): ResponseEntity<Status> { override suspend fun apiV1StatusesPost(statusesRequest: StatusesRequest): ResponseEntity<Status> {
val executor = oauth2CommandExecutorFactory.getCommandExecutor() val executor = delegateCommandExecutorFactory.getCommandExecutor() as Oauth2CommandExecutor
registerLocalPostApplicationService.execute( val execute = registerLocalPostApplicationService.execute(
RegisterLocalPost( RegisterLocalPost(
userDetailId = executor.userDetailId, userDetailId = executor.userDetailId,
content = statusesRequest.status.orEmpty(), content = statusesRequest.status.orEmpty(),
@ -65,6 +75,11 @@ class SpringStatusApi(
), ),
executor executor
) )
return ResponseEntity.ok().build()
val status = getStatusApplicationService.execute(GetStatus(execute.toString()), executor)
return ResponseEntity.ok(
status
)
} }
} }

View File

@ -0,0 +1,24 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.mastodon.query
import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.Account
interface AccountQueryService {
suspend fun findById(accountId: Long): Account?
suspend fun findByIds(accountIds: List<Long>): List<Account>
}

View File

@ -0,0 +1,45 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.mastodon.query
import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.Status
interface StatusQueryService {
suspend fun findByPostIds(ids: List<Long>): List<Status>
suspend fun findByPostIdsWithMediaIds(statusQueries: List<StatusQuery>): List<Status>
@Suppress("LongParameterList")
suspend fun accountsStatus(
accountId: Long,
onlyMedia: Boolean = false,
excludeReplies: Boolean = false,
excludeReblogs: Boolean = false,
pinned: Boolean = false,
tagged: String?,
includeFollowers: Boolean = false,
): List<Status>
suspend fun findByPostId(id: Long): Status?
}
data class StatusQuery(
val postId: Long,
val replyId: Long?,
val repostId: Long?,
val mediaIds: List<Long>,
val emojiIds: List<Long>,
)

View File

@ -1577,8 +1577,6 @@ components:
- discoverable - discoverable
- created_at - created_at
- statuses_count - statuses_count
- followers_count
- followers_count
CredentialAccount: CredentialAccount:
type: object type: object