mirror of https://github.com/usbharu/Hideout.git
feat: 投稿を取得できるように
This commit is contained in:
parent
91867d6b83
commit
86daf1041b
|
@ -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,
|
||||
)
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.CommandExecutor
|
||||
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.media.MediaId
|
||||
import dev.usbharu.hideout.core.domain.model.post.PostId
|
||||
|
@ -38,13 +37,13 @@ class RegisterLocalPostApplicationService(
|
|||
private val postRepository: PostRepository,
|
||||
private val userDetailRepository: UserDetailRepository,
|
||||
transaction: Transaction,
|
||||
) : AbstractApplicationService<RegisterLocalPost, Unit>(transaction, Companion.logger) {
|
||||
) : AbstractApplicationService<RegisterLocalPost, Long>(transaction, Companion.logger) {
|
||||
|
||||
companion object {
|
||||
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)
|
||||
?: throw IllegalStateException("actor not found")).actorId
|
||||
|
||||
|
@ -59,5 +58,7 @@ class RegisterLocalPostApplicationService(
|
|||
command.mediaIds.map { MediaId(it) })
|
||||
|
||||
postRepository.save(post)
|
||||
|
||||
return post.id.id
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -86,7 +86,7 @@ create table if not exists media
|
|||
url varchar(255) not null unique,
|
||||
remote_url varchar(255) null unique,
|
||||
thumbnail_url varchar(255) null unique,
|
||||
"type" int not null,
|
||||
"type" varchar(100) not null,
|
||||
blurhash varchar(255) null,
|
||||
mime_type varchar(255) not null,
|
||||
description varchar(4000) null
|
||||
|
|
|
@ -31,6 +31,7 @@ dependencies {
|
|||
implementation(libs.jakarta.annotation)
|
||||
implementation(libs.jakarta.validation)
|
||||
|
||||
implementation(libs.bundles.exposed)
|
||||
implementation(libs.bundles.openapi)
|
||||
implementation(libs.bundles.coroutines)
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
)
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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],
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
)
|
|
@ -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.RegisterLocalPostApplicationService
|
||||
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.model.Status
|
||||
import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.StatusesRequest
|
||||
|
@ -29,8 +32,9 @@ import org.springframework.stereotype.Controller
|
|||
|
||||
@Controller
|
||||
class SpringStatusApi(
|
||||
private val oauth2CommandExecutorFactory: Oauth2CommandExecutorFactory,
|
||||
private val delegateCommandExecutorFactory: DelegateCommandExecutorFactory,
|
||||
private val registerLocalPostApplicationService: RegisterLocalPostApplicationService,
|
||||
private val getStatusApplicationService: GetStatusApplicationService,
|
||||
) : StatusApi {
|
||||
override suspend fun apiV1StatusesIdEmojiReactionsEmojiDelete(id: String, emoji: String): ResponseEntity<Status> {
|
||||
return super.apiV1StatusesIdEmojiReactionsEmojiDelete(id, emoji)
|
||||
|
@ -41,12 +45,18 @@ class SpringStatusApi(
|
|||
}
|
||||
|
||||
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> {
|
||||
val executor = oauth2CommandExecutorFactory.getCommandExecutor()
|
||||
registerLocalPostApplicationService.execute(
|
||||
val executor = delegateCommandExecutorFactory.getCommandExecutor() as Oauth2CommandExecutor
|
||||
val execute = registerLocalPostApplicationService.execute(
|
||||
RegisterLocalPost(
|
||||
userDetailId = executor.userDetailId,
|
||||
content = statusesRequest.status.orEmpty(),
|
||||
|
@ -65,6 +75,11 @@ class SpringStatusApi(
|
|||
),
|
||||
executor
|
||||
)
|
||||
return ResponseEntity.ok().build()
|
||||
|
||||
|
||||
val status = getStatusApplicationService.execute(GetStatus(execute.toString()), executor)
|
||||
return ResponseEntity.ok(
|
||||
status
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
}
|
|
@ -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>,
|
||||
)
|
|
@ -1577,8 +1577,6 @@ components:
|
|||
- discoverable
|
||||
- created_at
|
||||
- statuses_count
|
||||
- followers_count
|
||||
- followers_count
|
||||
|
||||
CredentialAccount:
|
||||
type: object
|
||||
|
|
Loading…
Reference in New Issue