Merge pull request #168 from usbharu/feature/refactor2

Feature/refactor2
This commit is contained in:
usbharu 2023-11-27 18:33:48 +09:00 committed by GitHub
commit e2f355f4d3
31 changed files with 209 additions and 239 deletions

View File

@ -3,9 +3,7 @@ build:
weights: weights:
Indentation: 0 Indentation: 0
MagicNumber: 0 MagicNumber: 0
InjectDispatcher: 0
EnumEntryNameCase: 0 EnumEntryNameCase: 0
ReplaceSafeCallChainWithRun: 0
VariableNaming: 0 VariableNaming: 0
NoNameShadowing: 0 NoNameShadowing: 0
@ -78,7 +76,7 @@ complexity:
active: true active: true
ReplaceSafeCallChainWithRun: ReplaceSafeCallChainWithRun:
active: true active: false
StringLiteralDuplication: StringLiteralDuplication:
active: false active: false
@ -172,3 +170,5 @@ potential-bugs:
coroutines: coroutines:
RedundantSuspendModifier: RedundantSuspendModifier:
active: false active: false
InjectDispatcher:
active: false

View File

@ -6,6 +6,7 @@ import dev.usbharu.hideout.activitypub.domain.model.objects.ObjectDeserializer
open class Accept : Object { open class Accept : Object {
@JsonDeserialize(using = ObjectDeserializer::class) @JsonDeserialize(using = ObjectDeserializer::class)
@Suppress("VariableNaming")
var `object`: Object? = null var `object`: Object? = null
protected constructor() protected constructor()

View File

@ -6,6 +6,7 @@ import dev.usbharu.hideout.activitypub.domain.model.objects.ObjectDeserializer
open class Create : Object { open class Create : Object {
@JsonDeserialize(using = ObjectDeserializer::class) @JsonDeserialize(using = ObjectDeserializer::class)
@Suppress("VariableNaming")
var `object`: Object? = null var `object`: Object? = null
var to: List<String> = emptyList() var to: List<String> = emptyList()
var cc: List<String> = emptyList() var cc: List<String> = emptyList()

View File

@ -6,6 +6,7 @@ import dev.usbharu.hideout.activitypub.domain.model.objects.ObjectDeserializer
open class Delete : Object { open class Delete : Object {
@JsonDeserialize(using = ObjectDeserializer::class) @JsonDeserialize(using = ObjectDeserializer::class)
@Suppress("VariableNaming")
var `object`: Object? = null var `object`: Object? = null
var published: String? = null var published: String? = null

View File

@ -3,6 +3,7 @@ package dev.usbharu.hideout.activitypub.domain.model
import dev.usbharu.hideout.activitypub.domain.model.objects.Object import dev.usbharu.hideout.activitypub.domain.model.objects.Object
open class Follow : Object { open class Follow : Object {
@Suppress("VariableNaming")
var `object`: String? = null var `object`: String? = null
protected constructor() : super() protected constructor() : super()

View File

@ -5,6 +5,7 @@ import dev.usbharu.hideout.activitypub.domain.model.objects.Object
import dev.usbharu.hideout.activitypub.domain.model.objects.ObjectDeserializer import dev.usbharu.hideout.activitypub.domain.model.objects.ObjectDeserializer
open class Like : Object { open class Like : Object {
@Suppress("VariableNaming")
var `object`: String? = null var `object`: String? = null
var content: String? = null var content: String? = null

View File

@ -8,6 +8,7 @@ import java.time.Instant
open class Undo : Object { open class Undo : Object {
@JsonDeserialize(using = ObjectDeserializer::class) @JsonDeserialize(using = ObjectDeserializer::class)
@Suppress("VariableNaming")
var `object`: Object? = null var `object`: Object? = null
var published: String? = null var published: String? = null

View File

@ -33,15 +33,8 @@ class ObjectDeserializer : JsonDeserializer<Object>() {
} }
return when (activityType) { return when (activityType) {
ExtendedActivityVocabulary.Follow -> { ExtendedActivityVocabulary.Follow -> p.codec.treeToValue(treeNode, Follow::class.java)
val readValue = p.codec.treeToValue(treeNode, Follow::class.java) ExtendedActivityVocabulary.Note -> p.codec.treeToValue(treeNode, Note::class.java)
readValue
}
ExtendedActivityVocabulary.Note -> {
p.codec.treeToValue(treeNode, Note::class.java)
}
ExtendedActivityVocabulary.Object -> p.codec.treeToValue(treeNode, Object::class.java) ExtendedActivityVocabulary.Object -> p.codec.treeToValue(treeNode, Object::class.java)
ExtendedActivityVocabulary.Link -> TODO() ExtendedActivityVocabulary.Link -> TODO()
ExtendedActivityVocabulary.Activity -> TODO() ExtendedActivityVocabulary.Activity -> TODO()

View File

@ -80,7 +80,7 @@ class NoteQueryServiceImpl(private val postRepository: PostRepository, private v
private suspend fun Query.toNote(): Note { private suspend fun Query.toNote(): Note {
return this.groupBy { it[Posts.id] } return this.groupBy { it[Posts.id] }
.map { it.value } .map { it.value }
.map { it.first().toNote(it.mapNotNull { it.toMediaOrNull() }) } .map { it.first().toNote(it.mapNotNull { resultRow -> resultRow.toMediaOrNull() }) }
.singleOr { FailedToGetResourcesException("resource does not exist.") } .singleOr { FailedToGetResourcesException("resource does not exist.") }
} }

View File

@ -22,7 +22,7 @@ class APDeleteProcessor(
val post = try { val post = try {
postQueryService.findByApId(deleteId) postQueryService.findByApId(deleteId)
} catch (e: FailedToGetResourcesException) { } catch (e: FailedToGetResourcesException) {
logger.warn("FAILED delete id: {} is not found.", deleteId) logger.warn("FAILED delete id: {} is not found.", deleteId, e)
return return
} }

View File

@ -37,15 +37,29 @@ class APRequestServiceImpl(
logger.debug("START ActivityPub Request GET url: {}, signer: {}", url, signer?.url) logger.debug("START ActivityPub Request GET url: {}, signer: {}", url, signer?.url)
val date = dateTimeFormatter.format(ZonedDateTime.now(ZoneId.of("GMT"))) val date = dateTimeFormatter.format(ZonedDateTime.now(ZoneId.of("GMT")))
val u = URL(url) val u = URL(url)
if (signer?.privateKey == null) { val httpResponse = if (signer?.privateKey == null) {
val bodyAsText = httpClient.get(url) { apGetNotSign(url, date)
header("Accept", ContentType.Application.Activity) } else {
header("Date", date) apGetSign(date, u, signer, url)
}.bodyAsText()
logBody(bodyAsText, url)
return objectMapper.readValue(bodyAsText, responseClass)
} }
val bodyAsText = httpResponse.bodyAsText()
val readValue = objectMapper.readValue(bodyAsText, responseClass)
logger.debug(
"SUCCESS ActivityPub Request GET status: {} url: {}",
httpResponse.status,
httpResponse.request.url
)
logBody(bodyAsText, url)
return readValue
}
private suspend fun apGetSign(
date: String,
u: URL,
signer: User,
url: String
): HttpResponse {
val headers = headers { val headers = headers {
append("Accept", ContentType.Application.Activity) append("Accept", ContentType.Application.Activity)
append("Date", date) append("Date", date)
@ -60,7 +74,7 @@ class APRequestServiceImpl(
), ),
privateKey = PrivateKey( privateKey = PrivateKey(
keyId = "${signer.url}#pubkey", keyId = "${signer.url}#pubkey",
privateKey = RsaUtil.decodeRsaPrivateKeyPem(signer.privateKey), privateKey = RsaUtil.decodeRsaPrivateKeyPem(signer.privateKey!!),
), ),
signHeaders = listOf("(request-target)", "date", "host", "accept") signHeaders = listOf("(request-target)", "date", "host", "accept")
) )
@ -75,15 +89,12 @@ class APRequestServiceImpl(
} }
contentType(ContentType.Application.Activity) contentType(ContentType.Application.Activity)
} }
val bodyAsText = httpResponse.bodyAsText() return httpResponse
val readValue = objectMapper.readValue(bodyAsText, responseClass) }
logger.debug(
"SUCCESS ActivityPub Request GET status: {} url: {}", private suspend fun apGetNotSign(url: String, date: String?) = httpClient.get(url) {
httpResponse.status, header("Accept", ContentType.Application.Activity)
httpResponse.request.url header("Date", date)
)
logBody(bodyAsText, url)
return readValue
} }
override suspend fun <T : Object, R : Object> apPost( override suspend fun <T : Object, R : Object> apPost(
@ -96,18 +107,9 @@ class APRequestServiceImpl(
return objectMapper.readValue(bodyAsText, responseClass) return objectMapper.readValue(bodyAsText, responseClass)
} }
@Suppress("LongMethod")
override suspend fun <T : Object> apPost(url: String, body: T?, signer: User?): String { override suspend fun <T : Object> apPost(url: String, body: T?, signer: User?): String {
logger.debug("START ActivityPub Request POST url: {}, signer: {}", url, signer?.url) logger.debug("START ActivityPub Request POST url: {}, signer: {}", url, signer?.url)
val requestBody = if (body != null) { val requestBody = addContextIfNotNull(body)
val mutableListOf = mutableListOf<String>()
mutableListOf.add("https://www.w3.org/ns/activitystreams")
mutableListOf.addAll(body.context)
body.context = mutableListOf
objectMapper.writeValueAsString(body)
} else {
null
}
logger.trace( logger.trace(
""" """
@ -129,20 +131,44 @@ class APRequestServiceImpl(
val date = dateTimeFormatter.format(ZonedDateTime.now(ZoneId.of("GMT"))) val date = dateTimeFormatter.format(ZonedDateTime.now(ZoneId.of("GMT")))
val u = URL(url) val u = URL(url)
if (signer?.privateKey == null) { val httpResponse = if (signer?.privateKey == null) {
val bodyAsText = httpClient.post(url) { apPostNotSign(url, date, digest, requestBody)
accept(ContentType.Application.Activity) } else {
header("Date", date) apPostSign(date, u, digest, signer, requestBody)
header("Digest", "sha-256=$digest")
if (requestBody != null) {
setBody(requestBody)
contentType(ContentType.Application.Activity)
}
}.bodyAsText()
logBody(bodyAsText, url)
return bodyAsText
} }
val bodyAsText = httpResponse.bodyAsText()
logger.debug(
"SUCCESS ActivityPub Request POST status: {} url: {}",
httpResponse.status,
httpResponse.request.url
)
logBody(bodyAsText, url)
return bodyAsText
}
private suspend fun apPostNotSign(
url: String,
date: String?,
digest: String,
requestBody: String?
) = httpClient.post(url) {
accept(ContentType.Application.Activity)
header("Date", date)
header("Digest", "sha-256=$digest")
if (requestBody != null) {
setBody(requestBody)
contentType(ContentType.Application.Activity)
}
}
private suspend fun apPostSign(
date: String,
u: URL,
digest: String,
signer: User,
requestBody: String?
): HttpResponse {
val headers = headers { val headers = headers {
append("Accept", ContentType.Application.Activity) append("Accept", ContentType.Application.Activity)
append("Date", date) append("Date", date)
@ -158,30 +184,31 @@ class APRequestServiceImpl(
), ),
privateKey = PrivateKey( privateKey = PrivateKey(
keyId = signer.keyId, keyId = signer.keyId,
privateKey = RsaUtil.decodeRsaPrivateKeyPem(signer.privateKey) privateKey = RsaUtil.decodeRsaPrivateKeyPem(signer.privateKey!!)
), ),
signHeaders = listOf("(request-target)", "date", "host", "digest") signHeaders = listOf("(request-target)", "date", "host", "digest")
) )
val httpResponse = httpClient.post(url) { val httpResponse = httpClient.post(u) {
headers { headers {
headers { appendAll(headers)
appendAll(headers) append("Signature", sign.signatureHeader)
append("Signature", sign.signatureHeader) remove("Host")
remove("Host")
}
} }
setBody(requestBody) setBody(requestBody)
contentType(ContentType.Application.Activity) contentType(ContentType.Application.Activity)
} }
val bodyAsText = httpResponse.bodyAsText() return httpResponse
logger.debug( }
"SUCCESS ActivityPub Request POST status: {} url: {}",
httpResponse.status, private fun <T : Object> addContextIfNotNull(body: T?) = if (body != null) {
httpResponse.request.url val mutableListOf = mutableListOf<String>()
) mutableListOf.add("https://www.w3.org/ns/activitystreams")
logBody(bodyAsText, url) mutableListOf.addAll(body.context)
return bodyAsText body.context = mutableListOf
objectMapper.writeValueAsString(body)
} else {
null
} }
private fun logBody(bodyAsText: String, url: String) { private fun logBody(bodyAsText: String, url: String) {

View File

@ -218,7 +218,6 @@ class APServiceImpl(
} }
} }
@Suppress("CyclomaticComplexMethod", "NotImplementedDeclaration")
override suspend fun processActivity( override suspend fun processActivity(
json: String, json: String,
type: ActivityType, type: ActivityType,

View File

@ -86,6 +86,7 @@ class InboxJobProcessor(
return verify.success return verify.success
} }
@Suppress("TooGenericExceptionCaught")
private fun parseSignatureHeader(httpHeaders: HttpHeaders): Signature? { private fun parseSignatureHeader(httpHeaders: HttpHeaders): Signature? {
return try { return try {
signatureHeaderParser.parse(httpHeaders) signatureHeaderParser.parse(httpHeaders)

View File

@ -4,6 +4,7 @@ import dev.usbharu.hideout.activitypub.domain.model.Note
import dev.usbharu.hideout.activitypub.query.NoteQueryService import dev.usbharu.hideout.activitypub.query.NoteQueryService
import dev.usbharu.hideout.application.external.Transaction import dev.usbharu.hideout.application.external.Transaction
import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException
import dev.usbharu.hideout.core.domain.model.post.Post
import dev.usbharu.hideout.core.domain.model.post.Visibility import dev.usbharu.hideout.core.domain.model.post.Visibility
import dev.usbharu.hideout.core.query.FollowerQueryService import dev.usbharu.hideout.core.query.FollowerQueryService
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
@ -28,20 +29,27 @@ class NoteApApiServiceImpl(
} }
Visibility.FOLLOWERS -> { Visibility.FOLLOWERS -> {
if (userId == null) { return@transaction getFollowersNote(userId, findById)
return@transaction null
}
if (followerQueryService.alreadyFollow(findById.second.userId, userId).not()) {
return@transaction null
}
return@transaction findById.first
} }
Visibility.DIRECT -> return@transaction null Visibility.DIRECT -> return@transaction null
} }
} }
private suspend fun getFollowersNote(
userId: Long?,
findById: Pair<Note, Post>
): Note? {
if (userId == null) {
return null
}
if (followerQueryService.alreadyFollow(findById.second.userId, userId)) {
return findById.first
}
return null
}
companion object { companion object {
private val logger = LoggerFactory.getLogger(NoteApApiServiceImpl::class.java) private val logger = LoggerFactory.getLogger(NoteApApiServiceImpl::class.java)
} }

View File

@ -77,69 +77,18 @@ class APUserServiceImpl(
override suspend fun fetchPerson(url: String, targetActor: String?): Person = override suspend fun fetchPerson(url: String, targetActor: String?): Person =
fetchPersonWithEntity(url, targetActor).first fetchPersonWithEntity(url, targetActor).first
@Suppress("LongMethod")
override suspend fun fetchPersonWithEntity(url: String, targetActor: String?): Pair<Person, User> { override suspend fun fetchPersonWithEntity(url: String, targetActor: String?): Pair<Person, User> {
return try { return try {
val userEntity = userQueryService.findByUrl(url) val userEntity = userQueryService.findByUrl(url)
val id = userEntity.url val id = userEntity.url
return Person( return entityToPerson(userEntity, id) to userEntity
type = emptyList(),
name = userEntity.name,
id = id,
preferredUsername = userEntity.name,
summary = userEntity.description,
inbox = "$id/inbox",
outbox = "$id/outbox",
url = id,
icon = Image(
type = emptyList(),
name = "$id/icon.png",
mediaType = "image/png",
url = "$id/icon.png"
),
publicKey = Key(
type = emptyList(),
name = "Public Key",
id = userEntity.keyId,
owner = id,
publicKeyPem = userEntity.publicKey
),
endpoints = mapOf("sharedInbox" to "${applicationConfig.url}/inbox"),
followers = userEntity.followers,
following = userEntity.following
) to userEntity
} catch (ignore: FailedToGetResourcesException) { } catch (ignore: FailedToGetResourcesException) {
val person = apResourceResolveService.resolve<Person>(url, null as Long?) val person = apResourceResolveService.resolve<Person>(url, null as Long?)
val id = person.id ?: throw IllegalActivityPubObjectException("id is null") val id = person.id ?: throw IllegalActivityPubObjectException("id is null")
try { try {
val userEntity = userQueryService.findByUrl(id) val userEntity = userQueryService.findByUrl(id)
return Person( return entityToPerson(userEntity, id) to userEntity
type = emptyList(),
name = userEntity.name,
id = id,
preferredUsername = userEntity.name,
summary = userEntity.description,
inbox = "$id/inbox",
outbox = "$id/outbox",
url = id,
icon = Image(
type = emptyList(),
name = "$id/icon.png",
mediaType = "image/png",
url = "$id/icon.png"
),
publicKey = Key(
type = emptyList(),
name = "Public Key",
id = userEntity.keyId,
owner = id,
publicKeyPem = userEntity.publicKey
),
endpoints = mapOf("sharedInbox" to "${applicationConfig.url}/inbox"),
followers = userEntity.followers,
following = userEntity.following
) to userEntity
} catch (_: FailedToGetResourcesException) { } catch (_: FailedToGetResourcesException) {
} }
person to userService.createRemoteUser( person to userService.createRemoteUser(
@ -163,4 +112,34 @@ class APUserServiceImpl(
) )
} }
} }
private fun entityToPerson(
userEntity: User,
id: String
) = Person(
type = emptyList(),
name = userEntity.name,
id = id,
preferredUsername = userEntity.name,
summary = userEntity.description,
inbox = "$id/inbox",
outbox = "$id/outbox",
url = id,
icon = Image(
type = emptyList(),
name = "$id/icon.png",
mediaType = "image/png",
url = "$id/icon.png"
),
publicKey = Key(
type = emptyList(),
name = "Public Key",
id = userEntity.keyId,
owner = id,
publicKeyPem = userEntity.publicKey
),
endpoints = mapOf("sharedInbox" to "${applicationConfig.url}/inbox"),
followers = userEntity.followers,
following = userEntity.following
)
} }

View File

@ -200,8 +200,8 @@ class SecurityConfig {
it.ignoringRequestMatchers(builder.pattern("/inbox")) it.ignoringRequestMatchers(builder.pattern("/inbox"))
it.ignoringRequestMatchers(PathRequest.toH2Console()) it.ignoringRequestMatchers(PathRequest.toH2Console())
}.headers { }.headers {
it.frameOptions { it.frameOptions { frameOptionsConfig ->
it.sameOrigin() frameOptionsConfig.sameOrigin()
} }
} }
return http.build() return http.build()

View File

@ -2,6 +2,7 @@ package dev.usbharu.hideout.core.domain.model.media
import dev.usbharu.hideout.core.service.media.FileType import dev.usbharu.hideout.core.service.media.FileType
import dev.usbharu.hideout.core.service.media.MimeType import dev.usbharu.hideout.core.service.media.MimeType
import dev.usbharu.hideout.domain.mastodon.model.generated.MediaAttachment
data class Media( data class Media(
val id: Long, val id: Long,
@ -14,3 +15,19 @@ data class Media(
val blurHash: String?, val blurHash: String?,
val description: String? = null val description: String? = null
) )
fun Media.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,
previewUrl = thumbnailUrl,
remoteUrl = remoteUrl,
description = description,
blurhash = blurHash,
textUrl = url
)

View File

@ -2,7 +2,6 @@ package dev.usbharu.hideout.core.domain.model.user
import org.springframework.stereotype.Repository import org.springframework.stereotype.Repository
@Suppress("TooManyFunctions")
@Repository @Repository
interface UserRepository { interface UserRepository {
suspend fun save(user: User): User suspend fun save(user: User): User

View File

@ -112,7 +112,9 @@ object DeliverRemoveReactionJob :
val actor: Prop<DeliverRemoveReactionJob, String> = string("actor") val actor: Prop<DeliverRemoveReactionJob, String> = string("actor")
val like: Prop<DeliverRemoveReactionJob, String> = string("like") val like: Prop<DeliverRemoveReactionJob, String> = string("like")
override fun convert(value: DeliverRemoveReactionJobParam): ScheduleContext<DeliverRemoveReactionJob>.(DeliverRemoveReactionJob) -> Unit = override fun convert(
value: DeliverRemoveReactionJobParam
): ScheduleContext<DeliverRemoveReactionJob>.(DeliverRemoveReactionJob) -> Unit =
{ {
props[id] = value.id props[id] = value.id
props[inbox] = value.inbox props[inbox] = value.inbox

View File

@ -15,7 +15,7 @@ class PostQueryMapper(private val postResultRowMapper: ResultRowMapper<Post>) :
.map { it.value } .map { it.value }
.map { .map {
it.first().let(postResultRowMapper::map) it.first().let(postResultRowMapper::map)
.copy(mediaIds = it.mapNotNull { it.getOrNull(PostsMedia.mediaId) }) .copy(mediaIds = it.mapNotNull { resultRow -> resultRow.getOrNull(PostsMedia.mediaId) })
} }
} }
} }

View File

@ -13,7 +13,7 @@ import org.springframework.stereotype.Service
@Service @Service
@ConditionalOnProperty(name = ["hideout.use-mongodb"], havingValue = "false", matchIfMissing = true) @ConditionalOnProperty(name = ["hideout.use-mongodb"], havingValue = "false", matchIfMissing = true)
class KJobJobQueueParentService() : JobQueueParentService { class KJobJobQueueParentService : JobQueueParentService {
private val logger = LoggerFactory.getLogger(this::class.java) private val logger = LoggerFactory.getLogger(this::class.java)

View File

@ -30,7 +30,8 @@ class KjobMongoJobQueueParentService(private val mongoClient: MongoClient) : Job
} }
override suspend fun <T, J : HideoutJob<T, J>> scheduleTypeSafe(job: J, jobProps: T) { override suspend fun <T, J : HideoutJob<T, J>> scheduleTypeSafe(job: J, jobProps: T) {
TODO("Not yet implemented") val convert = job.convert(jobProps)
kjob.schedule(job, convert)
} }
override fun close() { override fun close() {

View File

@ -9,7 +9,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.stereotype.Repository import org.springframework.stereotype.Repository
@Repository @Repository
@Suppress("InjectDispatcher")
@ConditionalOnProperty("hideout.use-mongodb", havingValue = "true", matchIfMissing = false) @ConditionalOnProperty("hideout.use-mongodb", havingValue = "true", matchIfMissing = false)
class MongoTimelineRepositoryWrapper( class MongoTimelineRepositoryWrapper(
private val mongoTimelineRepository: MongoTimelineRepository, private val mongoTimelineRepository: MongoTimelineRepository,

View File

@ -197,7 +197,7 @@ class ExposedOAuth2AuthorizationService(
} }
} }
@Suppress("LongMethod", "CyclomaticComplexMethod") @Suppress("LongMethod", "CyclomaticComplexMethod", "CastToNullableType", "UNCHECKED_CAST")
fun ResultRow.toAuthorization(): OAuth2Authorization { fun ResultRow.toAuthorization(): OAuth2Authorization {
val registeredClientId = this[Authorization.registeredClientId] val registeredClientId = this[Authorization.registeredClientId]
@ -272,7 +272,6 @@ class ExposedOAuth2AuthorizationService(
oidcIdTokenValue, oidcIdTokenValue,
oidcTokenIssuedAt, oidcTokenIssuedAt,
oidcTokenExpiresAt, oidcTokenExpiresAt,
@Suppress("CastToNullableType")
oidcTokenMetadata.getValue(OAuth2Authorization.Token.CLAIMS_METADATA_NAME) oidcTokenMetadata.getValue(OAuth2Authorization.Token.CLAIMS_METADATA_NAME)
as MutableMap<String, Any>? as MutableMap<String, Any>?
) )

View File

@ -74,11 +74,7 @@ class ImageMediaProcessService(private val imageMediaProcessorConfiguration: Ima
convertType, convertType,
it it
) )
if (write) { tempThumbnailFile.takeIf { write }
tempThumbnailFile
} else {
null
}
} }
} else { } else {
null null

View File

@ -45,7 +45,7 @@ class ExposedGenerateTimelineService(private val statusQueryService: StatusQuery
it[Timelines.postId], it[Timelines.postId],
it[Timelines.replyId], it[Timelines.replyId],
it[Timelines.repostId], it[Timelines.repostId],
it[Timelines.mediaIds].split(",").mapNotNull { it.toLongOrNull() } it[Timelines.mediaIds].split(",").mapNotNull { s -> s.toLongOrNull() }
) )
} }

View File

@ -3,7 +3,6 @@ package dev.usbharu.hideout.core.service.user
import dev.usbharu.hideout.core.domain.model.user.User import dev.usbharu.hideout.core.domain.model.user.User
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
@Suppress("TooManyFunctions")
@Service @Service
interface UserService { interface UserService {

View File

@ -59,6 +59,7 @@ class UserServiceImpl(
} }
override suspend fun createRemoteUser(user: RemoteUserCreateDto): User { override suspend fun createRemoteUser(user: RemoteUserCreateDto): User {
@Suppress("TooGenericExceptionCaught")
val instance = try { val instance = try {
instanceService.fetchInstance(user.url, user.sharedInbox) instanceService.fetchInstance(user.url, user.sharedInbox)
} catch (e: Exception) { } catch (e: Exception) {

View File

@ -1,22 +1,19 @@
package dev.usbharu.hideout.mastodon.infrastructure.exposedquery package dev.usbharu.hideout.mastodon.infrastructure.exposedquery
import dev.usbharu.hideout.core.domain.model.media.toMediaAttachments
import dev.usbharu.hideout.core.infrastructure.exposedrepository.* import dev.usbharu.hideout.core.infrastructure.exposedrepository.*
import dev.usbharu.hideout.core.service.media.FileType
import dev.usbharu.hideout.domain.mastodon.model.generated.Account import dev.usbharu.hideout.domain.mastodon.model.generated.Account
import dev.usbharu.hideout.domain.mastodon.model.generated.MediaAttachment
import dev.usbharu.hideout.domain.mastodon.model.generated.Status import dev.usbharu.hideout.domain.mastodon.model.generated.Status
import dev.usbharu.hideout.mastodon.interfaces.api.status.StatusQuery import dev.usbharu.hideout.mastodon.interfaces.api.status.StatusQuery
import dev.usbharu.hideout.mastodon.query.StatusQueryService import dev.usbharu.hideout.mastodon.query.StatusQueryService
import org.jetbrains.exposed.sql.ResultRow import org.jetbrains.exposed.sql.ResultRow
import org.jetbrains.exposed.sql.innerJoin
import org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.select
import org.springframework.stereotype.Repository import org.springframework.stereotype.Repository
import java.time.Instant import java.time.Instant
@Repository @Repository
class StatusQueryServiceImpl : StatusQueryService { class StatusQueryServiceImpl : StatusQueryService {
@Suppress("LongMethod") override suspend fun findByPostIds(ids: List<Long>): List<Status> = findByPostIdsWithMedia(ids)
override suspend fun findByPostIds(ids: List<Long>): List<Status> = findByPostIdsWithMediaAttachments(ids)
override suspend fun findByPostIdsWithMediaIds(statusQueries: List<StatusQuery>): List<Status> { override suspend fun findByPostIdsWithMediaIds(statusQueries: List<StatusQuery>): List<Status> {
val postIdSet = mutableSetOf<Long>() val postIdSet = mutableSetOf<Long>()
@ -29,23 +26,7 @@ class StatusQueryServiceImpl : StatusQueryService {
.associate { it[Posts.id] to toStatus(it) } .associate { it[Posts.id] to toStatus(it) }
val mediaMap = Media.select { Media.id inList mediaIdSet } val mediaMap = Media.select { Media.id inList mediaIdSet }
.associate { .associate {
it[Media.id] to it.toMedia().let { it[Media.id] to it.toMedia().toMediaAttachments()
MediaAttachment(
id = it.id.toString(),
type = when (it.type) {
FileType.Image -> MediaAttachment.Type.image
FileType.Video -> MediaAttachment.Type.video
FileType.Audio -> MediaAttachment.Type.audio
FileType.Unknown -> MediaAttachment.Type.unknown
},
url = it.url,
previewUrl = it.thumbnailUrl,
remoteUrl = it.remoteUrl,
description = "",
blurhash = it.blurHash,
textUrl = it.url
)
}
} }
return statusQueries.mapNotNull { statusQuery -> return statusQueries.mapNotNull { statusQuery ->
@ -58,18 +39,6 @@ class StatusQueryServiceImpl : StatusQueryService {
} }
} }
@Suppress("unused")
private suspend fun internalFindByPostIds(ids: List<Long>): List<Status> {
val pairs = Posts
.innerJoin(Users, onColumn = { Posts.userId }, otherColumn = { Users.id })
.select { Posts.id inList ids }
.map {
toStatus(it) to it[Posts.repostId]
}
return resolveReplyAndRepost(pairs)
}
private fun resolveReplyAndRepost(pairs: List<Pair<Status, Long?>>): List<Status> { private fun resolveReplyAndRepost(pairs: List<Pair<Status, Long?>>): List<Status> {
val statuses = pairs.map { it.first } val statuses = pairs.map { it.first }
return pairs return pairs
@ -89,8 +58,7 @@ class StatusQueryServiceImpl : StatusQueryService {
} }
} }
@Suppress("FunctionMaxLength") private suspend fun findByPostIdsWithMedia(ids: List<Long>): List<Status> {
private suspend fun findByPostIdsWithMediaAttachments(ids: List<Long>): List<Status> {
val pairs = Posts val pairs = Posts
.leftJoin(PostsMedia) .leftJoin(PostsMedia)
.leftJoin(Users) .leftJoin(Users)
@ -100,24 +68,8 @@ class StatusQueryServiceImpl : StatusQueryService {
.map { it.value } .map { it.value }
.map { .map {
toStatus(it.first()).copy( toStatus(it.first()).copy(
mediaAttachments = it.mapNotNull { mediaAttachments = it.mapNotNull { resultRow ->
it.toMediaOrNull()?.let { resultRow.toMediaOrNull()?.toMediaAttachments()
MediaAttachment(
id = it.id.toString(),
type = when (it.type) {
FileType.Image -> MediaAttachment.Type.image
FileType.Video -> MediaAttachment.Type.video
FileType.Audio -> MediaAttachment.Type.audio
FileType.Unknown -> MediaAttachment.Type.unknown
},
url = it.url,
previewUrl = it.thumbnailUrl,
remoteUrl = it.remoteUrl,
description = "",
blurhash = it.blurHash,
textUrl = it.url
)
}
} }
) to it.first()[Posts.repostId] ) to it.first()[Posts.repostId]
} }

View File

@ -1,7 +1,10 @@
package dev.usbharu.hideout.mastodon.interfaces.api.status package dev.usbharu.hideout.mastodon.interfaces.api.status
import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.annotation.JsonProperty
import dev.usbharu.hideout.core.domain.model.post.Visibility
import dev.usbharu.hideout.domain.mastodon.model.generated.Status
import dev.usbharu.hideout.domain.mastodon.model.generated.StatusesRequestPoll import dev.usbharu.hideout.domain.mastodon.model.generated.StatusesRequestPoll
import dev.usbharu.hideout.mastodon.interfaces.api.status.StatusesRequest.Visibility.*
@Suppress("VariableNaming", "EnumEntryName") @Suppress("VariableNaming", "EnumEntryName")
class StatusesRequest { class StatusesRequest {
@ -67,7 +70,7 @@ class StatusesRequest {
" scheduledAt=$scheduled_at)" " scheduledAt=$scheduled_at)"
} }
@Suppress("EnumNaming") @Suppress("EnumNaming", "EnumEntryNameCase")
enum class Visibility { enum class Visibility {
`public`, `public`,
unlisted, unlisted,
@ -75,3 +78,23 @@ class StatusesRequest {
direct direct
} }
} }
fun StatusesRequest.Visibility?.toPostVisibility(): Visibility {
return when (this) {
public -> Visibility.PUBLIC
unlisted -> Visibility.UNLISTED
private -> Visibility.FOLLOWERS
direct -> Visibility.DIRECT
null -> Visibility.PUBLIC
}
}
fun StatusesRequest.Visibility?.toStatusVisibility(): Status.Visibility {
return when (this) {
public -> Status.Visibility.public
unlisted -> Status.Visibility.unlisted
private -> Status.Visibility.private
direct -> Status.Visibility.direct
null -> Status.Visibility.public
}
}

View File

@ -3,15 +3,15 @@ package dev.usbharu.hideout.mastodon.service.status
import dev.usbharu.hideout.application.external.Transaction import dev.usbharu.hideout.application.external.Transaction
import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException
import dev.usbharu.hideout.core.domain.model.media.MediaRepository import dev.usbharu.hideout.core.domain.model.media.MediaRepository
import dev.usbharu.hideout.core.domain.model.post.Visibility import dev.usbharu.hideout.core.domain.model.media.toMediaAttachments
import dev.usbharu.hideout.core.query.PostQueryService import dev.usbharu.hideout.core.query.PostQueryService
import dev.usbharu.hideout.core.query.UserQueryService import dev.usbharu.hideout.core.query.UserQueryService
import dev.usbharu.hideout.core.service.media.FileType
import dev.usbharu.hideout.core.service.post.PostCreateDto import dev.usbharu.hideout.core.service.post.PostCreateDto
import dev.usbharu.hideout.core.service.post.PostService import dev.usbharu.hideout.core.service.post.PostService
import dev.usbharu.hideout.domain.mastodon.model.generated.MediaAttachment
import dev.usbharu.hideout.domain.mastodon.model.generated.Status import dev.usbharu.hideout.domain.mastodon.model.generated.Status
import dev.usbharu.hideout.mastodon.interfaces.api.status.StatusesRequest import dev.usbharu.hideout.mastodon.interfaces.api.status.StatusesRequest
import dev.usbharu.hideout.mastodon.interfaces.api.status.toPostVisibility
import dev.usbharu.hideout.mastodon.interfaces.api.status.toStatusVisibility
import dev.usbharu.hideout.mastodon.service.account.AccountService import dev.usbharu.hideout.mastodon.service.account.AccountService
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import java.time.Instant import java.time.Instant
@ -34,24 +34,15 @@ class StatsesApiServiceImpl(
private val transaction: Transaction private val transaction: Transaction
) : ) :
StatusesApiService { StatusesApiService {
@Suppress("LongMethod", "CyclomaticComplexMethod")
override suspend fun postStatus( override suspend fun postStatus(
statusesRequest: StatusesRequest, statusesRequest: StatusesRequest,
userId: Long userId: Long
): Status = transaction.transaction { ): Status = transaction.transaction {
val visibility = when (statusesRequest.visibility) {
StatusesRequest.Visibility.public -> Visibility.PUBLIC
StatusesRequest.Visibility.unlisted -> Visibility.UNLISTED
StatusesRequest.Visibility.private -> Visibility.FOLLOWERS
StatusesRequest.Visibility.direct -> Visibility.DIRECT
null -> Visibility.PUBLIC
}
val post = postService.createLocal( val post = postService.createLocal(
PostCreateDto( PostCreateDto(
text = statusesRequest.status.orEmpty(), text = statusesRequest.status.orEmpty(),
overview = statusesRequest.spoiler_text, overview = statusesRequest.spoiler_text,
visibility = visibility, visibility = statusesRequest.visibility.toPostVisibility(),
repolyId = statusesRequest.in_reply_to_id?.toLongOrNull(), repolyId = statusesRequest.in_reply_to_id?.toLongOrNull(),
userId = userId, userId = userId,
mediaIds = statusesRequest.media_ids.map { it.toLong() } mediaIds = statusesRequest.media_ids.map { it.toLong() }
@ -59,14 +50,6 @@ class StatsesApiServiceImpl(
) )
val account = accountService.findById(userId) val account = accountService.findById(userId)
val postVisibility = when (statusesRequest.visibility) {
StatusesRequest.Visibility.public -> Status.Visibility.public
StatusesRequest.Visibility.unlisted -> Status.Visibility.unlisted
StatusesRequest.Visibility.private -> Status.Visibility.private
StatusesRequest.Visibility.direct -> Status.Visibility.direct
null -> Status.Visibility.public
}
val replyUser = if (post.replyId != null) { val replyUser = if (post.replyId != null) {
try { try {
userQueryService.findById(postQueryService.findById(post.replyId).userId).id userQueryService.findById(postQueryService.findById(post.replyId).userId).id
@ -81,21 +64,7 @@ class StatsesApiServiceImpl(
val mediaAttachment = post.mediaIds.map { mediaId -> val mediaAttachment = post.mediaIds.map { mediaId ->
mediaRepository.findById(mediaId) mediaRepository.findById(mediaId)
}.map { }.map {
MediaAttachment( it.toMediaAttachments()
id = it.id.toString(),
type = when (it.type) {
FileType.Image -> MediaAttachment.Type.image
FileType.Video -> MediaAttachment.Type.video
FileType.Audio -> MediaAttachment.Type.audio
FileType.Unknown -> MediaAttachment.Type.unknown
},
url = it.url,
previewUrl = it.thumbnailUrl,
remoteUrl = it.remoteUrl,
description = "",
blurhash = it.blurHash,
textUrl = it.url
)
} }
Status( Status(
@ -104,7 +73,7 @@ class StatsesApiServiceImpl(
createdAt = Instant.ofEpochMilli(post.createdAt).toString(), createdAt = Instant.ofEpochMilli(post.createdAt).toString(),
account = account, account = account,
content = post.text, content = post.text,
visibility = postVisibility, visibility = statusesRequest.visibility.toStatusVisibility(),
sensitive = post.sensitive, sensitive = post.sensitive,
spoilerText = post.overview.orEmpty(), spoilerText = post.overview.orEmpty(),
mediaAttachments = mediaAttachment, mediaAttachments = mediaAttachment,