fix: ジョブキュー等を修正

This commit is contained in:
usbharu 2023-09-25 17:08:01 +09:00
parent 2651545c17
commit 35ba6a2022
Signed by: usbharu
GPG Key ID: 6556747BF94EEBC8
25 changed files with 239 additions and 86 deletions

View File

@ -0,0 +1,49 @@
package dev.usbharu.hideout
import dev.usbharu.hideout.domain.model.job.HideoutJob
import dev.usbharu.hideout.service.ap.APService
import dev.usbharu.hideout.service.job.JobQueueParentService
import dev.usbharu.hideout.service.job.JobQueueWorkerService
import org.slf4j.LoggerFactory
import org.springframework.boot.ApplicationArguments
import org.springframework.boot.ApplicationRunner
import org.springframework.stereotype.Component
@Component
class JobQueueRunner(private val jobQueueParentService: JobQueueParentService, private val jobs: List<HideoutJob>) :
ApplicationRunner {
override fun run(args: ApplicationArguments?) {
LOGGER.info("Init job queue. ${jobs.size}")
jobQueueParentService.init(jobs)
}
companion object {
val LOGGER = LoggerFactory.getLogger(JobQueueRunner::class.java)
}
}
@Component
class JobQueueWorkerRunner(
private val jobQueueWorkerService: JobQueueWorkerService,
private val jobs: List<HideoutJob>,
private val apService: APService
) : ApplicationRunner {
override fun run(args: ApplicationArguments?) {
LOGGER.info("Init job queue worker.")
jobQueueWorkerService.init(jobs.map {
it to {
execute {
LOGGER.debug("excute job ${it.name}")
apService.processActivity(
job = this,
hideoutJob = it
)
}
}
})
}
companion object {
val LOGGER = LoggerFactory.getLogger(JobQueueWorkerRunner::class.java)
}
}

View File

@ -0,0 +1,23 @@
package dev.usbharu.hideout.config
import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@Configuration
class ActivityPubConfig {
@Bean
@Qualifier("activitypub")
fun objectMapper(): ObjectMapper {
val objectMapper = jacksonObjectMapper()
.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
.setSerializationInclusion(JsonInclude.Include.NON_EMPTY)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
return objectMapper
}
}

View File

@ -1,12 +1,38 @@
package dev.usbharu.hideout.config package dev.usbharu.hideout.config
import dev.usbharu.hideout.plugins.KtorKeyMap
import dev.usbharu.hideout.plugins.httpSignaturePlugin
import dev.usbharu.hideout.query.UserQueryService
import dev.usbharu.hideout.service.core.Transaction
import io.ktor.client.* import io.ktor.client.*
import io.ktor.client.engine.cio.* import io.ktor.client.engine.cio.*
import io.ktor.client.plugins.logging.*
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration import org.springframework.context.annotation.Configuration
import tech.barbero.http.message.signing.KeyMap
@Configuration @Configuration
class HttpClientConfig { class HttpClientConfig {
@Bean @Bean
fun httpClient(): HttpClient = HttpClient(CIO) fun httpClient(keyMap: KeyMap): HttpClient = HttpClient(CIO).config {
install(httpSignaturePlugin) {
this.keyMap = keyMap
}
install(Logging) {
logger = Logger.DEFAULT
level = LogLevel.ALL
}
expectSuccess = true
}
@Bean
fun keyMap(
userQueryService: UserQueryService,
transaction: Transaction,
applicationConfig: ApplicationConfig
): KtorKeyMap {
return KtorKeyMap(
userQueryService, transaction, applicationConfig
)
}
} }

View File

@ -33,7 +33,7 @@ import java.security.interfaces.RSAPrivateKey
import java.security.interfaces.RSAPublicKey import java.security.interfaces.RSAPublicKey
import java.util.* import java.util.*
@EnableWebSecurity(debug = true) @EnableWebSecurity(debug = false)
@Configuration @Configuration
class SecurityConfig { class SecurityConfig {
@ -66,6 +66,7 @@ class SecurityConfig {
it.requestMatchers(PathRequest.toH2Console()).permitAll() it.requestMatchers(PathRequest.toH2Console()).permitAll()
it.requestMatchers( it.requestMatchers(
builder.pattern("/inbox"), builder.pattern("/inbox"),
builder.pattern("/users/*/inbox"),
builder.pattern("/api/v1/apps"), builder.pattern("/api/v1/apps"),
builder.pattern("/api/v1/instance/**"), builder.pattern("/api/v1/instance/**"),
builder.pattern("/.well-known/**"), builder.pattern("/.well-known/**"),
@ -85,6 +86,8 @@ class SecurityConfig {
.formLogin(Customizer.withDefaults()) .formLogin(Customizer.withDefaults())
.csrf { .csrf {
it.ignoringRequestMatchers(builder.pattern("/api/**")) it.ignoringRequestMatchers(builder.pattern("/api/**"))
it.ignoringRequestMatchers(builder.pattern("/users/*/inbox"))
it.ignoringRequestMatchers(builder.pattern("/inbox"))
it.ignoringRequestMatchers(PathRequest.toH2Console()) it.ignoringRequestMatchers(PathRequest.toH2Console())
} }
.headers { .headers {

View File

@ -5,6 +5,7 @@ import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.context.properties.ConfigurationProperties import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration import org.springframework.context.annotation.Configuration
import java.net.URL
@Configuration @Configuration
class SpringConfig { class SpringConfig {
@ -28,7 +29,7 @@ class SpringConfig {
@ConfigurationProperties("hideout") @ConfigurationProperties("hideout")
data class ApplicationConfig( data class ApplicationConfig(
val url: String val url: URL
) )
@ConfigurationProperties("hideout.database") @ConfigurationProperties("hideout.database")

View File

@ -15,7 +15,7 @@ interface InboxController {
produces = ["application/activity+json", "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""], produces = ["application/activity+json", "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""],
method = [RequestMethod.GET, RequestMethod.POST] method = [RequestMethod.GET, RequestMethod.POST]
) )
suspend fun inbox(@RequestBody string: String): ResponseEntity<Unit> { fun inbox(@RequestBody string: String): ResponseEntity<Unit> {
return ResponseEntity(HttpStatus.ACCEPTED) return ResponseEntity(HttpStatus.ACCEPTED)
} }
} }

View File

@ -1,6 +1,7 @@
package dev.usbharu.hideout.controller package dev.usbharu.hideout.controller
import dev.usbharu.hideout.service.ap.APService import dev.usbharu.hideout.service.ap.APService
import kotlinx.coroutines.runBlocking
import org.springframework.http.HttpStatus import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestBody
@ -8,9 +9,9 @@ import org.springframework.web.bind.annotation.RestController
@RestController @RestController
class InboxControllerImpl(private val apService: APService) : InboxController { class InboxControllerImpl(private val apService: APService) : InboxController {
override suspend fun inbox(@RequestBody string: String): ResponseEntity<Unit> { override fun inbox(@RequestBody string: String): ResponseEntity<Unit> = runBlocking {
val parseActivity = apService.parseActivity(string) val parseActivity = apService.parseActivity(string)
apService.processActivity(string, parseActivity) apService.processActivity(string, parseActivity)
return ResponseEntity(HttpStatus.ACCEPTED) ResponseEntity(HttpStatus.ACCEPTED)
} }
} }

View File

@ -9,5 +9,5 @@ import org.springframework.web.bind.annotation.RestController
@RestController @RestController
interface UserAPController { interface UserAPController {
@GetMapping("/users/{username}") @GetMapping("/users/{username}")
suspend fun userAp(@PathVariable("username") username: String): ResponseEntity<Person> fun userAp(@PathVariable("username") username: String): ResponseEntity<Person>
} }

View File

@ -2,14 +2,16 @@ package dev.usbharu.hideout.controller
import dev.usbharu.hideout.domain.model.ap.Person import dev.usbharu.hideout.domain.model.ap.Person
import dev.usbharu.hideout.service.ap.APUserService import dev.usbharu.hideout.service.ap.APUserService
import kotlinx.coroutines.runBlocking
import org.springframework.http.HttpStatus import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.RestController import org.springframework.web.bind.annotation.RestController
@RestController @RestController
class UserAPControllerImpl(private val apUserService: APUserService) : UserAPController { class UserAPControllerImpl(private val apUserService: APUserService) : UserAPController {
override suspend fun userAp(username: String): ResponseEntity<Person> { override fun userAp(username: String): ResponseEntity<Person> = runBlocking {
val person = apUserService.getPersonByName(username) val person = apUserService.getPersonByName(username)
return ResponseEntity(person, HttpStatus.OK) person.context += listOf("https://www.w3.org/ns/activitystreams")
ResponseEntity(person, HttpStatus.OK)
} }
} }

View File

@ -10,7 +10,6 @@ import org.springframework.http.ResponseEntity
import org.springframework.stereotype.Controller import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RequestParam
import java.net.URL
@Controller @Controller
class WebFingerController( class WebFingerController(
@ -21,14 +20,14 @@ class WebFingerController(
fun webfinger(@RequestParam("resource") resource: String): ResponseEntity<WebFinger> = runBlocking { fun webfinger(@RequestParam("resource") resource: String): ResponseEntity<WebFinger> = runBlocking {
val acct = AcctUtil.parse(resource.replace("acct:", "")) val acct = AcctUtil.parse(resource.replace("acct:", ""))
val user = val user =
webFingerApiService.findByNameAndDomain(acct.username, acct.domain ?: URL(applicationConfig.url).host) webFingerApiService.findByNameAndDomain(acct.username, acct.domain ?: applicationConfig.url.host)
val webFinger = WebFinger( val webFinger = WebFinger(
"acct:${user.name}@${user.domain}", "acct:${user.name}@${user.domain}",
listOf( listOf(
WebFinger.Link( WebFinger.Link(
"self", "self",
"application/activity+json", "application/activity+json",
applicationConfig.url + "/users/" + user.id user.url
) )
) )
) )

View File

@ -1,21 +1,25 @@
package dev.usbharu.hideout.domain.model.job package dev.usbharu.hideout.domain.model.job
import kjob.core.Job import kjob.core.Job
import org.springframework.stereotype.Component
sealed class HideoutJob(name: String = "") : Job(name) sealed class HideoutJob(name: String = "") : Job(name)
@Component
object ReceiveFollowJob : HideoutJob("ReceiveFollowJob") { object ReceiveFollowJob : HideoutJob("ReceiveFollowJob") {
val actor = string("actor") val actor = string("actor")
val follow = string("follow") val follow = string("follow")
val targetActor = string("targetActor") val targetActor = string("targetActor")
} }
@Component
object DeliverPostJob : HideoutJob("DeliverPostJob") { object DeliverPostJob : HideoutJob("DeliverPostJob") {
val post = string("post") val post = string("post")
val actor = string("actor") val actor = string("actor")
val inbox = string("inbox") val inbox = string("inbox")
} }
@Component
object DeliverReactionJob : HideoutJob("DeliverReactionJob") { object DeliverReactionJob : HideoutJob("DeliverReactionJob") {
val reaction = string("reaction") val reaction = string("reaction")
val postUrl = string("postUrl") val postUrl = string("postUrl")
@ -24,6 +28,7 @@ object DeliverReactionJob : HideoutJob("DeliverReactionJob") {
val id = string("id") val id = string("id")
} }
@Component
object DeliverRemoveReactionJob : HideoutJob("DeliverRemoveReactionJob") { object DeliverRemoveReactionJob : HideoutJob("DeliverRemoveReactionJob") {
val id = string("id") val id = string("id")
val inbox = string("inbox") val inbox = string("inbox")

View File

@ -1,6 +1,7 @@
package dev.usbharu.hideout.plugins package dev.usbharu.hideout.plugins
import dev.usbharu.hideout.config.Config import com.fasterxml.jackson.databind.ObjectMapper
import dev.usbharu.hideout.config.ApplicationConfig
import dev.usbharu.hideout.domain.model.ap.JsonLd import dev.usbharu.hideout.domain.model.ap.JsonLd
import dev.usbharu.hideout.query.UserQueryService import dev.usbharu.hideout.query.UserQueryService
import dev.usbharu.hideout.service.core.Transaction import dev.usbharu.hideout.service.core.Transaction
@ -26,13 +27,18 @@ import java.text.SimpleDateFormat
import java.util.* import java.util.*
import javax.crypto.SecretKey import javax.crypto.SecretKey
suspend fun HttpClient.postAp(urlString: String, username: String, jsonLd: JsonLd): HttpResponse { suspend fun HttpClient.postAp(
urlString: String,
username: String,
jsonLd: JsonLd,
objectMapper: ObjectMapper
): HttpResponse {
jsonLd.context += "https://www.w3.org/ns/activitystreams" jsonLd.context += "https://www.w3.org/ns/activitystreams"
return this.post(urlString) { return this.post(urlString) {
header("Accept", ContentType.Application.Activity) header("Accept", ContentType.Application.Activity)
header("Content-Type", ContentType.Application.Activity) header("Content-Type", ContentType.Application.Activity)
header("Signature", "keyId=\"$username\",algorithm=\"rsa-sha256\",headers=\"(request-target) digest date\"") header("Signature", "keyId=\"$username\",algorithm=\"rsa-sha256\",headers=\"(request-target) digest date\"")
val text = Config.configData.objectMapper.writeValueAsString(jsonLd) val text = objectMapper.writeValueAsString(jsonLd)
setBody(text) setBody(text)
} }
} }
@ -157,7 +163,11 @@ val httpSignaturePlugin = createClientPlugin("HttpSign", ::HttpSignaturePluginCo
} }
} }
class KtorKeyMap(private val userQueryService: UserQueryService, private val transaction: Transaction) : KeyMap { class KtorKeyMap(
private val userQueryService: UserQueryService,
private val transaction: Transaction,
private val applicationConfig: ApplicationConfig
) : KeyMap {
override fun getPublicKey(keyId: String?): PublicKey = runBlocking { override fun getPublicKey(keyId: String?): PublicKey = runBlocking {
val username = (keyId ?: throw IllegalArgumentException("keyId is null")).substringBeforeLast("#pubkey") val username = (keyId ?: throw IllegalArgumentException("keyId is null")).substringBeforeLast("#pubkey")
.substringAfterLast("/") .substringAfterLast("/")
@ -165,7 +175,7 @@ class KtorKeyMap(private val userQueryService: UserQueryService, private val tra
transaction.transaction { transaction.transaction {
userQueryService.findByNameAndDomain( userQueryService.findByNameAndDomain(
username, username,
Config.configData.domain applicationConfig.url.host
).run { ).run {
publicKey publicKey
.replace("-----BEGIN PUBLIC KEY-----", "") .replace("-----BEGIN PUBLIC KEY-----", "")
@ -185,7 +195,7 @@ class KtorKeyMap(private val userQueryService: UserQueryService, private val tra
transaction.transaction { transaction.transaction {
userQueryService.findByNameAndDomain( userQueryService.findByNameAndDomain(
username, username,
Config.configData.domain applicationConfig.url.host
).privateKey?.run { ).privateKey?.run {
replace("-----BEGIN PRIVATE KEY-----", "") replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "") .replace("-----END PRIVATE KEY-----", "")

View File

@ -1,7 +1,8 @@
package dev.usbharu.hideout.service.ap package dev.usbharu.hideout.service.ap
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue import com.fasterxml.jackson.module.kotlin.readValue
import dev.usbharu.hideout.config.Config import dev.usbharu.hideout.config.ApplicationConfig
import dev.usbharu.hideout.domain.model.ap.Create import dev.usbharu.hideout.domain.model.ap.Create
import dev.usbharu.hideout.domain.model.ap.Note import dev.usbharu.hideout.domain.model.ap.Note
import dev.usbharu.hideout.domain.model.hideout.entity.Post import dev.usbharu.hideout.domain.model.hideout.entity.Post
@ -20,6 +21,7 @@ import io.ktor.client.*
import io.ktor.client.statement.* import io.ktor.client.statement.*
import kjob.core.job.JobProps import kjob.core.job.JobProps
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import java.time.Instant import java.time.Instant
@ -34,14 +36,17 @@ interface APNoteService {
} }
@Service @Service
class APNoteServiceImpl( class APNoteServiceImpl private constructor(
private val httpClient: HttpClient, private val httpClient: HttpClient,
private val jobQueueParentService: JobQueueParentService, private val jobQueueParentService: JobQueueParentService,
private val postRepository: PostRepository, private val postRepository: PostRepository,
private val apUserService: APUserService, private val apUserService: APUserService,
private val userQueryService: UserQueryService, private val userQueryService: UserQueryService,
private val followerQueryService: FollowerQueryService, private val followerQueryService: FollowerQueryService,
private val postQueryService: PostQueryService private val postQueryService: PostQueryService,
@Qualifier("activitypub") private val objectMapper: ObjectMapper,
private val applicationConfig: ApplicationConfig
) : APNoteService { ) : APNoteService {
private val logger = LoggerFactory.getLogger(this::class.java) private val logger = LoggerFactory.getLogger(this::class.java)
@ -49,7 +54,7 @@ class APNoteServiceImpl(
override suspend fun createNote(post: Post) { override suspend fun createNote(post: Post) {
val followers = followerQueryService.findFollowersById(post.userId) val followers = followerQueryService.findFollowersById(post.userId)
val userEntity = userQueryService.findById(post.userId) val userEntity = userQueryService.findById(post.userId)
val note = Config.configData.objectMapper.writeValueAsString(post) val note = objectMapper.writeValueAsString(post)
followers.forEach { followerEntity -> followers.forEach { followerEntity ->
jobQueueParentService.schedule(DeliverPostJob) { jobQueueParentService.schedule(DeliverPostJob) {
props[DeliverPostJob.actor] = userEntity.url props[DeliverPostJob.actor] = userEntity.url
@ -61,7 +66,7 @@ class APNoteServiceImpl(
override suspend fun createNoteJob(props: JobProps<DeliverPostJob>) { override suspend fun createNoteJob(props: JobProps<DeliverPostJob>) {
val actor = props[DeliverPostJob.actor] val actor = props[DeliverPostJob.actor]
val postEntity = Config.configData.objectMapper.readValue<Post>(props[DeliverPostJob.post]) val postEntity = objectMapper.readValue<Post>(props[DeliverPostJob.post])
val note = Note( val note = Note(
name = "Note", name = "Note",
id = postEntity.url, id = postEntity.url,
@ -79,8 +84,9 @@ class APNoteServiceImpl(
name = "Create Note", name = "Create Note",
`object` = note, `object` = note,
actor = note.attributedTo, actor = note.attributedTo,
id = "${Config.configData.url}/create/note/${postEntity.id}" id = "${applicationConfig.url}/create/note/${postEntity.id}"
) ),
objectMapper
) )
} }
@ -95,7 +101,7 @@ class APNoteServiceImpl(
url, url,
targetActor?.let { "$targetActor#pubkey" } targetActor?.let { "$targetActor#pubkey" }
) )
val note = Config.configData.objectMapper.readValue<Note>(response.bodyAsText()) val note = objectMapper.readValue<Note>(response.bodyAsText())
return note(note, targetActor, url) return note(note, targetActor, url)
} }

View File

@ -1,5 +1,6 @@
package dev.usbharu.hideout.service.ap package dev.usbharu.hideout.service.ap
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue import com.fasterxml.jackson.module.kotlin.readValue
import dev.usbharu.hideout.config.Config import dev.usbharu.hideout.config.Config
import dev.usbharu.hideout.domain.model.ap.Like import dev.usbharu.hideout.domain.model.ap.Like
@ -14,6 +15,7 @@ import dev.usbharu.hideout.query.UserQueryService
import dev.usbharu.hideout.service.job.JobQueueParentService import dev.usbharu.hideout.service.job.JobQueueParentService
import io.ktor.client.* import io.ktor.client.*
import kjob.core.job.JobProps import kjob.core.job.JobProps
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import java.time.Instant import java.time.Instant
@ -31,8 +33,10 @@ class APReactionServiceImpl(
private val httpClient: HttpClient, private val httpClient: HttpClient,
private val userQueryService: UserQueryService, private val userQueryService: UserQueryService,
private val followerQueryService: FollowerQueryService, private val followerQueryService: FollowerQueryService,
private val postQueryService: PostQueryService private val postQueryService: PostQueryService,
) : APReactionService { @Qualifier("activitypub") private val objectMapper: ObjectMapper,
) : APReactionService {
override suspend fun reaction(like: Reaction) { override suspend fun reaction(like: Reaction) {
val followers = followerQueryService.findFollowersById(like.userId) val followers = followerQueryService.findFollowersById(like.userId)
val user = userQueryService.findById(like.userId) val user = userQueryService.findById(like.userId)
@ -79,7 +83,7 @@ class APReactionServiceImpl(
`object` = postUrl, `object` = postUrl,
id = "${Config.configData.url}/like/note/$id", id = "${Config.configData.url}/like/note/$id",
content = content content = content
) ), objectMapper
) )
} }
@ -96,7 +100,7 @@ class APReactionServiceImpl(
`object` = like, `object` = like,
id = "${Config.configData.url}/undo/note/${like.id}", id = "${Config.configData.url}/undo/note/${like.id}",
published = Instant.now() published = Instant.now()
) ), objectMapper
) )
} }
} }

View File

@ -1,7 +1,7 @@
package dev.usbharu.hideout.service.ap package dev.usbharu.hideout.service.ap
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue import com.fasterxml.jackson.module.kotlin.readValue
import dev.usbharu.hideout.config.Config
import dev.usbharu.hideout.domain.model.ActivityPubResponse import dev.usbharu.hideout.domain.model.ActivityPubResponse
import dev.usbharu.hideout.domain.model.ActivityPubStringResponse import dev.usbharu.hideout.domain.model.ActivityPubStringResponse
import dev.usbharu.hideout.domain.model.ap.Accept import dev.usbharu.hideout.domain.model.ap.Accept
@ -15,9 +15,10 @@ import dev.usbharu.hideout.service.user.UserService
import io.ktor.client.* import io.ktor.client.*
import io.ktor.http.* import io.ktor.http.*
import kjob.core.job.JobProps import kjob.core.job.JobProps
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
@Service
interface APReceiveFollowService { interface APReceiveFollowService {
suspend fun receiveFollow(follow: Follow): ActivityPubResponse suspend fun receiveFollow(follow: Follow): ActivityPubResponse
suspend fun receiveFollowJob(props: JobProps<ReceiveFollowJob>) suspend fun receiveFollowJob(props: JobProps<ReceiveFollowJob>)
@ -30,24 +31,26 @@ class APReceiveFollowServiceImpl(
private val userService: UserService, private val userService: UserService,
private val httpClient: HttpClient, private val httpClient: HttpClient,
private val userQueryService: UserQueryService, private val userQueryService: UserQueryService,
private val transaction: Transaction private val transaction: Transaction,
@Qualifier("activitypub") private val objectMapper: ObjectMapper
) : APReceiveFollowService { ) : APReceiveFollowService {
override suspend fun receiveFollow(follow: Follow): ActivityPubResponse { override suspend fun receiveFollow(follow: Follow): ActivityPubResponse {
// TODO: Verify HTTP Signature // TODO: Verify HTTP Signature
jobQueueParentService.schedule(ReceiveFollowJob) { jobQueueParentService.schedule(ReceiveFollowJob) {
props[ReceiveFollowJob.actor] = follow.actor props[ReceiveFollowJob.actor] = follow.actor
props[ReceiveFollowJob.follow] = Config.configData.objectMapper.writeValueAsString(follow) props[ReceiveFollowJob.follow] = objectMapper.writeValueAsString(follow)
props[ReceiveFollowJob.targetActor] = follow.`object` props[ReceiveFollowJob.targetActor] = follow.`object`
} }
return ActivityPubStringResponse(HttpStatusCode.OK, "{}", ContentType.Application.Json) return ActivityPubStringResponse(HttpStatusCode.OK, "{}", ContentType.Application.Json)
} }
override suspend fun receiveFollowJob(props: JobProps<ReceiveFollowJob>) { override suspend fun receiveFollowJob(props: JobProps<ReceiveFollowJob>) {
// throw Exception()
transaction.transaction { transaction.transaction {
val actor = props[ReceiveFollowJob.actor] val actor = props[ReceiveFollowJob.actor]
val targetActor = props[ReceiveFollowJob.targetActor] val targetActor = props[ReceiveFollowJob.targetActor]
val person = apUserService.fetchPerson(actor, targetActor) val person = apUserService.fetchPerson(actor, targetActor)
val follow = Config.configData.objectMapper.readValue<Follow>(props[ReceiveFollowJob.follow]) val follow = objectMapper.readValue<Follow>(props[ReceiveFollowJob.follow])
httpClient.postAp( httpClient.postAp(
urlString = person.inbox ?: throw IllegalArgumentException("inbox is not found"), urlString = person.inbox ?: throw IllegalArgumentException("inbox is not found"),
username = "$targetActor#pubkey", username = "$targetActor#pubkey",
@ -55,7 +58,7 @@ class APReceiveFollowServiceImpl(
name = "Follow", name = "Follow",
`object` = follow, `object` = follow,
actor = targetActor actor = targetActor
) ), objectMapper
) )
val targetEntity = userQueryService.findByUrl(targetActor) val targetEntity = userQueryService.findByUrl(targetActor)

View File

@ -1,9 +1,11 @@
package dev.usbharu.hideout.service.ap package dev.usbharu.hideout.service.ap
import com.fasterxml.jackson.databind.ObjectMapper
import dev.usbharu.hideout.domain.model.ap.Follow import dev.usbharu.hideout.domain.model.ap.Follow
import dev.usbharu.hideout.domain.model.hideout.dto.SendFollowDto import dev.usbharu.hideout.domain.model.hideout.dto.SendFollowDto
import dev.usbharu.hideout.plugins.postAp import dev.usbharu.hideout.plugins.postAp
import io.ktor.client.* import io.ktor.client.*
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
@Service @Service
@ -12,7 +14,10 @@ interface APSendFollowService {
} }
@Service @Service
class APSendFollowServiceImpl(private val httpClient: HttpClient) : APSendFollowService { class APSendFollowServiceImpl(
private val httpClient: HttpClient,
@Qualifier("activitypub") private val objectMapper: ObjectMapper,
) : APSendFollowService {
override suspend fun sendFollow(sendFollowDto: SendFollowDto) { override suspend fun sendFollow(sendFollowDto: SendFollowDto) {
val follow = Follow( val follow = Follow(
name = "Follow", name = "Follow",
@ -22,7 +27,8 @@ class APSendFollowServiceImpl(private val httpClient: HttpClient) : APSendFollow
httpClient.postAp( httpClient.postAp(
urlString = sendFollowDto.followTargetUserId.inbox, urlString = sendFollowDto.followTargetUserId.inbox,
username = sendFollowDto.userId.url, username = sendFollowDto.userId.url,
jsonLd = follow jsonLd = follow,
objectMapper
) )
} }
} }

View File

@ -1,8 +1,8 @@
package dev.usbharu.hideout.service.ap package dev.usbharu.hideout.service.ap
import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue import com.fasterxml.jackson.module.kotlin.readValue
import dev.usbharu.hideout.config.Config
import dev.usbharu.hideout.domain.model.ActivityPubResponse import dev.usbharu.hideout.domain.model.ActivityPubResponse
import dev.usbharu.hideout.domain.model.ap.Follow import dev.usbharu.hideout.domain.model.ap.Follow
import dev.usbharu.hideout.domain.model.job.* import dev.usbharu.hideout.domain.model.job.*
@ -11,6 +11,7 @@ import kjob.core.dsl.JobContextWithProps
import kjob.core.job.JobProps import kjob.core.job.JobProps
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
@Service @Service
@ -181,12 +182,13 @@ class APServiceImpl(
private val apAcceptService: APAcceptService, private val apAcceptService: APAcceptService,
private val apCreateService: APCreateService, private val apCreateService: APCreateService,
private val apLikeService: APLikeService, private val apLikeService: APLikeService,
private val apReactionService: APReactionService private val apReactionService: APReactionService,
@Qualifier("activitypub") private val objectMapper: ObjectMapper
) : APService { ) : APService {
val logger: Logger = LoggerFactory.getLogger(this::class.java) val logger: Logger = LoggerFactory.getLogger(this::class.java)
override fun parseActivity(json: String): ActivityType { override fun parseActivity(json: String): ActivityType {
val readTree = Config.configData.objectMapper.readTree(json) val readTree = objectMapper.readTree(json)
logger.trace("readTree: {}", readTree) logger.trace("readTree: {}", readTree)
if (readTree.isObject.not()) { if (readTree.isObject.not()) {
throw JsonParseException("Json is not object.") throw JsonParseException("Json is not object.")
@ -204,17 +206,17 @@ class APServiceImpl(
override suspend fun processActivity(json: String, type: ActivityType): ActivityPubResponse { override suspend fun processActivity(json: String, type: ActivityType): ActivityPubResponse {
logger.debug("proccess activity: {}", type) logger.debug("proccess activity: {}", type)
return when (type) { return when (type) {
ActivityType.Accept -> apAcceptService.receiveAccept(Config.configData.objectMapper.readValue(json)) ActivityType.Accept -> apAcceptService.receiveAccept(objectMapper.readValue(json))
ActivityType.Follow -> apReceiveFollowService.receiveFollow( ActivityType.Follow -> apReceiveFollowService.receiveFollow(
Config.configData.objectMapper.readValue( objectMapper.readValue(
json, json,
Follow::class.java Follow::class.java
) )
) )
ActivityType.Create -> apCreateService.receiveCreate(Config.configData.objectMapper.readValue(json)) ActivityType.Create -> apCreateService.receiveCreate(objectMapper.readValue(json))
ActivityType.Like -> apLikeService.receiveLike(Config.configData.objectMapper.readValue(json)) ActivityType.Like -> apLikeService.receiveLike(objectMapper.readValue(json))
ActivityType.Undo -> apUndoService.receiveUndo(Config.configData.objectMapper.readValue(json)) ActivityType.Undo -> apUndoService.receiveUndo(objectMapper.readValue(json))
else -> { else -> {
throw IllegalArgumentException("$type is not supported.") throw IllegalArgumentException("$type is not supported.")
@ -224,16 +226,25 @@ class APServiceImpl(
override suspend fun <T : HideoutJob> processActivity(job: JobContextWithProps<T>, hideoutJob: HideoutJob) { override suspend fun <T : HideoutJob> processActivity(job: JobContextWithProps<T>, hideoutJob: HideoutJob) {
logger.debug("processActivity: ${hideoutJob.name}") logger.debug("processActivity: ${hideoutJob.name}")
when (hideoutJob) {
ReceiveFollowJob -> apReceiveFollowService.receiveFollowJob( // println(apReceiveFollowService::class.java)
// apReceiveFollowService.receiveFollowJob(job.props as JobProps<ReceiveFollowJob>)
when {
hideoutJob is ReceiveFollowJob -> {
apReceiveFollowService.receiveFollowJob(
job.props as JobProps<ReceiveFollowJob> job.props as JobProps<ReceiveFollowJob>
) )
}
DeliverPostJob -> apNoteService.createNoteJob(job.props as JobProps<DeliverPostJob>) hideoutJob is DeliverPostJob -> apNoteService.createNoteJob(job.props as JobProps<DeliverPostJob>)
DeliverReactionJob -> apReactionService.reactionJob(job.props as JobProps<DeliverReactionJob>) hideoutJob is DeliverReactionJob -> apReactionService.reactionJob(job.props as JobProps<DeliverReactionJob>)
DeliverRemoveReactionJob -> apReactionService.removeReactionJob( hideoutJob is DeliverRemoveReactionJob -> apReactionService.removeReactionJob(
job.props as JobProps<DeliverRemoveReactionJob> job.props as JobProps<DeliverRemoveReactionJob>
) )
else -> {
throw IllegalStateException("WTF")
}
} }
} }
} }

View File

@ -1,7 +1,8 @@
package dev.usbharu.hideout.service.ap package dev.usbharu.hideout.service.ap
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue import com.fasterxml.jackson.module.kotlin.readValue
import dev.usbharu.hideout.config.Config import dev.usbharu.hideout.config.ApplicationConfig
import dev.usbharu.hideout.domain.model.ap.Image import dev.usbharu.hideout.domain.model.ap.Image
import dev.usbharu.hideout.domain.model.ap.Key import dev.usbharu.hideout.domain.model.ap.Key
import dev.usbharu.hideout.domain.model.ap.Person import dev.usbharu.hideout.domain.model.ap.Person
@ -18,6 +19,7 @@ import io.ktor.client.*
import io.ktor.client.request.* import io.ktor.client.request.*
import io.ktor.client.statement.* import io.ktor.client.statement.*
import io.ktor.http.* import io.ktor.http.*
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
@Service @Service
@ -41,16 +43,18 @@ class APUserServiceImpl(
private val userService: UserService, private val userService: UserService,
private val httpClient: HttpClient, private val httpClient: HttpClient,
private val userQueryService: UserQueryService, private val userQueryService: UserQueryService,
private val transaction: Transaction private val transaction: Transaction,
private val applicationConfig: ApplicationConfig,
@Qualifier("activitypub") private val objectMapper: ObjectMapper
) : ) :
APUserService { APUserService {
override suspend fun getPersonByName(name: String): Person { override suspend fun getPersonByName(name: String): Person {
val userEntity = transaction.transaction { val userEntity = transaction.transaction {
userQueryService.findByNameAndDomain(name, Config.configData.domain) userQueryService.findByNameAndDomain(name, applicationConfig.url.host)
} }
// TODO: JOINで書き直し // TODO: JOINで書き直し
val userUrl = "${Config.configData.url}/users/$name" val userUrl = "${applicationConfig.url}/users/$name"
return Person( return Person(
type = emptyList(), type = emptyList(),
name = userEntity.name, name = userEntity.name,
@ -73,7 +77,7 @@ class APUserServiceImpl(
owner = userUrl, owner = userUrl,
publicKeyPem = userEntity.publicKey publicKeyPem = userEntity.publicKey
), ),
endpoints = mapOf("sharedInbox" to "${Config.configData.url}/inbox") endpoints = mapOf("sharedInbox" to "${applicationConfig.url}/inbox")
) )
} }
@ -105,7 +109,7 @@ class APUserServiceImpl(
owner = url, owner = url,
publicKeyPem = userEntity.publicKey publicKeyPem = userEntity.publicKey
), ),
endpoints = mapOf("sharedInbox" to "${Config.configData.url}/inbox") endpoints = mapOf("sharedInbox" to "${applicationConfig.url}/inbox")
) to userEntity ) to userEntity
} catch (ignore: FailedToGetResourcesException) { } catch (ignore: FailedToGetResourcesException) {
val httpResponse = if (targetActor != null) { val httpResponse = if (targetActor != null) {
@ -115,7 +119,7 @@ class APUserServiceImpl(
accept(ContentType.Application.Activity) accept(ContentType.Application.Activity)
} }
} }
val person = Config.configData.objectMapper.readValue<Person>(httpResponse.bodyAsText()) val person = objectMapper.readValue<Person>(httpResponse.bodyAsText())
person to userService.createRemoteUser( person to userService.createRemoteUser(
RemoteUserCreateDto( RemoteUserCreateDto(

View File

@ -3,7 +3,6 @@ package dev.usbharu.hideout.service.api.mastodon
import dev.usbharu.hideout.config.ApplicationConfig import dev.usbharu.hideout.config.ApplicationConfig
import dev.usbharu.hideout.domain.mastodon.model.generated.* import dev.usbharu.hideout.domain.mastodon.model.generated.*
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import java.net.URL
@Service @Service
interface InstanceApiService { interface InstanceApiService {
@ -14,15 +13,14 @@ interface InstanceApiService {
class InstanceApiServiceImpl(private val applicationConfig: ApplicationConfig) : InstanceApiService { class InstanceApiServiceImpl(private val applicationConfig: ApplicationConfig) : InstanceApiService {
override suspend fun v1Instance(): V1Instance { override suspend fun v1Instance(): V1Instance {
val url = applicationConfig.url val url = applicationConfig.url
val url1 = URL(url)
return V1Instance( return V1Instance(
uri = url1.host, uri = url.host,
title = "Hideout Server", title = "Hideout Server",
shortDescription = "Hideout test server", shortDescription = "Hideout test server",
description = "This server is operated for testing of Hideout. We are not responsible for any events that occur when associating with this server", description = "This server is operated for testing of Hideout. We are not responsible for any events that occur when associating with this server",
email = "i@usbharu.dev", email = "i@usbharu.dev",
version = "0.0.1", version = "0.0.1",
urls = V1InstanceUrls("wss://${url1.host}"), urls = V1InstanceUrls("wss://${url.host}"),
stats = V1InstanceStats(1, 0, 0), stats = V1InstanceStats(1, 0, 0),
thumbnail = null, thumbnail = null,
languages = listOf("ja-JP"), languages = listOf("ja-JP"),

View File

@ -1,5 +1,6 @@
package dev.usbharu.hideout.service.auth package dev.usbharu.hideout.service.auth
import dev.usbharu.hideout.config.ApplicationConfig
import dev.usbharu.hideout.plugins.KtorKeyMap import dev.usbharu.hideout.plugins.KtorKeyMap
import dev.usbharu.hideout.query.UserQueryService import dev.usbharu.hideout.query.UserQueryService
import dev.usbharu.hideout.service.core.Transaction import dev.usbharu.hideout.service.core.Transaction
@ -15,10 +16,13 @@ interface HttpSignatureVerifyService {
@Service @Service
class HttpSignatureVerifyServiceImpl( class HttpSignatureVerifyServiceImpl(
private val userQueryService: UserQueryService, private val userQueryService: UserQueryService,
private val transaction: Transaction private val transaction: Transaction,
private val applicationConfig: ApplicationConfig
) : HttpSignatureVerifyService { ) : HttpSignatureVerifyService {
override fun verify(headers: Headers): Boolean { override fun verify(headers: Headers): Boolean {
val build = SignatureHeaderVerifier.builder().keyMap(KtorKeyMap(userQueryService, transaction)).build() val build =
SignatureHeaderVerifier.builder().keyMap(KtorKeyMap(userQueryService, transaction, applicationConfig))
.build()
return true return true
// build.verify(object : HttpMessage { // build.verify(object : HttpMessage {
// override fun headerValues(name: String?): MutableList<String> { // override fun headerValues(name: String?): MutableList<String> {

View File

@ -9,7 +9,6 @@ import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.core.userdetails.UserDetailsService import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.core.userdetails.UsernameNotFoundException import org.springframework.security.core.userdetails.UsernameNotFoundException
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import java.net.URL
@Service @Service
class UserDetailsServiceImpl( class UserDetailsServiceImpl(
@ -23,7 +22,7 @@ class UserDetailsServiceImpl(
throw UsernameNotFoundException("$username not found") throw UsernameNotFoundException("$username not found")
} }
transaction.transaction { transaction.transaction {
val findById = userQueryService.findByNameAndDomain(username, URL(applicationConfig.url).host) val findById = userQueryService.findByNameAndDomain(username, applicationConfig.url.host)
UserDetailsImpl( UserDetailsImpl(
findById.id, findById.id,
findById.name, findById.name,

View File

@ -1,6 +1,6 @@
package dev.usbharu.hideout.service.job package dev.usbharu.hideout.service.job
import kjob.core.Job import dev.usbharu.hideout.domain.model.job.HideoutJob
import kjob.core.dsl.KJobFunctions import kjob.core.dsl.KJobFunctions
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import kjob.core.dsl.JobContextWithProps as JCWP import kjob.core.dsl.JobContextWithProps as JCWP
@ -8,5 +8,5 @@ import kjob.core.dsl.JobRegisterContext as JRC
@Service @Service
interface JobQueueWorkerService { interface JobQueueWorkerService {
fun init(defines: List<Pair<Job, JRC<Job, JCWP<Job>>.(Job) -> KJobFunctions<Job, JCWP<Job>>>>) fun init(defines: List<Pair<HideoutJob, JRC<HideoutJob, JCWP<HideoutJob>>.(HideoutJob) -> KJobFunctions<HideoutJob, JCWP<HideoutJob>>>>)
} }

View File

@ -1,13 +1,13 @@
package dev.usbharu.hideout.service.job package dev.usbharu.hideout.service.job
import dev.usbharu.hideout.domain.model.job.HideoutJob
import dev.usbharu.kjob.exposed.ExposedKJob import dev.usbharu.kjob.exposed.ExposedKJob
import kjob.core.Job import kjob.core.dsl.JobContextWithProps
import kjob.core.dsl.JobRegisterContext
import kjob.core.dsl.KJobFunctions import kjob.core.dsl.KJobFunctions
import kjob.core.kjob import kjob.core.kjob
import org.jetbrains.exposed.sql.Database import org.jetbrains.exposed.sql.Database
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import kjob.core.dsl.JobContextWithProps as JCWP
import kjob.core.dsl.JobRegisterContext as JRC
@Service @Service
class KJobJobQueueWorkerService(private val database: Database) : JobQueueWorkerService { class KJobJobQueueWorkerService(private val database: Database) : JobQueueWorkerService {
@ -21,9 +21,7 @@ class KJobJobQueueWorkerService(private val database: Database) : JobQueueWorker
}.start() }.start()
} }
override fun init( override fun init(defines: List<Pair<HideoutJob, JobRegisterContext<HideoutJob, JobContextWithProps<HideoutJob>>.(HideoutJob) -> KJobFunctions<HideoutJob, JobContextWithProps<HideoutJob>>>>) {
defines: List<Pair<Job, JRC<Job, JCWP<Job>>.(Job) -> KJobFunctions<Job, JCWP<Job>>>>
) {
defines.forEach { job -> defines.forEach { job ->
kjob.register(job.first, job.second) kjob.register(job.first, job.second)
} }

View File

@ -1,6 +1,6 @@
package dev.usbharu.hideout.service.user package dev.usbharu.hideout.service.user
import dev.usbharu.hideout.config.Config import dev.usbharu.hideout.config.ApplicationConfig
import dev.usbharu.hideout.domain.model.hideout.dto.RemoteUserCreateDto import dev.usbharu.hideout.domain.model.hideout.dto.RemoteUserCreateDto
import dev.usbharu.hideout.domain.model.hideout.dto.SendFollowDto import dev.usbharu.hideout.domain.model.hideout.dto.SendFollowDto
import dev.usbharu.hideout.domain.model.hideout.dto.UserCreateDto import dev.usbharu.hideout.domain.model.hideout.dto.UserCreateDto
@ -19,12 +19,13 @@ class UserServiceImpl(
private val userAuthService: UserAuthService, private val userAuthService: UserAuthService,
private val apSendFollowService: APSendFollowService, private val apSendFollowService: APSendFollowService,
private val userQueryService: UserQueryService, private val userQueryService: UserQueryService,
private val followerQueryService: FollowerQueryService private val followerQueryService: FollowerQueryService,
private val applicationConfig: ApplicationConfig
) : ) :
UserService { UserService {
override suspend fun usernameAlreadyUse(username: String): Boolean { override suspend fun usernameAlreadyUse(username: String): Boolean {
val findByNameAndDomain = userQueryService.findByNameAndDomain(username, Config.configData.domain) val findByNameAndDomain = userQueryService.findByNameAndDomain(username, applicationConfig.url.host)
return findByNameAndDomain != null return findByNameAndDomain != null
} }
@ -35,13 +36,13 @@ class UserServiceImpl(
val userEntity = User.of( val userEntity = User.of(
id = nextId, id = nextId,
name = user.name, name = user.name,
domain = Config.configData.domain, domain = applicationConfig.url.host,
screenName = user.screenName, screenName = user.screenName,
description = user.description, description = user.description,
password = hashedPassword, password = hashedPassword,
inbox = "${Config.configData.url}/users/${user.name}/inbox", inbox = "${applicationConfig.url}/users/${user.name}/inbox",
outbox = "${Config.configData.url}/users/${user.name}/outbox", outbox = "${applicationConfig.url}/users/${user.name}/outbox",
url = "${Config.configData.url}/users/${user.name}", url = "${applicationConfig.url}/users/${user.name}",
publicKey = keyPair.public.toPem(), publicKey = keyPair.public.toPem(),
privateKey = keyPair.private.toPem(), privateKey = keyPair.private.toPem(),
createdAt = Instant.now() createdAt = Instant.now()
@ -70,7 +71,7 @@ class UserServiceImpl(
override suspend fun followRequest(id: Long, followerId: Long): Boolean { override suspend fun followRequest(id: Long, followerId: Long): Boolean {
val user = userRepository.findById(id) ?: throw UserNotFoundException("$id was not found.") val user = userRepository.findById(id) ?: throw UserNotFoundException("$id was not found.")
val follower = userRepository.findById(followerId) ?: throw UserNotFoundException("$followerId was not found.") val follower = userRepository.findById(followerId) ?: throw UserNotFoundException("$followerId was not found.")
return if (user.domain == Config.configData.domain) { return if (user.domain == applicationConfig.url.host) {
follow(id, followerId) follow(id, followerId)
true true
} else { } else {

View File

@ -4,7 +4,7 @@
<pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> <pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder> </encoder>
</appender> </appender>
<root level="TRACE"> <root level="DEBUG">
<appender-ref ref="STDOUT"/> <appender-ref ref="STDOUT"/>
</root> </root>
<logger name="org.eclipse.jetty" level="INFO"/> <logger name="org.eclipse.jetty" level="INFO"/>
@ -12,5 +12,5 @@
<logger name="kjob.core.internal.scheduler.JobServiceImpl" level="INFO"/> <logger name="kjob.core.internal.scheduler.JobServiceImpl" level="INFO"/>
<logger name="Exposed" level="INFO"/> <logger name="Exposed" level="INFO"/>
<logger name="io.ktor.server.plugins.contentnegotiation" level="INFO"/> <logger name="io.ktor.server.plugins.contentnegotiation" level="INFO"/>
<logger name="org.springframework.security" level="trace"/> <logger name="org.springframework.security" level="DEBUG"/>
</configuration> </configuration>