feat: トランザクションエラーがある程度発生しないように

This commit is contained in:
usbharu 2023-08-11 15:47:25 +09:00
parent ee8bbf5091
commit 37db201f87
13 changed files with 119 additions and 64 deletions

View File

@ -20,6 +20,7 @@ import dev.usbharu.hideout.service.activitypub.ActivityPubUserService
import dev.usbharu.hideout.service.api.IPostApiService
import dev.usbharu.hideout.service.api.IUserApiService
import dev.usbharu.hideout.service.api.UserAuthApiService
import dev.usbharu.hideout.service.api.WebFingerApiService
import dev.usbharu.hideout.service.auth.HttpSignatureVerifyService
import dev.usbharu.hideout.service.core.*
import dev.usbharu.hideout.service.job.JobQueueParentService
@ -75,7 +76,7 @@ fun Application.parent() {
level = LogLevel.INFO
}
install(httpSignaturePlugin) {
keyMap = KtorKeyMap(get())
keyMap = KtorKeyMap(get(), get())
}
expectSuccess = true
}
@ -116,6 +117,7 @@ fun Application.parent() {
userQueryService = inject<UserQueryService>().value,
followerQueryService = inject<FollowerQueryService>().value,
userAuthApiService = inject<UserAuthApiService>().value,
webFingerApiService = inject<WebFingerApiService>().value,
transaction = inject<Transaction>().value
)
}

View File

@ -3,6 +3,7 @@ package dev.usbharu.hideout.plugins
import dev.usbharu.hideout.config.Config
import dev.usbharu.hideout.domain.model.ap.JsonLd
import dev.usbharu.hideout.query.UserQueryService
import dev.usbharu.hideout.service.core.Transaction
import dev.usbharu.hideout.service.user.UserAuthService
import dev.usbharu.hideout.util.HttpUtil.Activity
import io.ktor.client.*
@ -164,19 +165,21 @@ val httpSignaturePlugin = createClientPlugin("HttpSign", ::HttpSignaturePluginCo
}
}
class KtorKeyMap(private val userQueryService: UserQueryService) : KeyMap {
class KtorKeyMap(private val userQueryService: UserQueryService, private val transaction: Transaction) : KeyMap {
override fun getPublicKey(keyId: String?): PublicKey = runBlocking {
val username = (keyId ?: throw IllegalArgumentException("keyId is null")).substringBeforeLast("#pubkey")
.substringAfterLast("/")
val publicBytes = Base64.getDecoder().decode(
userQueryService.findByNameAndDomain(
username,
Config.configData.domain
).run {
publicKey
.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "")
.replace("\n", "")
transaction.transaction {
userQueryService.findByNameAndDomain(
username,
Config.configData.domain
).run {
publicKey
.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "")
.replace("\n", "")
}
}
)
val x509EncodedKeySpec = X509EncodedKeySpec(publicBytes)
@ -187,13 +190,15 @@ class KtorKeyMap(private val userQueryService: UserQueryService) : KeyMap {
val username = (keyId ?: throw IllegalArgumentException("keyId is null")).substringBeforeLast("#pubkey")
.substringAfterLast("/")
val publicBytes = Base64.getDecoder().decode(
userQueryService.findByNameAndDomain(
username,
Config.configData.domain
).privateKey?.run {
replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replace("\n", "")
transaction.transaction {
userQueryService.findByNameAndDomain(
username,
Config.configData.domain
).privateKey?.run {
replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replace("\n", "")
}
}
)
val x509EncodedKeySpec = PKCS8EncodedKeySpec(publicBytes)

View File

@ -14,6 +14,7 @@ import dev.usbharu.hideout.service.activitypub.ActivityPubUserService
import dev.usbharu.hideout.service.api.IPostApiService
import dev.usbharu.hideout.service.api.IUserApiService
import dev.usbharu.hideout.service.api.UserAuthApiService
import dev.usbharu.hideout.service.api.WebFingerApiService
import dev.usbharu.hideout.service.auth.HttpSignatureVerifyService
import dev.usbharu.hideout.service.core.Transaction
import dev.usbharu.hideout.service.user.IUserService
@ -32,6 +33,7 @@ fun Application.configureRouting(
userQueryService: UserQueryService,
followerQueryService: FollowerQueryService,
userAuthApiService: UserAuthApiService,
webFingerApiService: WebFingerApiService,
transaction: Transaction
) {
install(AutoHeadResponse)
@ -39,7 +41,7 @@ fun Application.configureRouting(
inbox(httpSignatureVerifyService, activityPubService)
outbox()
usersAP(activityPubUserService, userQueryService, followerQueryService, transaction)
webfinger(userQueryService)
webfinger(webFingerApiService)
route("/api/internal/v1") {
posts(postService)
users(userService, userApiService)

View File

@ -4,14 +4,14 @@ import dev.usbharu.hideout.config.Config
import dev.usbharu.hideout.domain.model.wellknown.WebFinger
import dev.usbharu.hideout.exception.IllegalParameterException
import dev.usbharu.hideout.exception.ParameterNotExistException
import dev.usbharu.hideout.query.UserQueryService
import dev.usbharu.hideout.service.api.WebFingerApiService
import dev.usbharu.hideout.util.HttpUtil.Activity
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
fun Routing.webfinger(userQueryService: UserQueryService) {
fun Routing.webfinger(webFingerApiService: WebFingerApiService) {
route("/.well-known/webfinger") {
get {
val acct = call.request.queryParameters["resource"]?.decodeURLPart()
@ -25,7 +25,7 @@ fun Routing.webfinger(userQueryService: UserQueryService) {
.substringAfter("acct:")
.trimStart('@')
val userEntity = userQueryService.findByNameAndDomain(accountName, Config.configData.domain)
val userEntity = webFingerApiService.findByNameAndDomain(accountName, Config.configData.domain)
val webFinger = WebFinger(
subject = acct,

View File

@ -5,12 +5,14 @@ import dev.usbharu.hideout.domain.model.ActivityPubStringResponse
import dev.usbharu.hideout.domain.model.ap.Create
import dev.usbharu.hideout.domain.model.ap.Note
import dev.usbharu.hideout.exception.ap.IllegalActivityPubObjectException
import dev.usbharu.hideout.service.core.Transaction
import io.ktor.http.*
import org.koin.core.annotation.Single
@Single
class ActivityPubCreateServiceImpl(
private val activityPubNoteService: ActivityPubNoteService
private val activityPubNoteService: ActivityPubNoteService,
private val transaction: Transaction
) : ActivityPubCreateService {
override suspend fun receiveCreate(create: Create): ActivityPubResponse {
val value = create.`object` ?: throw IllegalActivityPubObjectException("object is null")
@ -18,8 +20,10 @@ class ActivityPubCreateServiceImpl(
throw IllegalActivityPubObjectException("object is not Note")
}
val note = value as Note
activityPubNoteService.fetchNote(note)
return ActivityPubStringResponse(HttpStatusCode.OK, "Created")
return transaction.transaction {
val note = value as Note
activityPubNoteService.fetchNote(note)
ActivityPubStringResponse(HttpStatusCode.OK, "Created")
}
}
}

View File

@ -6,6 +6,7 @@ import dev.usbharu.hideout.domain.model.ap.Like
import dev.usbharu.hideout.exception.ap.IllegalActivityPubObjectException
import dev.usbharu.hideout.query.PostQueryService
import dev.usbharu.hideout.query.UserQueryService
import dev.usbharu.hideout.service.core.Transaction
import dev.usbharu.hideout.service.reaction.IReactionService
import io.ktor.http.*
import org.koin.core.annotation.Single
@ -16,23 +17,26 @@ class ActivityPubLikeServiceImpl(
private val activityPubUserService: ActivityPubUserService,
private val activityPubNoteService: ActivityPubNoteService,
private val userQueryService: UserQueryService,
private val postQueryService: PostQueryService
private val postQueryService: PostQueryService,
private val transaction: Transaction
) : ActivityPubLikeService {
override suspend fun receiveLike(like: Like): ActivityPubResponse {
val actor = like.actor ?: throw IllegalActivityPubObjectException("actor is null")
val content = like.content ?: throw IllegalActivityPubObjectException("content is null")
like.`object` ?: throw IllegalActivityPubObjectException("object is null")
val person = activityPubUserService.fetchPerson(actor)
activityPubNoteService.fetchNote(like.`object`!!)
transaction.transaction {
val person = activityPubUserService.fetchPerson(actor)
activityPubNoteService.fetchNote(like.`object`!!)
val user = userQueryService.findByUrl(
person.url
?: throw IllegalActivityPubObjectException("actor is not found")
)
val user = userQueryService.findByUrl(
person.url
?: throw IllegalActivityPubObjectException("actor is not found")
)
val post = postQueryService.findByUrl(like.`object`!!)
val post = postQueryService.findByUrl(like.`object`!!)
reactionService.receiveReaction(content, actor.substringAfter("://").substringBefore("/"), user.id, post.id)
reactionService.receiveReaction(content, actor.substringAfter("://").substringBefore("/"), user.id, post.id)
}
return ActivityPubStringResponse(HttpStatusCode.OK, "")
}
}

View File

@ -9,6 +9,7 @@ import dev.usbharu.hideout.domain.model.ap.Follow
import dev.usbharu.hideout.domain.model.job.ReceiveFollowJob
import dev.usbharu.hideout.plugins.postAp
import dev.usbharu.hideout.query.UserQueryService
import dev.usbharu.hideout.service.core.Transaction
import dev.usbharu.hideout.service.job.JobQueueParentService
import dev.usbharu.hideout.service.user.IUserService
import io.ktor.client.*
@ -22,7 +23,8 @@ class ActivityPubReceiveFollowServiceImpl(
private val activityPubUserService: ActivityPubUserService,
private val userService: IUserService,
private val httpClient: HttpClient,
private val userQueryService: UserQueryService
private val userQueryService: UserQueryService,
private val transaction: Transaction
) : ActivityPubReceiveFollowService {
override suspend fun receiveFollow(follow: Follow): ActivityPubResponse {
// TODO: Verify HTTP Signature
@ -35,24 +37,26 @@ class ActivityPubReceiveFollowServiceImpl(
}
override suspend fun receiveFollowJob(props: JobProps<ReceiveFollowJob>) {
val actor = props[ReceiveFollowJob.actor]
val targetActor = props[ReceiveFollowJob.targetActor]
val person = activityPubUserService.fetchPerson(actor, targetActor)
val follow = Config.configData.objectMapper.readValue<Follow>(props[ReceiveFollowJob.follow])
httpClient.postAp(
urlString = person.inbox ?: throw IllegalArgumentException("inbox is not found"),
username = "$targetActor#pubkey",
jsonLd = Accept(
name = "Follow",
`object` = follow,
actor = targetActor
transaction.transaction {
val actor = props[ReceiveFollowJob.actor]
val targetActor = props[ReceiveFollowJob.targetActor]
val person = activityPubUserService.fetchPerson(actor, targetActor)
val follow = Config.configData.objectMapper.readValue<Follow>(props[ReceiveFollowJob.follow])
httpClient.postAp(
urlString = person.inbox ?: throw IllegalArgumentException("inbox is not found"),
username = "$targetActor#pubkey",
jsonLd = Accept(
name = "Follow",
`object` = follow,
actor = targetActor
)
)
)
val targetEntity = userQueryService.findByUrl(targetActor)
val followActorEntity =
userQueryService.findByUrl(follow.actor ?: throw java.lang.IllegalArgumentException("Actor is null"))
val targetEntity = userQueryService.findByUrl(targetActor)
val followActorEntity =
userQueryService.findByUrl(follow.actor ?: throw java.lang.IllegalArgumentException("Actor is null"))
userService.followRequest(targetEntity.id, followActorEntity.id)
userService.followRequest(targetEntity.id, followActorEntity.id)
}
}
}

View File

@ -5,6 +5,7 @@ import dev.usbharu.hideout.domain.model.ActivityPubStringResponse
import dev.usbharu.hideout.domain.model.ap.Follow
import dev.usbharu.hideout.domain.model.ap.Undo
import dev.usbharu.hideout.query.UserQueryService
import dev.usbharu.hideout.service.core.Transaction
import dev.usbharu.hideout.service.user.IUserService
import io.ktor.http.*
import org.koin.core.annotation.Single
@ -14,7 +15,8 @@ import org.koin.core.annotation.Single
class ActivityPubUndoServiceImpl(
private val userService: IUserService,
private val activityPubUserService: ActivityPubUserService,
private val userQueryService: UserQueryService
private val userQueryService: UserQueryService,
private val transaction: Transaction
) : ActivityPubUndoService {
override suspend fun receiveUndo(undo: Undo): ActivityPubResponse {
if (undo.actor == null) {
@ -33,11 +35,12 @@ class ActivityPubUndoServiceImpl(
if (follow.`object` == null) {
return ActivityPubStringResponse(HttpStatusCode.BadRequest, "object.object is null")
}
activityPubUserService.fetchPerson(undo.actor!!, follow.`object`)
val follower = userQueryService.findByUrl(undo.actor!!)
val target = userQueryService.findByUrl(follow.`object`!!)
userService.unfollow(target.id, follower.id)
transaction.transaction {
activityPubUserService.fetchPerson(undo.actor!!, follow.`object`)
val follower = userQueryService.findByUrl(undo.actor!!)
val target = userQueryService.findByUrl(follow.`object`!!)
userService.unfollow(target.id, follower.id)
}
}
else -> {}

View File

@ -6,10 +6,10 @@ import dev.usbharu.hideout.domain.model.ap.Image
import dev.usbharu.hideout.domain.model.ap.Key
import dev.usbharu.hideout.domain.model.ap.Person
import dev.usbharu.hideout.domain.model.hideout.dto.RemoteUserCreateDto
import dev.usbharu.hideout.exception.UserNotFoundException
import dev.usbharu.hideout.exception.ap.IllegalActivityPubObjectException
import dev.usbharu.hideout.plugins.getAp
import dev.usbharu.hideout.query.UserQueryService
import dev.usbharu.hideout.service.core.Transaction
import dev.usbharu.hideout.service.user.IUserService
import dev.usbharu.hideout.util.HttpUtil.Activity
import io.ktor.client.*
@ -22,13 +22,16 @@ import org.koin.core.annotation.Single
class ActivityPubUserServiceImpl(
private val userService: IUserService,
private val httpClient: HttpClient,
private val userQueryService: UserQueryService
private val userQueryService: UserQueryService,
private val transaction: Transaction
) :
ActivityPubUserService {
override suspend fun getPersonByName(name: String): Person {
val userEntity = transaction.transaction {
userQueryService.findByNameAndDomain(name, Config.configData.domain)
}
// TODO: JOINで書き直し
val userEntity = userQueryService.findByNameAndDomain(name, Config.configData.domain)
val userUrl = "${Config.configData.url}/users/$name"
return Person(
type = emptyList(),
@ -81,7 +84,7 @@ class ActivityPubUserServiceImpl(
publicKeyPem = userEntity.publicKey
)
)
} catch (ignore: UserNotFoundException) {
} catch (ignore: NoSuchElementException) {
val httpResponse = if (targetActor != null) {
httpClient.getAp(url, "$targetActor#pubkey")
} else {
@ -108,5 +111,6 @@ class ActivityPubUserServiceImpl(
)
person
}
}
}

View File

@ -0,0 +1,7 @@
package dev.usbharu.hideout.service.api
import dev.usbharu.hideout.domain.model.hideout.entity.User
interface WebFingerApiService {
suspend fun findByNameAndDomain(name: String, domain: String): User
}

View File

@ -0,0 +1,16 @@
package dev.usbharu.hideout.service.api
import dev.usbharu.hideout.domain.model.hideout.entity.User
import dev.usbharu.hideout.query.UserQueryService
import dev.usbharu.hideout.service.core.Transaction
import org.koin.core.annotation.Single
@Single
class WebFingerApiServiceImpl(private val transaction: Transaction, private val userQueryService: UserQueryService) :
WebFingerApiService {
override suspend fun findByNameAndDomain(name: String, domain: String): User {
return transaction.transaction {
userQueryService.findByNameAndDomain(name, domain)
}
}
}

View File

@ -2,14 +2,18 @@ package dev.usbharu.hideout.service.auth
import dev.usbharu.hideout.plugins.KtorKeyMap
import dev.usbharu.hideout.query.UserQueryService
import dev.usbharu.hideout.service.core.Transaction
import io.ktor.http.*
import org.koin.core.annotation.Single
import tech.barbero.http.message.signing.SignatureHeaderVerifier
@Single
class HttpSignatureVerifyServiceImpl(private val userQueryService: UserQueryService) : HttpSignatureVerifyService {
class HttpSignatureVerifyServiceImpl(
private val userQueryService: UserQueryService,
private val transaction: Transaction
) : HttpSignatureVerifyService {
override fun verify(headers: Headers): Boolean {
val build = SignatureHeaderVerifier.builder().keyMap(KtorKeyMap(userQueryService)).build()
val build = SignatureHeaderVerifier.builder().keyMap(KtorKeyMap(userQueryService, transaction)).build()
return true
// build.verify(object : HttpMessage {
// override fun headerValues(name: String?): MutableList<String> {

View File

@ -4,7 +4,7 @@
<pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="TRACE">
<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>
<logger name="org.eclipse.jetty" level="INFO"/>