Merge pull request #228 from usbharu/feature/for-update-transaction

Feature/for update transaction
This commit is contained in:
usbharu 2023-12-21 15:12:27 +09:00 committed by GitHub
commit f32102fef1
113 changed files with 1261 additions and 1089 deletions

2
.gitignore vendored
View File

@ -43,3 +43,5 @@ out/
/tomcat-e2e/ /tomcat-e2e/
/e2eTest.log /e2eTest.log
/files/ /files/
*.log

View File

@ -9,6 +9,9 @@ hideout:
public-key: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyehkd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdgcKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbcmwIDAQAB" public-key: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyehkd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdgcKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbcmwIDAQAB"
storage: storage:
type: local type: local
debug:
trace-query-exception: true
trace-query-call: true
spring: spring:
flyway: flyway:

View File

@ -1,7 +1,7 @@
package mastodon.account package mastodon.account
import dev.usbharu.hideout.SpringApplication import dev.usbharu.hideout.SpringApplication
import dev.usbharu.hideout.core.infrastructure.exposedquery.ActorQueryServiceImpl import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.infrastructure.exposedquery.FollowerQueryServiceImpl import dev.usbharu.hideout.core.infrastructure.exposedquery.FollowerQueryServiceImpl
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
@ -39,7 +39,7 @@ class AccountApiTest {
private lateinit var followerQueryServiceImpl: FollowerQueryServiceImpl private lateinit var followerQueryServiceImpl: FollowerQueryServiceImpl
@Autowired @Autowired
private lateinit var userQueryServiceImpl: ActorQueryServiceImpl private lateinit var actorRepository: ActorRepository
@Autowired @Autowired
@ -100,7 +100,7 @@ class AccountApiTest {
.asyncDispatch() .asyncDispatch()
.andExpect { status { isFound() } } .andExpect { status { isFound() } }
userQueryServiceImpl.findByNameAndDomain("api-test-user-1", "example.com") actorRepository.findByNameAndDomain("api-test-user-1", "example.com")
} }
@Test @Test
@ -116,7 +116,7 @@ class AccountApiTest {
.asyncDispatch() .asyncDispatch()
.andExpect { status { isFound() } } .andExpect { status { isFound() } }
userQueryServiceImpl.findByNameAndDomain("api-test-user-2", "example.com") actorRepository.findByNameAndDomain("api-test-user-2", "example.com")
} }
@Test @Test

View File

@ -1,8 +1,9 @@
package util package util
import dev.usbharu.hideout.application.external.Transaction import dev.usbharu.hideout.application.external.Transaction
import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.infrastructure.springframework.httpsignature.HttpSignatureUser import dev.usbharu.hideout.core.infrastructure.springframework.httpsignature.HttpSignatureUser
import dev.usbharu.hideout.core.query.ActorQueryService
import dev.usbharu.httpsignature.common.HttpHeaders import dev.usbharu.httpsignature.common.HttpHeaders
import dev.usbharu.httpsignature.common.HttpMethod import dev.usbharu.httpsignature.common.HttpMethod
import dev.usbharu.httpsignature.common.HttpRequest import dev.usbharu.httpsignature.common.HttpRequest
@ -14,7 +15,7 @@ import org.springframework.security.web.authentication.preauth.PreAuthenticatedA
import java.net.URL import java.net.URL
class WithHttpSignatureSecurityContextFactory( class WithHttpSignatureSecurityContextFactory(
private val actorQueryService: ActorQueryService, private val actorRepository: ActorRepository,
private val transaction: Transaction private val transaction: Transaction
) : WithSecurityContextFactory<WithHttpSignature> { ) : WithSecurityContextFactory<WithHttpSignature> {
@ -28,7 +29,8 @@ class WithHttpSignatureSecurityContextFactory(
) )
) )
val httpSignatureUser = transaction.transaction { val httpSignatureUser = transaction.transaction {
val findByKeyId = actorQueryService.findByKeyId(annotation.keyId) val findByKeyId =
actorRepository.findByKeyId(annotation.keyId) ?: throw UserNotFoundException.withKeyId(annotation.keyId)
HttpSignatureUser( HttpSignatureUser(
findByKeyId.name, findByKeyId.name,
findByKeyId.domain, findByKeyId.domain,

View File

@ -49,9 +49,9 @@ constructor(
@Suppress("CyclomaticComplexMethod") @Suppress("CyclomaticComplexMethod")
override fun hashCode(): Int { override fun hashCode(): Int {
var result = super.hashCode() var result = super.hashCode()
result = 31 * result + name.hashCode() result = 31 * result + (name?.hashCode() ?: 0)
result = 31 * result + id.hashCode() result = 31 * result + id.hashCode()
result = 31 * result + (preferredUsername?.hashCode() ?: 0) result = 31 * result + preferredUsername.hashCode()
result = 31 * result + (summary?.hashCode() ?: 0) result = 31 * result + (summary?.hashCode() ?: 0)
result = 31 * result + inbox.hashCode() result = 31 * result + inbox.hashCode()
result = 31 * result + outbox.hashCode() result = 31 * result + outbox.hashCode()
@ -61,15 +61,15 @@ constructor(
result = 31 * result + endpoints.hashCode() result = 31 * result + endpoints.hashCode()
result = 31 * result + (followers?.hashCode() ?: 0) result = 31 * result + (followers?.hashCode() ?: 0)
result = 31 * result + (following?.hashCode() ?: 0) result = 31 * result + (following?.hashCode() ?: 0)
result = 31 * result + manuallyApprovesFollowers.hashCode() result = 31 * result + (manuallyApprovesFollowers?.hashCode() ?: 0)
return result return result
} }
override fun toString(): String { override fun toString(): String {
return "Person(" + return "Person(" +
"name='$name', " + "name=$name, " +
"id='$id', " + "id='$id', " +
"preferredUsername=$preferredUsername, " + "preferredUsername='$preferredUsername', " +
"summary=$summary, " + "summary=$summary, " +
"inbox='$inbox', " + "inbox='$inbox', " +
"outbox='$outbox', " + "outbox='$outbox', " +

View File

@ -5,12 +5,10 @@ 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.activitypub.service.objects.note.APNoteServiceImpl.Companion.public import dev.usbharu.hideout.activitypub.service.objects.note.APNoteServiceImpl.Companion.public
import dev.usbharu.hideout.application.infrastructure.exposed.QueryMapper import dev.usbharu.hideout.application.infrastructure.exposed.QueryMapper
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.Post
import dev.usbharu.hideout.core.domain.model.post.PostRepository import dev.usbharu.hideout.core.domain.model.post.PostRepository
import dev.usbharu.hideout.core.domain.model.post.Visibility import dev.usbharu.hideout.core.domain.model.post.Visibility
import dev.usbharu.hideout.core.infrastructure.exposedrepository.* import dev.usbharu.hideout.core.infrastructure.exposedrepository.*
import dev.usbharu.hideout.util.singleOr
import org.jetbrains.exposed.sql.Query import org.jetbrains.exposed.sql.Query
import org.jetbrains.exposed.sql.ResultRow import org.jetbrains.exposed.sql.ResultRow
import org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.select
@ -21,39 +19,42 @@ import java.time.Instant
@Repository @Repository
class NoteQueryServiceImpl(private val postRepository: PostRepository, private val postQueryMapper: QueryMapper<Post>) : class NoteQueryServiceImpl(private val postRepository: PostRepository, private val postQueryMapper: QueryMapper<Post>) :
NoteQueryService { NoteQueryService {
override suspend fun findById(id: Long): Pair<Note, Post> { override suspend fun findById(id: Long): Pair<Note, Post>? {
return Posts return Posts
.leftJoin(Actors) .leftJoin(Actors)
.leftJoin(PostsMedia) .leftJoin(PostsMedia)
.leftJoin(Media) .leftJoin(Media)
.select { Posts.id eq id } .select { Posts.id eq id }
.let { .let {
it.toNote() to postQueryMapper.map(it) (it.toNote() ?: return null) to (
.singleOr { FailedToGetResourcesException("id: $id does not exist.") } postQueryMapper.map(it)
.singleOrNull() ?: return null
)
} }
} }
override suspend fun findByApid(apId: String): Pair<Note, Post> { override suspend fun findByApid(apId: String): Pair<Note, Post>? {
return Posts return Posts
.leftJoin(Actors) .leftJoin(Actors)
.leftJoin(PostsMedia) .leftJoin(PostsMedia)
.leftJoin(Media) .leftJoin(Media)
.select { Posts.apId eq apId } .select { Posts.apId eq apId }
.let { .let {
it.toNote() to postQueryMapper.map(it) (it.toNote() ?: return null) to (
.singleOr { FailedToGetResourcesException("apid: $apId does not exist.") } postQueryMapper.map(it)
.singleOrNull() ?: return null
)
} }
} }
private suspend fun ResultRow.toNote(mediaList: List<dev.usbharu.hideout.core.domain.model.media.Media>): Note { private suspend fun ResultRow.toNote(mediaList: List<dev.usbharu.hideout.core.domain.model.media.Media>): Note {
val replyId = this[Posts.replyId] val replyId = this[Posts.replyId]
val replyTo = if (replyId != null) { val replyTo = if (replyId != null) {
try { val url = postRepository.findById(replyId)?.url
postRepository.findById(replyId).url if (url == null) {
} catch (e: FailedToGetResourcesException) { logger.warn("Failed to get replyId: $replyId")
logger.warn("Failed to get replyId: $replyId", e)
null
} }
url
} else { } else {
null null
} }
@ -76,11 +77,11 @@ 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 { resultRow -> resultRow.toMediaOrNull() }) } .map { it.first().toNote(it.mapNotNull { resultRow -> resultRow.toMediaOrNull() }) }
.singleOr { FailedToGetResourcesException("resource does not exist.") } .singleOrNull()
} }
private fun visibility(visibility: Visibility, followers: String?): Pair<List<String>, List<String>> { private fun visibility(visibility: Visibility, followers: String?): Pair<List<String>, List<String>> {

View File

@ -2,7 +2,7 @@ package dev.usbharu.hideout.activitypub.interfaces.api.actor
import dev.usbharu.hideout.activitypub.domain.model.Person import dev.usbharu.hideout.activitypub.domain.model.Person
import dev.usbharu.hideout.activitypub.service.objects.user.APUserService import dev.usbharu.hideout.activitypub.service.objects.user.APUserService
import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException
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
@ -12,7 +12,7 @@ class UserAPControllerImpl(private val apUserService: APUserService) : UserAPCon
override suspend fun userAp(username: String): ResponseEntity<Person> { override suspend fun userAp(username: String): ResponseEntity<Person> {
val person = try { val person = try {
apUserService.getPersonByName(username) apUserService.getPersonByName(username)
} catch (_: FailedToGetResourcesException) { } catch (_: UserNotFoundException) {
return ResponseEntity.notFound().build() return ResponseEntity.notFound().build()
} }
person.context += listOf("https://www.w3.org/ns/activitystreams") person.context += listOf("https://www.w3.org/ns/activitystreams")

View File

@ -3,7 +3,7 @@ package dev.usbharu.hideout.activitypub.interfaces.api.webfinger
import dev.usbharu.hideout.activitypub.domain.model.webfinger.WebFinger import dev.usbharu.hideout.activitypub.domain.model.webfinger.WebFinger
import dev.usbharu.hideout.activitypub.service.webfinger.WebFingerApiService import dev.usbharu.hideout.activitypub.service.webfinger.WebFingerApiService
import dev.usbharu.hideout.application.config.ApplicationConfig import dev.usbharu.hideout.application.config.ApplicationConfig
import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException
import dev.usbharu.hideout.util.AcctUtil import dev.usbharu.hideout.util.AcctUtil
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
@ -29,7 +29,7 @@ class WebFingerController(
} }
val user = try { val user = try {
webFingerApiService.findByNameAndDomain(acct.username, acct.domain ?: applicationConfig.url.host) webFingerApiService.findByNameAndDomain(acct.username, acct.domain ?: applicationConfig.url.host)
} catch (_: FailedToGetResourcesException) { } catch (_: UserNotFoundException) {
return@runBlocking ResponseEntity.notFound().build() return@runBlocking ResponseEntity.notFound().build()
} }
val webFinger = WebFinger( val webFinger = WebFinger(

View File

@ -4,6 +4,6 @@ import dev.usbharu.hideout.activitypub.domain.model.Note
import dev.usbharu.hideout.core.domain.model.post.Post import dev.usbharu.hideout.core.domain.model.post.Post
interface NoteQueryService { interface NoteQueryService {
suspend fun findById(id: Long): Pair<Note, Post> suspend fun findById(id: Long): Pair<Note, Post>?
suspend fun findByApid(apId: String): Pair<Note, Post> suspend fun findByApid(apId: String): Pair<Note, Post>?
} }

View File

@ -2,22 +2,22 @@ package dev.usbharu.hideout.activitypub.service.activity.accept
import dev.usbharu.hideout.activitypub.service.common.APRequestService import dev.usbharu.hideout.activitypub.service.common.APRequestService
import dev.usbharu.hideout.application.external.Transaction import dev.usbharu.hideout.application.external.Transaction
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.external.job.DeliverAcceptJob import dev.usbharu.hideout.core.external.job.DeliverAcceptJob
import dev.usbharu.hideout.core.external.job.DeliverAcceptJobParam import dev.usbharu.hideout.core.external.job.DeliverAcceptJobParam
import dev.usbharu.hideout.core.query.ActorQueryService
import dev.usbharu.hideout.core.service.job.JobProcessor import dev.usbharu.hideout.core.service.job.JobProcessor
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
@Service @Service
class APDeliverAcceptJobProcessor( class APDeliverAcceptJobProcessor(
private val apRequestService: APRequestService, private val apRequestService: APRequestService,
private val actorQueryService: ActorQueryService,
private val deliverAcceptJob: DeliverAcceptJob, private val deliverAcceptJob: DeliverAcceptJob,
private val transaction: Transaction private val transaction: Transaction,
private val actorRepository: ActorRepository
) : ) :
JobProcessor<DeliverAcceptJobParam, DeliverAcceptJob> { JobProcessor<DeliverAcceptJobParam, DeliverAcceptJob> {
override suspend fun process(param: DeliverAcceptJobParam): Unit = transaction.transaction { override suspend fun process(param: DeliverAcceptJobParam): Unit = transaction.transaction {
apRequestService.apPost(param.inbox, param.accept, actorQueryService.findById(param.signer)) apRequestService.apPost(param.inbox, param.accept, actorRepository.findById(param.signer))
} }
override fun job(): DeliverAcceptJob = deliverAcceptJob override fun job(): DeliverAcceptJob = deliverAcceptJob

View File

@ -7,15 +7,16 @@ import dev.usbharu.hideout.activitypub.service.common.AbstractActivityPubProcess
import dev.usbharu.hideout.activitypub.service.common.ActivityPubProcessContext import dev.usbharu.hideout.activitypub.service.common.ActivityPubProcessContext
import dev.usbharu.hideout.activitypub.service.common.ActivityType import dev.usbharu.hideout.activitypub.service.common.ActivityType
import dev.usbharu.hideout.application.external.Transaction import dev.usbharu.hideout.application.external.Transaction
import dev.usbharu.hideout.core.query.ActorQueryService import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.service.relationship.RelationshipService import dev.usbharu.hideout.core.service.relationship.RelationshipService
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
@Service @Service
class ApAcceptProcessor( class ApAcceptProcessor(
transaction: Transaction, transaction: Transaction,
private val actorQueryService: ActorQueryService, private val relationshipService: RelationshipService,
private val relationshipService: RelationshipService private val actorRepository: ActorRepository
) : ) :
AbstractActivityPubProcessor<Accept>(transaction) { AbstractActivityPubProcessor<Accept>(transaction) {
@ -32,8 +33,8 @@ class ApAcceptProcessor(
val userUrl = follow.apObject val userUrl = follow.apObject
val followerUrl = follow.actor val followerUrl = follow.actor
val user = actorQueryService.findByUrl(userUrl) val user = actorRepository.findByUrl(userUrl) ?: throw UserNotFoundException.withUrl(userUrl)
val follower = actorQueryService.findByUrl(followerUrl) val follower = actorRepository.findByUrl(followerUrl) ?: throw UserNotFoundException.withUrl(followerUrl)
relationshipService.acceptFollowRequest(user.id, follower.id) relationshipService.acceptFollowRequest(user.id, follower.id)
logger.debug("SUCCESS Follow from ${user.url} to ${follower.url}.") logger.debug("SUCCESS Follow from ${user.url} to ${follower.url}.")

View File

@ -5,7 +5,8 @@ import dev.usbharu.hideout.activitypub.service.common.AbstractActivityPubProcess
import dev.usbharu.hideout.activitypub.service.common.ActivityPubProcessContext import dev.usbharu.hideout.activitypub.service.common.ActivityPubProcessContext
import dev.usbharu.hideout.activitypub.service.common.ActivityType import dev.usbharu.hideout.activitypub.service.common.ActivityType
import dev.usbharu.hideout.application.external.Transaction import dev.usbharu.hideout.application.external.Transaction
import dev.usbharu.hideout.core.query.ActorQueryService import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.service.relationship.RelationshipService import dev.usbharu.hideout.core.service.relationship.RelationshipService
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
@ -14,14 +15,17 @@ import org.springframework.stereotype.Service
*/ */
@Service @Service
class BlockActivityPubProcessor( class BlockActivityPubProcessor(
private val actorQueryService: ActorQueryService,
private val relationshipService: RelationshipService, private val relationshipService: RelationshipService,
private val actorRepository: ActorRepository,
transaction: Transaction transaction: Transaction
) : ) :
AbstractActivityPubProcessor<Block>(transaction) { AbstractActivityPubProcessor<Block>(transaction) {
override suspend fun internalProcess(activity: ActivityPubProcessContext<Block>) { override suspend fun internalProcess(activity: ActivityPubProcessContext<Block>) {
val user = actorQueryService.findByUrl(activity.activity.actor) val user = actorRepository.findByUrl(activity.activity.actor)
val target = actorQueryService.findByUrl(activity.activity.apObject) ?: throw UserNotFoundException.withUrl(activity.activity.actor)
val target = actorRepository.findByUrl(activity.activity.apObject) ?: throw UserNotFoundException.withUrl(
activity.activity.apObject
)
relationshipService.block(user.id, target.id) relationshipService.block(user.id, target.id)
} }

View File

@ -4,9 +4,11 @@ import com.fasterxml.jackson.databind.ObjectMapper
import dev.usbharu.hideout.activitypub.domain.model.Create import dev.usbharu.hideout.activitypub.domain.model.Create
import dev.usbharu.hideout.activitypub.query.NoteQueryService import dev.usbharu.hideout.activitypub.query.NoteQueryService
import dev.usbharu.hideout.application.config.ApplicationConfig import dev.usbharu.hideout.application.config.ApplicationConfig
import dev.usbharu.hideout.core.domain.exception.resource.PostNotFoundException
import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.domain.model.post.Post import dev.usbharu.hideout.core.domain.model.post.Post
import dev.usbharu.hideout.core.external.job.DeliverPostJob import dev.usbharu.hideout.core.external.job.DeliverPostJob
import dev.usbharu.hideout.core.query.ActorQueryService
import dev.usbharu.hideout.core.query.FollowerQueryService import dev.usbharu.hideout.core.query.FollowerQueryService
import dev.usbharu.hideout.core.service.job.JobQueueParentService import dev.usbharu.hideout.core.service.job.JobQueueParentService
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
@ -17,9 +19,9 @@ class ApSendCreateServiceImpl(
private val followerQueryService: FollowerQueryService, private val followerQueryService: FollowerQueryService,
private val objectMapper: ObjectMapper, private val objectMapper: ObjectMapper,
private val jobQueueParentService: JobQueueParentService, private val jobQueueParentService: JobQueueParentService,
private val actorQueryService: ActorQueryService,
private val noteQueryService: NoteQueryService, private val noteQueryService: NoteQueryService,
private val applicationConfig: ApplicationConfig private val applicationConfig: ApplicationConfig,
private val actorRepository: ActorRepository
) : ApSendCreateService { ) : ApSendCreateService {
override suspend fun createNote(post: Post) { override suspend fun createNote(post: Post) {
logger.info("CREATE Create Local Note ${post.url}") logger.info("CREATE Create Local Note ${post.url}")
@ -29,8 +31,8 @@ class ApSendCreateServiceImpl(
logger.debug("DELIVER Deliver Note Create ${followers.size} accounts.") logger.debug("DELIVER Deliver Note Create ${followers.size} accounts.")
val userEntity = actorQueryService.findById(post.actorId) val userEntity = actorRepository.findById(post.actorId) ?: throw UserNotFoundException.withId(post.actorId)
val note = noteQueryService.findById(post.id).first val note = noteQueryService.findById(post.id)?.first ?: throw PostNotFoundException.withId(post.id)
val create = Create( val create = Create(
name = "Create Note", name = "Create Note",
apObject = note, apObject = note,

View File

@ -8,9 +8,8 @@ import dev.usbharu.hideout.activitypub.service.common.AbstractActivityPubProcess
import dev.usbharu.hideout.activitypub.service.common.ActivityPubProcessContext import dev.usbharu.hideout.activitypub.service.common.ActivityPubProcessContext
import dev.usbharu.hideout.activitypub.service.common.ActivityType import dev.usbharu.hideout.activitypub.service.common.ActivityType
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.model.actor.ActorRepository
import dev.usbharu.hideout.core.query.ActorQueryService import dev.usbharu.hideout.core.domain.model.post.PostRepository
import dev.usbharu.hideout.core.query.PostQueryService
import dev.usbharu.hideout.core.service.post.PostService import dev.usbharu.hideout.core.service.post.PostService
import dev.usbharu.hideout.core.service.user.UserService import dev.usbharu.hideout.core.service.user.UserService
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
@ -18,10 +17,10 @@ import org.springframework.stereotype.Service
@Service @Service
class APDeleteProcessor( class APDeleteProcessor(
transaction: Transaction, transaction: Transaction,
private val postQueryService: PostQueryService,
private val actorQueryService: ActorQueryService,
private val userService: UserService, private val userService: UserService,
private val postService: PostService private val postService: PostService,
private val actorRepository: ActorRepository,
private val postRepository: PostRepository
) : ) :
AbstractActivityPubProcessor<Delete>(transaction) { AbstractActivityPubProcessor<Delete>(transaction) {
override suspend fun internalProcess(activity: ActivityPubProcessContext<Delete>) { override suspend fun internalProcess(activity: ActivityPubProcessContext<Delete>) {
@ -34,19 +33,15 @@ class APDeleteProcessor(
throw IllegalActivityPubObjectException("object hasn't id or object") throw IllegalActivityPubObjectException("object hasn't id or object")
} }
try { val actor = actorRepository.findByUrl(deleteId)
val actor = actorQueryService.findByUrl(deleteId) actor?.let { userService.deleteRemoteActor(it.id) }
userService.deleteRemoteActor(actor.id)
} catch (e: Exception) {
logger.warn("FAILED delete id: {} is not found.", deleteId, e)
}
try { val post = postRepository.findByApId(deleteId)
val post = postQueryService.findByApId(deleteId) if (post == null) {
postService.deleteRemote(post) logger.warn("FAILED Delete id: {} is not found.", deleteId)
} catch (e: FailedToGetResourcesException) { return
logger.warn("FAILED delete id: {} is not found.", deleteId, e)
} }
postService.deleteRemote(post)
} }
override fun isSupported(activityType: ActivityType): Boolean = activityType == ActivityType.Delete override fun isSupported(activityType: ActivityType): Boolean = activityType == ActivityType.Delete

View File

@ -2,21 +2,21 @@ package dev.usbharu.hideout.activitypub.service.activity.delete
import dev.usbharu.hideout.activitypub.service.common.APRequestService import dev.usbharu.hideout.activitypub.service.common.APRequestService
import dev.usbharu.hideout.application.external.Transaction import dev.usbharu.hideout.application.external.Transaction
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.external.job.DeliverDeleteJob import dev.usbharu.hideout.core.external.job.DeliverDeleteJob
import dev.usbharu.hideout.core.external.job.DeliverDeleteJobParam import dev.usbharu.hideout.core.external.job.DeliverDeleteJobParam
import dev.usbharu.hideout.core.query.ActorQueryService
import dev.usbharu.hideout.core.service.job.JobProcessor import dev.usbharu.hideout.core.service.job.JobProcessor
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
@Service @Service
class APDeliverDeleteJobProcessor( class APDeliverDeleteJobProcessor(
private val apRequestService: APRequestService, private val apRequestService: APRequestService,
private val actorQueryService: ActorQueryService,
private val transaction: Transaction, private val transaction: Transaction,
private val deliverDeleteJob: DeliverDeleteJob private val deliverDeleteJob: DeliverDeleteJob,
private val actorRepository: ActorRepository
) : JobProcessor<DeliverDeleteJobParam, DeliverDeleteJob> { ) : JobProcessor<DeliverDeleteJobParam, DeliverDeleteJob> {
override suspend fun process(param: DeliverDeleteJobParam): Unit = transaction.transaction { override suspend fun process(param: DeliverDeleteJobParam): Unit = transaction.transaction {
apRequestService.apPost(param.inbox, param.delete, actorQueryService.findById(param.signer)) apRequestService.apPost(param.inbox, param.delete, actorRepository.findById(param.signer))
} }
override fun job(): DeliverDeleteJob = deliverDeleteJob override fun job(): DeliverDeleteJob = deliverDeleteJob

View File

@ -4,11 +4,12 @@ import dev.usbharu.hideout.activitypub.domain.model.Delete
import dev.usbharu.hideout.activitypub.domain.model.Tombstone import dev.usbharu.hideout.activitypub.domain.model.Tombstone
import dev.usbharu.hideout.activitypub.domain.model.objects.ObjectValue import dev.usbharu.hideout.activitypub.domain.model.objects.ObjectValue
import dev.usbharu.hideout.application.config.ApplicationConfig import dev.usbharu.hideout.application.config.ApplicationConfig
import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException
import dev.usbharu.hideout.core.domain.model.actor.Actor import dev.usbharu.hideout.core.domain.model.actor.Actor
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.domain.model.post.Post import dev.usbharu.hideout.core.domain.model.post.Post
import dev.usbharu.hideout.core.external.job.DeliverDeleteJob import dev.usbharu.hideout.core.external.job.DeliverDeleteJob
import dev.usbharu.hideout.core.external.job.DeliverDeleteJobParam import dev.usbharu.hideout.core.external.job.DeliverDeleteJobParam
import dev.usbharu.hideout.core.query.ActorQueryService
import dev.usbharu.hideout.core.query.FollowerQueryService import dev.usbharu.hideout.core.query.FollowerQueryService
import dev.usbharu.hideout.core.service.job.JobQueueParentService import dev.usbharu.hideout.core.service.job.JobQueueParentService
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
@ -24,11 +25,12 @@ class APSendDeleteServiceImpl(
private val jobQueueParentService: JobQueueParentService, private val jobQueueParentService: JobQueueParentService,
private val delverDeleteJob: DeliverDeleteJob, private val delverDeleteJob: DeliverDeleteJob,
private val followerQueryService: FollowerQueryService, private val followerQueryService: FollowerQueryService,
private val actorQueryService: ActorQueryService, private val applicationConfig: ApplicationConfig,
private val applicationConfig: ApplicationConfig private val actorRepository: ActorRepository
) : APSendDeleteService { ) : APSendDeleteService {
override suspend fun sendDeleteNote(deletedPost: Post) { override suspend fun sendDeleteNote(deletedPost: Post) {
val actor = actorQueryService.findById(deletedPost.actorId) val actor =
actorRepository.findById(deletedPost.actorId) ?: throw UserNotFoundException.withId(deletedPost.actorId)
val followersById = followerQueryService.findFollowersById(deletedPost.actorId) val followersById = followerQueryService.findFollowersById(deletedPost.actorId)
val delete = Delete( val delete = Delete(

View File

@ -5,9 +5,10 @@ import com.fasterxml.jackson.module.kotlin.readValue
import dev.usbharu.hideout.activitypub.domain.model.Follow import dev.usbharu.hideout.activitypub.domain.model.Follow
import dev.usbharu.hideout.activitypub.service.objects.user.APUserService import dev.usbharu.hideout.activitypub.service.objects.user.APUserService
import dev.usbharu.hideout.application.external.Transaction import dev.usbharu.hideout.application.external.Transaction
import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.external.job.ReceiveFollowJob import dev.usbharu.hideout.core.external.job.ReceiveFollowJob
import dev.usbharu.hideout.core.external.job.ReceiveFollowJobParam import dev.usbharu.hideout.core.external.job.ReceiveFollowJobParam
import dev.usbharu.hideout.core.query.ActorQueryService
import dev.usbharu.hideout.core.service.job.JobProcessor import dev.usbharu.hideout.core.service.job.JobProcessor
import dev.usbharu.hideout.core.service.relationship.RelationshipService import dev.usbharu.hideout.core.service.relationship.RelationshipService
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
@ -16,10 +17,10 @@ import org.springframework.stereotype.Service
@Service @Service
class APReceiveFollowJobProcessor( class APReceiveFollowJobProcessor(
private val transaction: Transaction, private val transaction: Transaction,
private val actorQueryService: ActorQueryService,
private val apUserService: APUserService, private val apUserService: APUserService,
private val objectMapper: ObjectMapper, private val objectMapper: ObjectMapper,
private val relationshipService: RelationshipService private val relationshipService: RelationshipService,
private val actorRepository: ActorRepository
) : ) :
JobProcessor<ReceiveFollowJobParam, ReceiveFollowJob> { JobProcessor<ReceiveFollowJobParam, ReceiveFollowJob> {
override suspend fun process(param: ReceiveFollowJobParam) = transaction.transaction { override suspend fun process(param: ReceiveFollowJobParam) = transaction.transaction {
@ -28,9 +29,10 @@ class APReceiveFollowJobProcessor(
logger.info("START Follow from: {} to {}", param.targetActor, param.actor) logger.info("START Follow from: {} to {}", param.targetActor, param.actor)
val targetEntity = actorQueryService.findByUrl(param.targetActor) val targetEntity =
actorRepository.findByUrl(param.targetActor) ?: throw UserNotFoundException.withUrl(param.targetActor)
val followActorEntity = val followActorEntity =
actorQueryService.findByUrl(follow.actor) actorRepository.findByUrl(follow.actor) ?: throw UserNotFoundException.withUrl(follow.actor)
relationshipService.followRequest(followActorEntity.id, targetEntity.id) relationshipService.followRequest(followActorEntity.id, targetEntity.id)

View File

@ -8,7 +8,6 @@ import dev.usbharu.hideout.activitypub.service.common.ActivityType
import dev.usbharu.hideout.activitypub.service.objects.note.APNoteService import dev.usbharu.hideout.activitypub.service.objects.note.APNoteService
import dev.usbharu.hideout.activitypub.service.objects.user.APUserService import dev.usbharu.hideout.activitypub.service.objects.user.APUserService
import dev.usbharu.hideout.application.external.Transaction import dev.usbharu.hideout.application.external.Transaction
import dev.usbharu.hideout.core.query.PostQueryService
import dev.usbharu.hideout.core.service.reaction.ReactionService import dev.usbharu.hideout.core.service.reaction.ReactionService
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
@ -17,7 +16,6 @@ class APLikeProcessor(
transaction: Transaction, transaction: Transaction,
private val apUserService: APUserService, private val apUserService: APUserService,
private val apNoteService: APNoteService, private val apNoteService: APNoteService,
private val postQueryService: PostQueryService,
private val reactionService: ReactionService private val reactionService: ReactionService
) : ) :
AbstractActivityPubProcessor<Like>(transaction) { AbstractActivityPubProcessor<Like>(transaction) {
@ -30,23 +28,20 @@ class APLikeProcessor(
val personWithEntity = apUserService.fetchPersonWithEntity(actor) val personWithEntity = apUserService.fetchPersonWithEntity(actor)
try { try {
apNoteService.fetchNoteAsync(target).await() val post = apNoteService.fetchNoteWithEntity(target).second
reactionService.receiveReaction(
content,
actor.substringAfter("://").substringBefore("/"),
personWithEntity.second.id,
post.id
)
logger.debug("SUCCESS Add Like($content) from ${personWithEntity.second.url} to ${post.url}")
} catch (e: FailedToGetActivityPubResourceException) { } catch (e: FailedToGetActivityPubResourceException) {
logger.debug("FAILED failed to get {}", target) logger.debug("FAILED failed to get {}", target)
logger.trace("", e) logger.trace("", e)
return return
} }
val post = postQueryService.findByUrl(target)
reactionService.receiveReaction(
content,
actor.substringAfter("://").substringBefore("/"),
personWithEntity.second.id,
post.id
)
logger.debug("SUCCESS Add Like($content) from ${personWithEntity.second.url} to ${post.url}")
} }
override fun isSupported(activityType: ActivityType): Boolean = activityType == ActivityType.Like override fun isSupported(activityType: ActivityType): Boolean = activityType == ActivityType.Like

View File

@ -1,12 +1,14 @@
package dev.usbharu.hideout.activitypub.service.activity.like package dev.usbharu.hideout.activitypub.service.activity.like
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import dev.usbharu.hideout.core.domain.exception.resource.PostNotFoundException
import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.domain.model.post.PostRepository
import dev.usbharu.hideout.core.domain.model.reaction.Reaction import dev.usbharu.hideout.core.domain.model.reaction.Reaction
import dev.usbharu.hideout.core.external.job.DeliverReactionJob import dev.usbharu.hideout.core.external.job.DeliverReactionJob
import dev.usbharu.hideout.core.external.job.DeliverRemoveReactionJob import dev.usbharu.hideout.core.external.job.DeliverRemoveReactionJob
import dev.usbharu.hideout.core.query.ActorQueryService
import dev.usbharu.hideout.core.query.FollowerQueryService import dev.usbharu.hideout.core.query.FollowerQueryService
import dev.usbharu.hideout.core.query.PostQueryService
import dev.usbharu.hideout.core.service.job.JobQueueParentService import dev.usbharu.hideout.core.service.job.JobQueueParentService
import org.springframework.beans.factory.annotation.Qualifier import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
@ -19,16 +21,16 @@ interface APReactionService {
@Service @Service
class APReactionServiceImpl( class APReactionServiceImpl(
private val jobQueueParentService: JobQueueParentService, private val jobQueueParentService: JobQueueParentService,
private val actorQueryService: ActorQueryService,
private val followerQueryService: FollowerQueryService, private val followerQueryService: FollowerQueryService,
private val postQueryService: PostQueryService, private val actorRepository: ActorRepository,
@Qualifier("activitypub") private val objectMapper: ObjectMapper @Qualifier("activitypub") private val objectMapper: ObjectMapper,
private val postRepository: PostRepository
) : APReactionService { ) : APReactionService {
override suspend fun reaction(like: Reaction) { override suspend fun reaction(like: Reaction) {
val followers = followerQueryService.findFollowersById(like.actorId) val followers = followerQueryService.findFollowersById(like.actorId)
val user = actorQueryService.findById(like.actorId) val user = actorRepository.findById(like.actorId) ?: throw UserNotFoundException.withId(like.actorId)
val post = val post =
postQueryService.findById(like.postId) postRepository.findById(like.postId) ?: throw PostNotFoundException.withId(like.postId)
followers.forEach { follower -> followers.forEach { follower ->
jobQueueParentService.schedule(DeliverReactionJob) { jobQueueParentService.schedule(DeliverReactionJob) {
props[DeliverReactionJob.actor] = user.url props[DeliverReactionJob.actor] = user.url
@ -42,9 +44,9 @@ class APReactionServiceImpl(
override suspend fun removeReaction(like: Reaction) { override suspend fun removeReaction(like: Reaction) {
val followers = followerQueryService.findFollowersById(like.actorId) val followers = followerQueryService.findFollowersById(like.actorId)
val user = actorQueryService.findById(like.actorId) val user = actorRepository.findById(like.actorId) ?: throw UserNotFoundException.withId(like.actorId)
val post = val post =
postQueryService.findById(like.postId) postRepository.findById(like.postId) ?: throw PostNotFoundException.withId(like.postId)
followers.forEach { follower -> followers.forEach { follower ->
jobQueueParentService.schedule(DeliverRemoveReactionJob) { jobQueueParentService.schedule(DeliverRemoveReactionJob) {
props[DeliverRemoveReactionJob.actor] = user.url props[DeliverRemoveReactionJob.actor] = user.url

View File

@ -4,21 +4,21 @@ import dev.usbharu.hideout.activitypub.domain.model.Like
import dev.usbharu.hideout.activitypub.service.common.APRequestService import dev.usbharu.hideout.activitypub.service.common.APRequestService
import dev.usbharu.hideout.application.config.ApplicationConfig import dev.usbharu.hideout.application.config.ApplicationConfig
import dev.usbharu.hideout.application.external.Transaction import dev.usbharu.hideout.application.external.Transaction
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.external.job.DeliverReactionJob import dev.usbharu.hideout.core.external.job.DeliverReactionJob
import dev.usbharu.hideout.core.external.job.DeliverReactionJobParam import dev.usbharu.hideout.core.external.job.DeliverReactionJobParam
import dev.usbharu.hideout.core.query.ActorQueryService
import dev.usbharu.hideout.core.service.job.JobProcessor import dev.usbharu.hideout.core.service.job.JobProcessor
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
@Service @Service
class ApReactionJobProcessor( class ApReactionJobProcessor(
private val actorQueryService: ActorQueryService,
private val apRequestService: APRequestService, private val apRequestService: APRequestService,
private val applicationConfig: ApplicationConfig, private val applicationConfig: ApplicationConfig,
private val transaction: Transaction private val transaction: Transaction,
private val actorRepository: ActorRepository
) : JobProcessor<DeliverReactionJobParam, DeliverReactionJob> { ) : JobProcessor<DeliverReactionJobParam, DeliverReactionJob> {
override suspend fun process(param: DeliverReactionJobParam): Unit = transaction.transaction { override suspend fun process(param: DeliverReactionJobParam): Unit = transaction.transaction {
val signer = actorQueryService.findByUrl(param.actor) val signer = actorRepository.findByUrl(param.actor)
apRequestService.apPost( apRequestService.apPost(
param.inbox, param.inbox,

View File

@ -7,25 +7,25 @@ import dev.usbharu.hideout.activitypub.domain.model.Undo
import dev.usbharu.hideout.activitypub.service.common.APRequestService import dev.usbharu.hideout.activitypub.service.common.APRequestService
import dev.usbharu.hideout.application.config.ApplicationConfig import dev.usbharu.hideout.application.config.ApplicationConfig
import dev.usbharu.hideout.application.external.Transaction import dev.usbharu.hideout.application.external.Transaction
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.external.job.DeliverRemoveReactionJob import dev.usbharu.hideout.core.external.job.DeliverRemoveReactionJob
import dev.usbharu.hideout.core.external.job.DeliverRemoveReactionJobParam import dev.usbharu.hideout.core.external.job.DeliverRemoveReactionJobParam
import dev.usbharu.hideout.core.query.ActorQueryService
import dev.usbharu.hideout.core.service.job.JobProcessor import dev.usbharu.hideout.core.service.job.JobProcessor
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import java.time.Instant import java.time.Instant
@Service @Service
class ApRemoveReactionJobProcessor( class ApRemoveReactionJobProcessor(
private val actorQueryService: ActorQueryService,
private val transaction: Transaction, private val transaction: Transaction,
private val objectMapper: ObjectMapper, private val objectMapper: ObjectMapper,
private val apRequestService: APRequestService, private val apRequestService: APRequestService,
private val applicationConfig: ApplicationConfig private val applicationConfig: ApplicationConfig,
private val actorRepository: ActorRepository
) : JobProcessor<DeliverRemoveReactionJobParam, DeliverRemoveReactionJob> { ) : JobProcessor<DeliverRemoveReactionJobParam, DeliverRemoveReactionJob> {
override suspend fun process(param: DeliverRemoveReactionJobParam): Unit = transaction.transaction { override suspend fun process(param: DeliverRemoveReactionJobParam): Unit = transaction.transaction {
val like = objectMapper.readValue<Like>(param.like) val like = objectMapper.readValue<Like>(param.like)
val signer = actorQueryService.findByUrl(param.actor) val signer = actorRepository.findByUrl(param.actor)
apRequestService.apPost( apRequestService.apPost(
param.inbox, param.inbox,

View File

@ -2,22 +2,22 @@ package dev.usbharu.hideout.activitypub.service.activity.reject
import dev.usbharu.hideout.activitypub.service.common.APRequestService import dev.usbharu.hideout.activitypub.service.common.APRequestService
import dev.usbharu.hideout.application.external.Transaction import dev.usbharu.hideout.application.external.Transaction
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.external.job.DeliverRejectJob import dev.usbharu.hideout.core.external.job.DeliverRejectJob
import dev.usbharu.hideout.core.external.job.DeliverRejectJobParam import dev.usbharu.hideout.core.external.job.DeliverRejectJobParam
import dev.usbharu.hideout.core.query.ActorQueryService
import dev.usbharu.hideout.core.service.job.JobProcessor import dev.usbharu.hideout.core.service.job.JobProcessor
import org.springframework.stereotype.Component import org.springframework.stereotype.Component
@Component @Component
class APDeliverRejectJobProcessor( class APDeliverRejectJobProcessor(
private val apRequestService: APRequestService, private val apRequestService: APRequestService,
private val actorQueryService: ActorQueryService,
private val deliverRejectJob: DeliverRejectJob, private val deliverRejectJob: DeliverRejectJob,
private val transaction: Transaction private val transaction: Transaction,
private val actorRepository: ActorRepository
) : ) :
JobProcessor<DeliverRejectJobParam, DeliverRejectJob> { JobProcessor<DeliverRejectJobParam, DeliverRejectJob> {
override suspend fun process(param: DeliverRejectJobParam): Unit = transaction.transaction { override suspend fun process(param: DeliverRejectJobParam): Unit = transaction.transaction {
apRequestService.apPost(param.inbox, param.reject, actorQueryService.findById(param.signer)) apRequestService.apPost(param.inbox, param.reject, actorRepository.findById(param.signer))
} }
override fun job(): DeliverRejectJob = deliverRejectJob override fun job(): DeliverRejectJob = deliverRejectJob

View File

@ -6,15 +6,16 @@ import dev.usbharu.hideout.activitypub.service.common.AbstractActivityPubProcess
import dev.usbharu.hideout.activitypub.service.common.ActivityPubProcessContext import dev.usbharu.hideout.activitypub.service.common.ActivityPubProcessContext
import dev.usbharu.hideout.activitypub.service.common.ActivityType import dev.usbharu.hideout.activitypub.service.common.ActivityType
import dev.usbharu.hideout.application.external.Transaction import dev.usbharu.hideout.application.external.Transaction
import dev.usbharu.hideout.core.query.ActorQueryService import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.service.relationship.RelationshipService import dev.usbharu.hideout.core.service.relationship.RelationshipService
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
@Service @Service
class ApRejectProcessor( class ApRejectProcessor(
private val relationshipService: RelationshipService, private val relationshipService: RelationshipService,
private val actorQueryService: ActorQueryService, transaction: Transaction,
transaction: Transaction private val actorRepository: ActorRepository
) : ) :
AbstractActivityPubProcessor<Reject>(transaction) { AbstractActivityPubProcessor<Reject>(transaction) {
override suspend fun internalProcess(activity: ActivityPubProcessContext<Reject>) { override suspend fun internalProcess(activity: ActivityPubProcessContext<Reject>) {
@ -26,13 +27,15 @@ class ApRejectProcessor(
} }
when (activityType) { when (activityType) {
"Follow" -> { "Follow" -> {
val user = actorQueryService.findByUrl(activity.activity.actor) val user = actorRepository.findByUrl(activity.activity.actor) ?: throw UserNotFoundException.withUrl(
activity.activity.actor
)
activity.activity.apObject as Follow activity.activity.apObject as Follow
val actor = activity.activity.apObject.actor val actor = activity.activity.apObject.actor
val target = actorQueryService.findByUrl(actor) val target = actorRepository.findByUrl(actor) ?: throw UserNotFoundException.withUrl(actor)
logger.debug("REJECT Follow user {} target {}", user.url, target.url) logger.debug("REJECT Follow user {} target {}", user.url, target.url)

View File

@ -2,9 +2,9 @@ package dev.usbharu.hideout.activitypub.service.activity.undo
import dev.usbharu.hideout.activitypub.service.common.APRequestService import dev.usbharu.hideout.activitypub.service.common.APRequestService
import dev.usbharu.hideout.application.external.Transaction import dev.usbharu.hideout.application.external.Transaction
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.external.job.DeliverUndoJob import dev.usbharu.hideout.core.external.job.DeliverUndoJob
import dev.usbharu.hideout.core.external.job.DeliverUndoJobParam import dev.usbharu.hideout.core.external.job.DeliverUndoJobParam
import dev.usbharu.hideout.core.query.ActorQueryService
import dev.usbharu.hideout.core.service.job.JobProcessor import dev.usbharu.hideout.core.service.job.JobProcessor
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
@ -12,11 +12,11 @@ import org.springframework.stereotype.Service
class APDeliverUndoJobProcessor( class APDeliverUndoJobProcessor(
private val deliverUndoJob: DeliverUndoJob, private val deliverUndoJob: DeliverUndoJob,
private val apRequestService: APRequestService, private val apRequestService: APRequestService,
private val actorQueryService: ActorQueryService, private val transaction: Transaction,
private val transaction: Transaction private val actorRepository: ActorRepository
) : JobProcessor<DeliverUndoJobParam, DeliverUndoJob> { ) : JobProcessor<DeliverUndoJobParam, DeliverUndoJob> {
override suspend fun process(param: DeliverUndoJobParam): Unit = transaction.transaction { override suspend fun process(param: DeliverUndoJobParam): Unit = transaction.transaction {
apRequestService.apPost(param.inbox, param.undo, actorQueryService.findById(param.signer)) apRequestService.apPost(param.inbox, param.undo, actorRepository.findById(param.signer))
} }
override fun job(): DeliverUndoJob = deliverUndoJob override fun job(): DeliverUndoJob = deliverUndoJob

View File

@ -7,8 +7,11 @@ import dev.usbharu.hideout.activitypub.service.common.ActivityPubProcessContext
import dev.usbharu.hideout.activitypub.service.common.ActivityType import dev.usbharu.hideout.activitypub.service.common.ActivityType
import dev.usbharu.hideout.activitypub.service.objects.user.APUserService import dev.usbharu.hideout.activitypub.service.objects.user.APUserService
import dev.usbharu.hideout.application.external.Transaction import dev.usbharu.hideout.application.external.Transaction
import dev.usbharu.hideout.core.query.ActorQueryService import dev.usbharu.hideout.core.domain.exception.resource.PostNotFoundException
import dev.usbharu.hideout.core.query.PostQueryService import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException
import dev.usbharu.hideout.core.domain.exception.resource.local.LocalUserNotFoundException
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.domain.model.post.PostRepository
import dev.usbharu.hideout.core.service.reaction.ReactionService import dev.usbharu.hideout.core.service.reaction.ReactionService
import dev.usbharu.hideout.core.service.relationship.RelationshipService import dev.usbharu.hideout.core.service.relationship.RelationshipService
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
@ -17,69 +20,36 @@ import org.springframework.stereotype.Service
class APUndoProcessor( class APUndoProcessor(
transaction: Transaction, transaction: Transaction,
private val apUserService: APUserService, private val apUserService: APUserService,
private val actorQueryService: ActorQueryService,
private val relationshipService: RelationshipService, private val relationshipService: RelationshipService,
private val postQueryService: PostQueryService, private val reactionService: ReactionService,
private val reactionService: ReactionService private val actorRepository: ActorRepository,
) : private val postRepository: PostRepository
AbstractActivityPubProcessor<Undo>(transaction) { ) : AbstractActivityPubProcessor<Undo>(transaction) {
override suspend fun internalProcess(activity: ActivityPubProcessContext<Undo>) { override suspend fun internalProcess(activity: ActivityPubProcessContext<Undo>) {
val undo = activity.activity val undo = activity.activity
val type = val type = undo.apObject.type.firstOrNull {
undo.apObject.type it == "Block" || it == "Follow" || it == "Like" || it == "Announce" || it == "Accept"
.firstOrNull { it == "Block" || it == "Follow" || it == "Like" || it == "Announce" || it == "Accept" } } ?: return
?: return
when (type) { when (type) {
"Follow" -> { "Follow" -> {
val follow = undo.apObject as Follow follow(undo)
apUserService.fetchPerson(undo.actor, follow.apObject)
val follower = actorQueryService.findByUrl(undo.actor)
val target = actorQueryService.findByUrl(follow.apObject)
relationshipService.unfollow(follower.id, target.id)
return return
} }
"Block" -> { "Block" -> {
val block = undo.apObject as Block block(undo)
val blocker = apUserService.fetchPersonWithEntity(undo.actor, block.apObject).second
val target = actorQueryService.findByUrl(block.apObject)
relationshipService.unblock(blocker.id, target.id)
return return
} }
"Accept" -> { "Accept" -> {
val accept = undo.apObject as Accept accept(undo)
val acceptObject = if (accept.apObject is ObjectValue) {
accept.apObject.`object`
} else if (accept.apObject is Follow) {
accept.apObject.apObject
} else {
logger.warn("FAILED Unsupported type. Undo Accept {}", accept.apObject.type)
return
}
val accepter = apUserService.fetchPersonWithEntity(undo.actor, acceptObject).second
val target = actorQueryService.findByUrl(acceptObject)
relationshipService.rejectFollowRequest(accepter.id, target.id)
return return
} }
"Like" -> { "Like" -> {
val like = undo.apObject as Like like(undo)
val post = postQueryService.findByUrl(like.apObject)
val actor = actorQueryService.findByUrl(like.actor)
reactionService.receiveRemoveReaction(actor.id, post.id)
return return
} }
@ -88,6 +58,57 @@ class APUndoProcessor(
TODO() TODO()
} }
private suspend fun accept(undo: Undo) {
val accept = undo.apObject as Accept
val acceptObject = if (accept.apObject is ObjectValue) {
accept.apObject.`object`
} else if (accept.apObject is Follow) {
accept.apObject.apObject
} else {
logger.warn("FAILED Unsupported type. Undo Accept {}", accept.apObject.type)
return
}
val accepter = apUserService.fetchPersonWithEntity(undo.actor, acceptObject).second
val target = actorRepository.findByUrl(acceptObject) ?: throw UserNotFoundException.withUrl(acceptObject)
relationshipService.rejectFollowRequest(accepter.id, target.id)
return
}
private suspend fun like(undo: Undo) {
val like = undo.apObject as Like
val post = postRepository.findByUrl(like.apObject) ?: throw PostNotFoundException.withUrl(like.apObject)
val signer = actorRepository.findById(post.actorId) ?: throw LocalUserNotFoundException.withId(post.actorId)
val actor = apUserService.fetchPersonWithEntity(like.actor, signer.url).second
reactionService.receiveRemoveReaction(actor.id, post.id)
return
}
private suspend fun block(undo: Undo) {
val block = undo.apObject as Block
val blocker = apUserService.fetchPersonWithEntity(undo.actor, block.apObject).second
val target = actorRepository.findByUrl(block.apObject) ?: throw UserNotFoundException.withUrl(block.apObject)
relationshipService.unblock(blocker.id, target.id)
return
}
private suspend fun follow(undo: Undo) {
val follow = undo.apObject as Follow
val follower = apUserService.fetchPersonWithEntity(undo.actor, follow.apObject).second
val target = actorRepository.findByUrl(follow.apObject) ?: throw UserNotFoundException.withUrl(follow.apObject)
relationshipService.unfollow(follower.id, target.id)
return
}
override fun isSupported(activityType: ActivityType): Boolean = activityType == ActivityType.Undo override fun isSupported(activityType: ActivityType): Boolean = activityType == ActivityType.Undo
override fun type(): Class<Undo> = Undo::class.java override fun type(): Class<Undo> = Undo::class.java

View File

@ -229,7 +229,6 @@ class APServiceImpl(
props[it.json] = json props[it.json] = json
props[it.type] = type.name props[it.type] = type.name
val writeValueAsString = objectMapper.writeValueAsString(httpRequest) val writeValueAsString = objectMapper.writeValueAsString(httpRequest)
println(writeValueAsString)
props[it.httpRequest] = writeValueAsString props[it.httpRequest] = writeValueAsString
props[it.headers] = objectMapper.writeValueAsString(map) props[it.headers] = objectMapper.writeValueAsString(map)
} }

View File

@ -18,7 +18,7 @@ abstract class AbstractActivityPubProcessor<T : Object>(
if (activity.isAuthorized.not() && allowUnauthorized.not()) { if (activity.isAuthorized.not() && allowUnauthorized.not()) {
throw HttpSignatureUnauthorizedException() throw HttpSignatureUnauthorizedException()
} }
logger.info("START ActivityPub process") logger.info("START ActivityPub process. {}", this.type())
try { try {
transaction.transaction { transaction.transaction {
internalProcess(activity) internalProcess(activity)
@ -27,7 +27,7 @@ abstract class AbstractActivityPubProcessor<T : Object>(
logger.warn("FAILED ActivityPub process", e) logger.warn("FAILED ActivityPub process", e)
throw FailedProcessException("Failed process", e) throw FailedProcessException("Failed process", e)
} }
logger.info("SUCCESS ActivityPub process") logger.info("SUCCESS ActivityPub process. {}", this.type())
} }
abstract suspend fun internalProcess(activity: ActivityPubProcessContext<T>) abstract suspend fun internalProcess(activity: ActivityPubProcessContext<T>)

View File

@ -7,10 +7,8 @@ import dev.usbharu.hideout.activitypub.service.common.ActivityPubProcessContext
import dev.usbharu.hideout.activitypub.service.common.ActivityPubProcessor import dev.usbharu.hideout.activitypub.service.common.ActivityPubProcessor
import dev.usbharu.hideout.activitypub.service.objects.user.APUserService import dev.usbharu.hideout.activitypub.service.objects.user.APUserService
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.external.job.InboxJob import dev.usbharu.hideout.core.external.job.InboxJob
import dev.usbharu.hideout.core.external.job.InboxJobParam import dev.usbharu.hideout.core.external.job.InboxJobParam
import dev.usbharu.hideout.core.query.ActorQueryService
import dev.usbharu.hideout.core.service.job.JobProcessor import dev.usbharu.hideout.core.service.job.JobProcessor
import dev.usbharu.hideout.util.RsaUtil import dev.usbharu.hideout.util.RsaUtil
import dev.usbharu.httpsignature.common.HttpHeaders import dev.usbharu.httpsignature.common.HttpHeaders
@ -30,7 +28,6 @@ class InboxJobProcessor(
private val objectMapper: ObjectMapper, private val objectMapper: ObjectMapper,
private val signatureHeaderParser: SignatureHeaderParser, private val signatureHeaderParser: SignatureHeaderParser,
private val signatureVerifier: HttpSignatureVerifier, private val signatureVerifier: HttpSignatureVerifier,
private val actorQueryService: ActorQueryService,
private val apUserService: APUserService, private val apUserService: APUserService,
private val transaction: Transaction private val transaction: Transaction
) : JobProcessor<InboxJobParam, InboxJob> { ) : JobProcessor<InboxJobParam, InboxJob> {
@ -41,7 +38,8 @@ class InboxJobProcessor(
private suspend fun verifyHttpSignature( private suspend fun verifyHttpSignature(
httpRequest: HttpRequest, httpRequest: HttpRequest,
signature: Signature, signature: Signature,
transaction: Transaction transaction: Transaction,
actor: String
): Boolean { ): Boolean {
val requiredHeaders = when (httpRequest.method) { val requiredHeaders = when (httpRequest.method) {
HttpMethod.GET -> getRequiredHeaders HttpMethod.GET -> getRequiredHeaders
@ -53,11 +51,7 @@ class InboxJobProcessor(
} }
val user = transaction.transaction { val user = transaction.transaction {
try { apUserService.fetchPersonWithEntity(actor).second
actorQueryService.findByKeyId(signature.keyId)
} catch (_: FailedToGetResourcesException) {
apUserService.fetchPersonWithEntity(signature.keyId).second
}
} }
@Suppress("TooGenericExceptionCaught") @Suppress("TooGenericExceptionCaught")
@ -93,7 +87,6 @@ class InboxJobProcessor(
logger.trace("type: {}\njson: \n{}", param.type, jsonNode.toPrettyString()) logger.trace("type: {}\njson: \n{}", param.type, jsonNode.toPrettyString())
} }
val map = objectMapper.readValue<Map<String, List<String>>>(param.headers) val map = objectMapper.readValue<Map<String, List<String>>>(param.headers)
val httpRequest = objectMapper.readValue<HttpRequest>(param.httpRequest).copy(headers = HttpHeaders(map)) val httpRequest = objectMapper.readValue<HttpRequest>(param.httpRequest).copy(headers = HttpHeaders(map))
@ -104,7 +97,17 @@ class InboxJobProcessor(
logger.debug("Has signature? {}", signature != null) logger.debug("Has signature? {}", signature != null)
val verify = signature?.let { verifyHttpSignature(httpRequest, it, transaction) } ?: false // todo 不正なactorを取得してしまわないようにする
val verify =
signature?.let {
verifyHttpSignature(
httpRequest,
it,
transaction,
jsonNode.get("actor")?.asText() ?: signature.keyId
)
}
?: false
logger.debug("Is verifying success? {}", verify) logger.debug("Is verifying success? {}", verify)

View File

@ -6,39 +6,21 @@ import dev.usbharu.hideout.activitypub.query.NoteQueryService
import dev.usbharu.hideout.activitypub.service.common.APResourceResolveService import dev.usbharu.hideout.activitypub.service.common.APResourceResolveService
import dev.usbharu.hideout.activitypub.service.common.resolve import dev.usbharu.hideout.activitypub.service.common.resolve
import dev.usbharu.hideout.activitypub.service.objects.user.APUserService import dev.usbharu.hideout.activitypub.service.objects.user.APUserService
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.Post
import dev.usbharu.hideout.core.domain.model.post.PostRepository import dev.usbharu.hideout.core.domain.model.post.PostRepository
import dev.usbharu.hideout.core.domain.model.post.Visibility import dev.usbharu.hideout.core.domain.model.post.Visibility
import dev.usbharu.hideout.core.query.PostQueryService
import dev.usbharu.hideout.core.service.media.MediaService import dev.usbharu.hideout.core.service.media.MediaService
import dev.usbharu.hideout.core.service.media.RemoteMedia import dev.usbharu.hideout.core.service.media.RemoteMedia
import dev.usbharu.hideout.core.service.post.PostService import dev.usbharu.hideout.core.service.post.PostService
import io.ktor.client.plugins.* import io.ktor.client.plugins.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.slf4j.MDCContext
import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.cache.annotation.Cacheable
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import java.time.Instant import java.time.Instant
interface APNoteService { interface APNoteService {
suspend fun fetchNote(url: String, targetActor: String? = null): Note = fetchNoteWithEntity(url, targetActor).first
@Cacheable("fetchNote")
fun fetchNoteAsync(url: String, targetActor: String? = null): Deferred<Note> {
return CoroutineScope(Dispatchers.IO + MDCContext()).async {
newSuspendedTransaction(MDCContext()) {
fetchNote(url, targetActor)
}
}
}
suspend fun fetchNote(url: String, targetActor: String? = null): Note
suspend fun fetchNote(note: Note, targetActor: String? = null): Note suspend fun fetchNote(note: Note, targetActor: String? = null): Note
suspend fun fetchNoteWithEntity(url: String, targetActor: String? = null): Pair<Note, Post>
} }
@Service @Service
@ -46,7 +28,6 @@ interface APNoteService {
class APNoteServiceImpl( class APNoteServiceImpl(
private val postRepository: PostRepository, private val postRepository: PostRepository,
private val apUserService: APUserService, private val apUserService: APUserService,
private val postQueryService: PostQueryService,
private val postService: PostService, private val postService: PostService,
private val apResourceResolveService: APResourceResolveService, private val apResourceResolveService: APResourceResolveService,
private val postBuilder: Post.PostBuilder, private val postBuilder: Post.PostBuilder,
@ -57,13 +38,14 @@ class APNoteServiceImpl(
private val logger = LoggerFactory.getLogger(APNoteServiceImpl::class.java) private val logger = LoggerFactory.getLogger(APNoteServiceImpl::class.java)
override suspend fun fetchNote(url: String, targetActor: String?): Note { override suspend fun fetchNoteWithEntity(url: String, targetActor: String?): Pair<Note, Post> {
logger.debug("START Fetch Note url: {}", url) logger.debug("START Fetch Note url: {}", url)
try {
val post = noteQueryService.findByApid(url) val post = noteQueryService.findByApid(url)
if (post != null) {
logger.debug("SUCCESS Found in local url: {}", url) logger.debug("SUCCESS Found in local url: {}", url)
return post.first return post
} catch (_: FailedToGetResourcesException) {
} }
logger.info("AP GET url: {}", url) logger.info("AP GET url: {}", url)
@ -77,7 +59,7 @@ class APNoteServiceImpl(
) )
throw FailedToGetActivityPubResourceException("Could not retrieve $url.", e) throw FailedToGetActivityPubResourceException("Could not retrieve $url.", e)
} }
val savedNote = saveNote(note, targetActor, url) val savedNote = saveIfMissing(note, targetActor, url)
logger.debug("SUCCESS Fetch Note url: {}", url) logger.debug("SUCCESS Fetch Note url: {}", url)
return savedNote return savedNote
} }
@ -86,58 +68,51 @@ class APNoteServiceImpl(
note: Note, note: Note,
targetActor: String?, targetActor: String?,
url: String url: String
): Note { ): Pair<Note, Post> = noteQueryService.findByApid(note.id) ?: saveNote(note, targetActor, url)
requireNotNull(note.id) { "id is null" }
return try { private suspend fun saveNote(note: Note, targetActor: String?, url: String): Pair<Note, Post> {
noteQueryService.findByApid(note.id).first
} catch (_: FailedToGetResourcesException) {
saveNote(note, targetActor, url)
}
}
private suspend fun saveNote(note: Note, targetActor: String?, url: String): Note {
val person = apUserService.fetchPersonWithEntity( val person = apUserService.fetchPersonWithEntity(
note.attributedTo, note.attributedTo,
targetActor targetActor
) )
val post = postRepository.findByApId(note.id)
if (post != null) {
return note to post
}
logger.debug("VISIBILITY url: {} to: {} cc: {}", note.id, note.to, note.cc) logger.debug("VISIBILITY url: {} to: {} cc: {}", note.id, note.to, note.cc)
val visibility = val visibility = if (note.to.contains(public)) {
if (note.to.contains(public)) { Visibility.PUBLIC
Visibility.PUBLIC } else if (note.to.contains(person.second.followers) && note.cc.contains(public)) {
} else if (note.to.contains(person.second.followers) && note.cc.contains(public)) { Visibility.UNLISTED
Visibility.UNLISTED } else if (note.to.contains(person.second.followers)) {
} else if (note.to.contains(person.second.followers)) { Visibility.FOLLOWERS
Visibility.FOLLOWERS } else {
} else { Visibility.DIRECT
Visibility.DIRECT }
}
logger.debug("VISIBILITY is {} url: {}", visibility.name, note.id) logger.debug("VISIBILITY is {} url: {}", visibility.name, note.id)
val reply = note.inReplyTo?.let { val reply = note.inReplyTo?.let {
fetchNote(it, targetActor) fetchNote(it, targetActor)
postQueryService.findByUrl(it) postRepository.findByUrl(it)
} }
val mediaList = note.attachment val mediaList = note.attachment.map {
.filter { it.url != null } mediaService.uploadRemoteMedia(
.map { RemoteMedia(
mediaService.uploadRemoteMedia( it.name,
RemoteMedia( it.url,
it.name, it.mediaType,
it.url, description = it.name
it.mediaType,
description = it.name
)
) )
} )
.map { it.id } }.map { it.id }
// TODO: リモートのメディア処理を追加 val createRemote = postService.createRemote(
postService.createRemote(
postBuilder.of( postBuilder.of(
id = postRepository.generateId(), id = postRepository.generateId(),
actorId = person.second.id, actorId = person.second.id,
@ -151,11 +126,11 @@ class APNoteServiceImpl(
mediaIds = mediaList mediaIds = mediaList
) )
) )
return note return note to createRemote
} }
override suspend fun fetchNote(note: Note, targetActor: String?): Note = override suspend fun fetchNote(note: Note, targetActor: String?): Note =
saveIfMissing(note, targetActor, note.id) saveIfMissing(note, targetActor, note.id).first
companion object { companion object {
const val public: String = "https://www.w3.org/ns/activitystreams#Public" const val public: String = "https://www.w3.org/ns/activitystreams#Public"

View File

@ -5,9 +5,9 @@ import com.fasterxml.jackson.module.kotlin.readValue
import dev.usbharu.hideout.activitypub.domain.model.Create import dev.usbharu.hideout.activitypub.domain.model.Create
import dev.usbharu.hideout.activitypub.service.common.APRequestService import dev.usbharu.hideout.activitypub.service.common.APRequestService
import dev.usbharu.hideout.application.external.Transaction import dev.usbharu.hideout.application.external.Transaction
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.external.job.DeliverPostJob import dev.usbharu.hideout.core.external.job.DeliverPostJob
import dev.usbharu.hideout.core.external.job.DeliverPostJobParam import dev.usbharu.hideout.core.external.job.DeliverPostJobParam
import dev.usbharu.hideout.core.query.ActorQueryService
import dev.usbharu.hideout.core.service.job.JobProcessor import dev.usbharu.hideout.core.service.job.JobProcessor
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
@ -16,13 +16,13 @@ import org.springframework.stereotype.Service
class ApNoteJobProcessor( class ApNoteJobProcessor(
private val transaction: Transaction, private val transaction: Transaction,
private val objectMapper: ObjectMapper, private val objectMapper: ObjectMapper,
private val actorQueryService: ActorQueryService, private val apRequestService: APRequestService,
private val apRequestService: APRequestService private val actorRepository: ActorRepository
) : JobProcessor<DeliverPostJobParam, DeliverPostJob> { ) : JobProcessor<DeliverPostJobParam, DeliverPostJob> {
override suspend fun process(param: DeliverPostJobParam) { override suspend fun process(param: DeliverPostJobParam) {
val create = objectMapper.readValue<Create>(param.create) val create = objectMapper.readValue<Create>(param.create)
transaction.transaction { transaction.transaction {
val signer = actorQueryService.findByUrl(param.actor) val signer = actorRepository.findByUrl(param.actor)
logger.debug("CreateNoteJob: actor: {} create: {} inbox: {}", param.actor, create, param.inbox) logger.debug("CreateNoteJob: actor: {} create: {} inbox: {}", param.actor, create, param.inbox)

View File

@ -3,7 +3,6 @@ package dev.usbharu.hideout.activitypub.service.objects.note
import dev.usbharu.hideout.activitypub.domain.model.Note 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.model.post.Post 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
@ -17,12 +16,13 @@ class NoteApApiServiceImpl(
private val transaction: Transaction private val transaction: Transaction
) : NoteApApiService { ) : NoteApApiService {
override suspend fun getNote(postId: Long, userId: Long?): Note? = transaction.transaction { override suspend fun getNote(postId: Long, userId: Long?): Note? = transaction.transaction {
val findById = try { val findById = noteQueryService.findById(postId)
noteQueryService.findById(postId)
} catch (e: FailedToGetResourcesException) { if (findById == null) {
logger.warn("Note not found.", e) logger.warn("Note not found. $postId $userId")
return@transaction null return@transaction null
} }
when (findById.second.visibility) { when (findById.second.visibility) {
Visibility.PUBLIC, Visibility.UNLISTED -> { Visibility.PUBLIC, Visibility.UNLISTED -> {
return@transaction findById.first return@transaction findById.first

View File

@ -1,6 +1,5 @@
package dev.usbharu.hideout.activitypub.service.objects.user package dev.usbharu.hideout.activitypub.service.objects.user
import dev.usbharu.hideout.activitypub.domain.exception.IllegalActivityPubObjectException
import dev.usbharu.hideout.activitypub.domain.model.Image import dev.usbharu.hideout.activitypub.domain.model.Image
import dev.usbharu.hideout.activitypub.domain.model.Key import dev.usbharu.hideout.activitypub.domain.model.Key
import dev.usbharu.hideout.activitypub.domain.model.Person import dev.usbharu.hideout.activitypub.domain.model.Person
@ -8,13 +7,12 @@ import dev.usbharu.hideout.activitypub.service.common.APResourceResolveService
import dev.usbharu.hideout.activitypub.service.common.resolve import dev.usbharu.hideout.activitypub.service.common.resolve
import dev.usbharu.hideout.application.config.ApplicationConfig import dev.usbharu.hideout.application.config.ApplicationConfig
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.resource.UserNotFoundException
import dev.usbharu.hideout.core.domain.model.actor.Actor import dev.usbharu.hideout.core.domain.model.actor.Actor
import dev.usbharu.hideout.core.query.ActorQueryService import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.service.user.RemoteUserCreateDto import dev.usbharu.hideout.core.service.user.RemoteUserCreateDto
import dev.usbharu.hideout.core.service.user.UserService import dev.usbharu.hideout.core.service.user.UserService
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
interface APUserService { interface APUserService {
suspend fun getPersonByName(name: String): Person suspend fun getPersonByName(name: String): Person
@ -34,16 +32,17 @@ interface APUserService {
@Service @Service
class APUserServiceImpl( class APUserServiceImpl(
private val userService: UserService, private val userService: UserService,
private val actorQueryService: ActorQueryService,
private val transaction: Transaction, private val transaction: Transaction,
private val applicationConfig: ApplicationConfig, private val applicationConfig: ApplicationConfig,
private val apResourceResolveService: APResourceResolveService private val apResourceResolveService: APResourceResolveService,
private val actorRepository: ActorRepository
) : ) :
APUserService { APUserService {
override suspend fun getPersonByName(name: String): Person { override suspend fun getPersonByName(name: String): Person {
val userEntity = transaction.transaction { val userEntity = transaction.transaction {
actorQueryService.findByNameAndDomain(name, applicationConfig.url.host) actorRepository.findByNameAndDomain(name, applicationConfig.url.host)
?: throw UserNotFoundException.withNameAndDomain(name, applicationConfig.url.host)
} }
// TODO: JOINで書き直し // TODO: JOINで書き直し
val userUrl = "${applicationConfig.url}/users/$name" val userUrl = "${applicationConfig.url}/users/$name"
@ -76,40 +75,40 @@ 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
@Transactional
override suspend fun fetchPersonWithEntity(url: String, targetActor: String?): Pair<Person, Actor> { override suspend fun fetchPersonWithEntity(url: String, targetActor: String?): Pair<Person, Actor> {
return try { val userEntity = actorRepository.findByUrl(url)
val userEntity = actorQueryService.findByUrl(url)
val id = userEntity.url
return entityToPerson(userEntity, id) to userEntity
} catch (ignore: FailedToGetResourcesException) {
val person = apResourceResolveService.resolve<Person>(url, null as Long?)
val id = person.id if (userEntity != null) {
try { return entityToPerson(userEntity, userEntity.url) to userEntity
val userEntity = actorQueryService.findByUrl(id)
return entityToPerson(userEntity, id) to userEntity
} catch (_: FailedToGetResourcesException) {
}
person to userService.createRemoteUser(
RemoteUserCreateDto(
name = person.preferredUsername
?: throw IllegalActivityPubObjectException("preferredUsername is null"),
domain = id.substringAfter("://").substringBefore("/"),
screenName = person.name ?: person.preferredUsername,
description = person.summary.orEmpty(),
inbox = person.inbox,
outbox = person.outbox,
url = id,
publicKey = person.publicKey.publicKeyPem,
keyId = person.publicKey.id,
following = person.following,
followers = person.followers,
sharedInbox = person.endpoints["sharedInbox"],
locked = person.manuallyApprovesFollowers
)
)
} }
val person = apResourceResolveService.resolve<Person>(url, null as Long?)
val id = person.id
val actor = actorRepository.findByUrlWithLock(id)
if (actor != null) {
return person to actor
}
return person to userService.createRemoteUser(
RemoteUserCreateDto(
name = person.preferredUsername,
domain = id.substringAfter("://").substringBefore("/"),
screenName = person.name ?: person.preferredUsername,
description = person.summary.orEmpty(),
inbox = person.inbox,
outbox = person.outbox,
url = id,
publicKey = person.publicKey.publicKeyPem,
keyId = person.publicKey.id,
following = person.following,
followers = person.followers,
sharedInbox = person.endpoints["sharedInbox"],
locked = person.manuallyApprovesFollowers
)
)
} }
private fun entityToPerson( private fun entityToPerson(

View File

@ -1,8 +1,9 @@
package dev.usbharu.hideout.activitypub.service.webfinger package dev.usbharu.hideout.activitypub.service.webfinger
import dev.usbharu.hideout.application.external.Transaction import dev.usbharu.hideout.application.external.Transaction
import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException
import dev.usbharu.hideout.core.domain.model.actor.Actor import dev.usbharu.hideout.core.domain.model.actor.Actor
import dev.usbharu.hideout.core.query.ActorQueryService import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
@Service @Service
@ -11,11 +12,17 @@ interface WebFingerApiService {
} }
@Service @Service
class WebFingerApiServiceImpl(private val transaction: Transaction, private val actorQueryService: ActorQueryService) : class WebFingerApiServiceImpl(
private val transaction: Transaction,
private val actorRepository: ActorRepository
) :
WebFingerApiService { WebFingerApiService {
override suspend fun findByNameAndDomain(name: String, domain: String): Actor { override suspend fun findByNameAndDomain(name: String, domain: String): Actor {
return transaction.transaction { return transaction.transaction {
actorQueryService.findByNameAndDomain(name, domain) actorRepository.findByNameAndDomain(name, domain) ?: throw UserNotFoundException.withNameAndDomain(
name,
domain
)
} }
} }
} }

View File

@ -7,19 +7,18 @@ import com.nimbusds.jose.jwk.source.ImmutableJWKSet
import com.nimbusds.jose.jwk.source.JWKSource import com.nimbusds.jose.jwk.source.JWKSource
import com.nimbusds.jose.proc.SecurityContext import com.nimbusds.jose.proc.SecurityContext
import dev.usbharu.hideout.application.external.Transaction import dev.usbharu.hideout.application.external.Transaction
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.infrastructure.springframework.httpsignature.HttpSignatureFilter import dev.usbharu.hideout.core.infrastructure.springframework.httpsignature.HttpSignatureFilter
import dev.usbharu.hideout.core.infrastructure.springframework.httpsignature.HttpSignatureUserDetailsService import dev.usbharu.hideout.core.infrastructure.springframework.httpsignature.HttpSignatureUserDetailsService
import dev.usbharu.hideout.core.infrastructure.springframework.httpsignature.HttpSignatureVerifierComposite import dev.usbharu.hideout.core.infrastructure.springframework.httpsignature.HttpSignatureVerifierComposite
import dev.usbharu.hideout.core.infrastructure.springframework.oauth2.UserDetailsImpl import dev.usbharu.hideout.core.infrastructure.springframework.oauth2.UserDetailsImpl
import dev.usbharu.hideout.core.infrastructure.springframework.oauth2.UserDetailsServiceImpl import dev.usbharu.hideout.core.infrastructure.springframework.oauth2.UserDetailsServiceImpl
import dev.usbharu.hideout.core.query.ActorQueryService
import dev.usbharu.hideout.util.RsaUtil import dev.usbharu.hideout.util.RsaUtil
import dev.usbharu.hideout.util.hasAnyScope import dev.usbharu.hideout.util.hasAnyScope
import dev.usbharu.httpsignature.sign.RsaSha256HttpSignatureSigner import dev.usbharu.httpsignature.sign.RsaSha256HttpSignatureSigner
import dev.usbharu.httpsignature.verify.DefaultSignatureHeaderParser import dev.usbharu.httpsignature.verify.DefaultSignatureHeaderParser
import dev.usbharu.httpsignature.verify.RsaSha256HttpSignatureVerifier import dev.usbharu.httpsignature.verify.RsaSha256HttpSignatureVerifier
import jakarta.annotation.PostConstruct import jakarta.annotation.PostConstruct
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer
import org.springframework.boot.context.properties.ConfigurationProperties import org.springframework.boot.context.properties.ConfigurationProperties
@ -68,9 +67,6 @@ import java.util.*
@Suppress("FunctionMaxLength", "TooManyFunctions") @Suppress("FunctionMaxLength", "TooManyFunctions")
class SecurityConfig { class SecurityConfig {
@Autowired
private lateinit var actorQueryService: ActorQueryService
@Bean @Bean
fun authenticationManager(authenticationConfiguration: AuthenticationConfiguration): AuthenticationManager? = fun authenticationManager(authenticationConfiguration: AuthenticationConfiguration): AuthenticationManager? =
authenticationConfiguration.authenticationManager authenticationConfiguration.authenticationManager
@ -130,12 +126,14 @@ class SecurityConfig {
@Bean @Bean
@Order(1) @Order(1)
fun httpSignatureAuthenticationProvider(transaction: Transaction): PreAuthenticatedAuthenticationProvider { fun httpSignatureAuthenticationProvider(
transaction: Transaction,
actorRepository: ActorRepository
): PreAuthenticatedAuthenticationProvider {
val provider = PreAuthenticatedAuthenticationProvider() val provider = PreAuthenticatedAuthenticationProvider()
val signatureHeaderParser = DefaultSignatureHeaderParser() val signatureHeaderParser = DefaultSignatureHeaderParser()
provider.setPreAuthenticatedUserDetailsService( provider.setPreAuthenticatedUserDetailsService(
HttpSignatureUserDetailsService( HttpSignatureUserDetailsService(
actorQueryService,
HttpSignatureVerifierComposite( HttpSignatureVerifierComposite(
mapOf( mapOf(
"rsa-sha256" to RsaSha256HttpSignatureVerifier( "rsa-sha256" to RsaSha256HttpSignatureVerifier(
@ -145,7 +143,8 @@ class SecurityConfig {
signatureHeaderParser signatureHeaderParser
), ),
transaction, transaction,
signatureHeaderParser signatureHeaderParser,
actorRepository
) )
) )
provider.setUserDetailsChecker(AccountStatusUserDetailsChecker()) provider.setUserDetailsChecker(AccountStatusUserDetailsChecker())

View File

@ -1,25 +1,37 @@
package dev.usbharu.hideout.application.infrastructure.exposed package dev.usbharu.hideout.application.infrastructure.exposed
import dev.usbharu.hideout.application.external.Transaction import dev.usbharu.hideout.application.external.Transaction
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.slf4j.MDCContext import kotlinx.coroutines.slf4j.MDCContext
import org.jetbrains.exposed.sql.StdOutSqlLogger import org.jetbrains.exposed.sql.Slf4jSqlDebugLogger
import org.jetbrains.exposed.sql.addLogger import org.jetbrains.exposed.sql.addLogger
import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction
import org.jetbrains.exposed.sql.transactions.transaction
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import java.sql.Connection import java.sql.Connection
@Service @Service
class ExposedTransaction : Transaction { class ExposedTransaction : Transaction {
override suspend fun <T> transaction(block: suspend () -> T): T { override suspend fun <T> transaction(block: suspend () -> T): T {
return newSuspendedTransaction(MDCContext(), transactionIsolation = Connection.TRANSACTION_SERIALIZABLE) { // return newSuspendedTransaction(MDCContext(), transactionIsolation = java.sql.Connection.TRANSACTION_READ_COMMITTED) {
addLogger(StdOutSqlLogger) // warnLongQueriesDuration = 1000
block() // addLogger(Slf4jSqlDebugLogger)
// block()
// }
return transaction(transactionIsolation = Connection.TRANSACTION_READ_COMMITTED) {
debug = true
warnLongQueriesDuration = 1000
addLogger(Slf4jSqlDebugLogger)
runBlocking(MDCContext()) {
block()
}
} }
} }
override suspend fun <T> transaction(transactionLevel: Int, block: suspend () -> T): T { override suspend fun <T> transaction(transactionLevel: Int, block: suspend () -> T): T {
return newSuspendedTransaction(MDCContext(), transactionIsolation = transactionLevel) { return newSuspendedTransaction(MDCContext(), transactionIsolation = transactionLevel) {
addLogger(StdOutSqlLogger) addLogger(Slf4jSqlDebugLogger)
block() block()
} }
} }

View File

@ -0,0 +1,21 @@
package dev.usbharu.hideout.core.domain.exception
import java.io.Serial
open class HideoutException : RuntimeException {
constructor() : super()
constructor(message: String?) : super(message)
constructor(message: String?, cause: Throwable?) : super(message, cause)
constructor(cause: Throwable?) : super(cause)
constructor(message: String?, cause: Throwable?, enableSuppression: Boolean, writableStackTrace: Boolean) : super(
message,
cause,
enableSuppression,
writableStackTrace
)
companion object {
@Serial
private const val serialVersionUID: Long = 8506638570017469956L
}
}

View File

@ -0,0 +1,7 @@
package dev.usbharu.hideout.core.domain.exception
import dev.usbharu.hideout.core.domain.exception.resource.ResourceAccessException
interface SQLExceptionTranslator {
fun translate(message: String, sql: String? = null, exception: Exception): ResourceAccessException
}

View File

@ -0,0 +1,19 @@
package dev.usbharu.hideout.core.domain.exception
import dev.usbharu.hideout.core.domain.exception.resource.DuplicateException
import dev.usbharu.hideout.core.domain.exception.resource.ResourceAccessException
import org.springframework.dao.DataAccessException
import org.springframework.dao.DuplicateKeyException
class SpringDataAccessExceptionSQLExceptionTranslator : SQLExceptionTranslator {
override fun translate(message: String, sql: String?, exception: Exception): ResourceAccessException {
if (exception !is DataAccessException) {
throw IllegalArgumentException("exception must be DataAccessException.")
}
return when (exception) {
is DuplicateKeyException -> DuplicateException(message, exception.rootCause)
else -> ResourceAccessException(message, exception.rootCause)
}
}
}

View File

@ -0,0 +1,21 @@
package dev.usbharu.hideout.core.domain.exception.resource
import java.io.Serial
class DuplicateException : ResourceAccessException {
constructor() : super()
constructor(message: String?) : super(message)
constructor(message: String?, cause: Throwable?) : super(message, cause)
constructor(cause: Throwable?) : super(cause)
constructor(message: String?, cause: Throwable?, enableSuppression: Boolean, writableStackTrace: Boolean) : super(
message,
cause,
enableSuppression,
writableStackTrace
)
companion object {
@Serial
private const val serialVersionUID: Long = 7092046653037974417L
}
}

View File

@ -0,0 +1,14 @@
package dev.usbharu.hideout.core.domain.exception.resource
open class NotFoundException : ResourceAccessException {
constructor() : super()
constructor(message: String?) : super(message)
constructor(message: String?, cause: Throwable?) : super(message, cause)
constructor(cause: Throwable?) : super(cause)
constructor(message: String?, cause: Throwable?, enableSuppression: Boolean, writableStackTrace: Boolean) : super(
message,
cause,
enableSuppression,
writableStackTrace
)
}

View File

@ -0,0 +1,27 @@
package dev.usbharu.hideout.core.domain.exception.resource
import java.io.Serial
class PostNotFoundException : NotFoundException {
constructor() : super()
constructor(message: String?) : super(message)
constructor(message: String?, cause: Throwable?) : super(message, cause)
constructor(cause: Throwable?) : super(cause)
constructor(message: String?, cause: Throwable?, enableSuppression: Boolean, writableStackTrace: Boolean) : super(
message,
cause,
enableSuppression,
writableStackTrace
)
companion object {
@Serial
private const val serialVersionUID: Long = 1315818410686905717L
fun withApId(apId: String): PostNotFoundException = PostNotFoundException("apId: $apId was not found.")
fun withId(id: Long): PostNotFoundException = PostNotFoundException("id: $id was not found.")
fun withUrl(url: String): PostNotFoundException = PostNotFoundException("url: $url was not found.")
}
}

View File

@ -0,0 +1,16 @@
package dev.usbharu.hideout.core.domain.exception.resource
import dev.usbharu.hideout.core.domain.exception.HideoutException
open class ResourceAccessException : HideoutException {
constructor() : super()
constructor(message: String?) : super(message)
constructor(message: String?, cause: Throwable?) : super(message, cause)
constructor(cause: Throwable?) : super(cause)
constructor(message: String?, cause: Throwable?, enableSuppression: Boolean, writableStackTrace: Boolean) : super(
message,
cause,
enableSuppression,
writableStackTrace
)
}

View File

@ -0,0 +1,36 @@
package dev.usbharu.hideout.core.domain.exception.resource
import java.io.Serial
open class UserNotFoundException : NotFoundException {
constructor() : super()
constructor(message: String?) : super(message)
constructor(message: String?, cause: Throwable?) : super(message, cause)
constructor(cause: Throwable?) : super(cause)
constructor(message: String?, cause: Throwable?, enableSuppression: Boolean, writableStackTrace: Boolean) : super(
message,
cause,
enableSuppression,
writableStackTrace
)
companion object {
@Serial
private const val serialVersionUID: Long = 3219433672235626200L
fun withName(string: String, throwable: Throwable? = null): UserNotFoundException =
UserNotFoundException("name: $string was not found.", throwable)
fun withId(id: Long, throwable: Throwable? = null): UserNotFoundException =
UserNotFoundException("id: $id was not found.", throwable)
fun withUrl(url: String, throwable: Throwable? = null): UserNotFoundException =
UserNotFoundException("url: $url was not found.", throwable)
fun withNameAndDomain(name: String, domain: String, throwable: Throwable? = null): UserNotFoundException =
UserNotFoundException("name: $name domain: $domain (@$name@$domain) was not found.", throwable)
fun withKeyId(keyId: String, throwable: Throwable? = null) =
UserNotFoundException("keyId: $keyId was not found.", throwable)
}
}

View File

@ -0,0 +1,31 @@
package dev.usbharu.hideout.core.domain.exception.resource.local
import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException
import java.io.Serial
class LocalUserNotFoundException : UserNotFoundException {
constructor() : super()
constructor(message: String?) : super(message)
constructor(message: String?, cause: Throwable?) : super(message, cause)
constructor(cause: Throwable?) : super(cause)
constructor(message: String?, cause: Throwable?, enableSuppression: Boolean, writableStackTrace: Boolean) : super(
message,
cause,
enableSuppression,
writableStackTrace
)
companion object {
@Serial
private const val serialVersionUID: Long = -4742548128672528145L
fun withName(string: String, throwable: Throwable? = null): LocalUserNotFoundException =
LocalUserNotFoundException("name: $string was not found.", throwable)
fun withId(id: Long, throwable: Throwable? = null): LocalUserNotFoundException =
LocalUserNotFoundException("id: $id was not found.", throwable)
fun withUrl(url: String, throwable: Throwable? = null): LocalUserNotFoundException =
LocalUserNotFoundException("url: $url was not found.", throwable)
}
}

View File

@ -57,6 +57,31 @@ data class Actor private constructor(
postsCount: Int = 0, postsCount: Int = 0,
lastPostDate: Instant? = null lastPostDate: Instant? = null
): Actor { ): Actor {
if (id == 0L) {
return Actor(
id = id,
name = name,
domain = domain,
screenName = screenName,
description = description,
inbox = inbox,
outbox = outbox,
url = url,
publicKey = publicKey,
privateKey = privateKey,
createdAt = createdAt,
keyId = keyId,
followers = followers,
following = following,
instance = instance,
locked = locked,
followersCount = followersCount,
followingCount = followingCount,
postsCount = postsCount,
lastPostDate = lastPostDate
)
}
// idは0未満ではいけない // idは0未満ではいけない
require(id >= 0) { "id must be greater than or equal to 0." } require(id >= 0) { "id must be greater than or equal to 0." }

View File

@ -3,11 +3,30 @@ package dev.usbharu.hideout.core.domain.model.actor
import org.springframework.stereotype.Repository import org.springframework.stereotype.Repository
@Repository @Repository
@Suppress("TooManyFunctions")
interface ActorRepository { interface ActorRepository {
suspend fun save(actor: Actor): Actor suspend fun save(actor: Actor): Actor
suspend fun findById(id: Long): Actor? suspend fun findById(id: Long): Actor?
suspend fun findByIdWithLock(id: Long): Actor?
suspend fun findAll(limit: Int, offset: Long): List<Actor>
suspend fun findByName(name: String): List<Actor>
suspend fun findByNameAndDomain(name: String, domain: String): Actor?
suspend fun findByNameAndDomainWithLock(name: String, domain: String): Actor?
suspend fun findByUrl(url: String): Actor?
suspend fun findByUrlWithLock(url: String): Actor?
suspend fun findByIds(ids: List<Long>): List<Actor>
suspend fun findByKeyId(keyId: String): Actor?
suspend fun delete(id: Long) suspend fun delete(id: Long)
suspend fun nextId(): Long suspend fun nextId(): Long

View File

@ -3,5 +3,6 @@ package dev.usbharu.hideout.core.domain.model.deletedActor
interface DeletedActorRepository { interface DeletedActorRepository {
suspend fun save(deletedActor: DeletedActor): DeletedActor suspend fun save(deletedActor: DeletedActor): DeletedActor
suspend fun delete(deletedActor: DeletedActor) suspend fun delete(deletedActor: DeletedActor)
suspend fun findById(id: Long): DeletedActor suspend fun findById(id: Long): DeletedActor?
suspend fun findByNameAndDomain(name: String, domain: String): DeletedActor?
} }

View File

@ -3,6 +3,7 @@ package dev.usbharu.hideout.core.domain.model.instance
interface InstanceRepository { interface InstanceRepository {
suspend fun generateId(): Long suspend fun generateId(): Long
suspend fun save(instance: Instance): Instance suspend fun save(instance: Instance): Instance
suspend fun findById(id: Long): Instance suspend fun findById(id: Long): Instance?
suspend fun delete(instance: Instance) suspend fun delete(instance: Instance)
suspend fun findByUrl(url: String): Instance?
} }

View File

@ -3,6 +3,7 @@ package dev.usbharu.hideout.core.domain.model.media
interface MediaRepository { interface MediaRepository {
suspend fun generateId(): Long suspend fun generateId(): Long
suspend fun save(media: Media): Media suspend fun save(media: Media): Media
suspend fun findById(id: Long): Media suspend fun findById(id: Long): Media?
suspend fun delete(id: Long) suspend fun delete(id: Long)
suspend fun findByRemoteUrl(remoteUrl: String): Media?
} }

View File

@ -6,7 +6,12 @@ import org.springframework.stereotype.Repository
@Repository @Repository
interface PostRepository { interface PostRepository {
suspend fun generateId(): Long suspend fun generateId(): Long
suspend fun save(post: Post): Boolean suspend fun save(post: Post): Post
suspend fun delete(id: Long) suspend fun delete(id: Long)
suspend fun findById(id: Long): Post suspend fun findById(id: Long): Post?
suspend fun findByUrl(url: String): Post?
suspend fun findByApId(apId: String): Post?
suspend fun existByApIdWithLock(apId: String): Boolean
suspend fun findByActorId(actorId: Long): List<Post>
} }

View File

@ -3,10 +3,15 @@ package dev.usbharu.hideout.core.domain.model.reaction
import org.springframework.stereotype.Repository import org.springframework.stereotype.Repository
@Repository @Repository
@Suppress("FunctionMaxLength")
interface ReactionRepository { interface ReactionRepository {
suspend fun generateId(): Long suspend fun generateId(): Long
suspend fun save(reaction: Reaction): Reaction suspend fun save(reaction: Reaction): Reaction
suspend fun delete(reaction: Reaction): Reaction suspend fun delete(reaction: Reaction): Reaction
suspend fun deleteByPostId(postId: Long): Int suspend fun deleteByPostId(postId: Long): Int
suspend fun deleteByActorId(actorId: Long): Int suspend fun deleteByActorId(actorId: Long): Int
suspend fun findByPostId(postId: Long): List<Reaction>
suspend fun findByPostIdAndActorIdAndEmojiId(postId: Long, actorId: Long, emojiId: Long): Reaction?
suspend fun existByPostIdAndActorIdAndEmojiId(postId: Long, actorId: Long, emojiId: Long): Boolean
suspend fun findByPostIdAndActorId(postId: Long, actorId: Long): List<Reaction>
} }

View File

@ -30,4 +30,16 @@ interface RelationshipRepository {
suspend fun findByUserIdAndTargetUserId(actorId: Long, targetActorId: Long): Relationship? suspend fun findByUserIdAndTargetUserId(actorId: Long, targetActorId: Long): Relationship?
suspend fun deleteByActorIdOrTargetActorId(actorId: Long, targetActorId: Long) suspend fun deleteByActorIdOrTargetActorId(actorId: Long, targetActorId: Long)
suspend fun findByTargetIdAndFollowing(targetId: Long, following: Boolean): List<Relationship>
@Suppress("LongParameterList", "FunctionMaxLength")
suspend fun findByTargetIdAndFollowRequestAndIgnoreFollowRequest(
maxId: Long?,
sinceId: Long?,
limit: Int,
targetId: Long,
followRequest: Boolean,
ignoreFollowRequest: Boolean
): List<Relationship>
} }

View File

@ -1,21 +1,22 @@
package dev.usbharu.hideout.core.domain.model.relationship package dev.usbharu.hideout.core.domain.model.relationship
import dev.usbharu.hideout.core.infrastructure.exposedrepository.AbstractRepository
import dev.usbharu.hideout.core.infrastructure.exposedrepository.Actors import dev.usbharu.hideout.core.infrastructure.exposedrepository.Actors
import org.jetbrains.exposed.dao.id.LongIdTable import org.jetbrains.exposed.dao.id.LongIdTable
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
@Service @Service
class RelationshipRepositoryImpl : RelationshipRepository { class RelationshipRepositoryImpl : RelationshipRepository, AbstractRepository() {
override suspend fun save(relationship: Relationship): Relationship { override suspend fun save(relationship: Relationship): Relationship = query {
val singleOrNull = val singleOrNull = Relationships.select {
Relationships (Relationships.actorId eq relationship.actorId).and(
.select { Relationships.targetActorId eq relationship.targetActorId
(Relationships.actorId eq relationship.actorId) )
.and(Relationships.targetActorId eq relationship.targetActorId) }.forUpdate().singleOrNull()
}
.singleOrNull()
if (singleOrNull == null) { if (singleOrNull == null) {
Relationships.insert { Relationships.insert {
@ -28,41 +29,77 @@ class RelationshipRepositoryImpl : RelationshipRepository {
it[ignoreFollowRequestFromTarget] = relationship.ignoreFollowRequestToTarget it[ignoreFollowRequestFromTarget] = relationship.ignoreFollowRequestToTarget
} }
} else { } else {
Relationships Relationships.update({
.update({ (Relationships.actorId eq relationship.actorId).and(
(Relationships.actorId eq relationship.actorId) Relationships.targetActorId eq relationship.targetActorId
.and(Relationships.targetActorId eq relationship.targetActorId) )
}) { }) {
it[following] = relationship.following it[following] = relationship.following
it[blocking] = relationship.blocking it[blocking] = relationship.blocking
it[muting] = relationship.muting it[muting] = relationship.muting
it[followRequest] = relationship.followRequest it[followRequest] = relationship.followRequest
it[ignoreFollowRequestFromTarget] = relationship.ignoreFollowRequestToTarget it[ignoreFollowRequestFromTarget] = relationship.ignoreFollowRequestToTarget
} }
} }
return relationship return@query relationship
} }
override suspend fun delete(relationship: Relationship) { override suspend fun delete(relationship: Relationship): Unit = query {
Relationships.deleteWhere { Relationships.deleteWhere {
(Relationships.actorId eq relationship.actorId) (Relationships.actorId eq relationship.actorId).and(
.and(Relationships.targetActorId eq relationship.targetActorId) Relationships.targetActorId eq relationship.targetActorId
)
} }
} }
override suspend fun findByUserIdAndTargetUserId(actorId: Long, targetActorId: Long): Relationship? { override suspend fun findByUserIdAndTargetUserId(actorId: Long, targetActorId: Long): Relationship? = query {
return Relationships.select { return@query Relationships.select {
(Relationships.actorId eq actorId) (Relationships.actorId eq actorId).and(Relationships.targetActorId eq targetActorId)
.and(Relationships.targetActorId eq targetActorId) }.singleOrNull()?.toRelationships()
}.singleOrNull()
?.toRelationships()
} }
override suspend fun deleteByActorIdOrTargetActorId(actorId: Long, targetActorId: Long) { override suspend fun deleteByActorIdOrTargetActorId(actorId: Long, targetActorId: Long): Unit = query {
Relationships.deleteWhere { Relationships.deleteWhere {
Relationships.actorId.eq(actorId).or(Relationships.targetActorId.eq(targetActorId)) Relationships.actorId.eq(actorId).or(Relationships.targetActorId.eq(targetActorId))
} }
} }
override suspend fun findByTargetIdAndFollowing(targetId: Long, following: Boolean): List<Relationship> = query {
return@query Relationships
.select { Relationships.targetActorId eq targetId and (Relationships.following eq following) }
.map { it.toRelationships() }
}
override suspend fun findByTargetIdAndFollowRequestAndIgnoreFollowRequest(
maxId: Long?,
sinceId: Long?,
limit: Int,
targetId: Long,
followRequest: Boolean,
ignoreFollowRequest: Boolean
): List<Relationship> = query {
val query = Relationships.select {
Relationships.targetActorId.eq(targetId).and(Relationships.followRequest.eq(followRequest))
.and(Relationships.ignoreFollowRequestFromTarget.eq(ignoreFollowRequest))
}.limit(limit)
if (maxId != null) {
query.andWhere { Relationships.id lessEq maxId }
}
if (sinceId != null) {
query.andWhere { Relationships.id greaterEq sinceId }
}
return@query query.map { it.toRelationships() }
}
override val logger: Logger
get() = Companion.logger
companion object {
private val logger = LoggerFactory.getLogger(RelationshipRepositoryImpl::class.java)
}
} }
fun ResultRow.toRelationships(): Relationship = Relationship( fun ResultRow.toRelationships(): Relationship = Relationship(

View File

@ -1,60 +0,0 @@
package dev.usbharu.hideout.core.infrastructure.exposedquery
import dev.usbharu.hideout.application.infrastructure.exposed.QueryMapper
import dev.usbharu.hideout.application.infrastructure.exposed.ResultRowMapper
import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException
import dev.usbharu.hideout.core.domain.model.actor.Actor
import dev.usbharu.hideout.core.infrastructure.exposedrepository.Actors
import dev.usbharu.hideout.core.query.ActorQueryService
import dev.usbharu.hideout.util.singleOr
import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.selectAll
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Repository
@Repository
class ActorQueryServiceImpl(
private val actorResultRowMapper: ResultRowMapper<Actor>,
private val actorQueryMapper: QueryMapper<Actor>
) : ActorQueryService {
private val logger = LoggerFactory.getLogger(ActorQueryServiceImpl::class.java)
override suspend fun findAll(limit: Int, offset: Long): List<Actor> =
Actors.selectAll().limit(limit, offset).let(actorQueryMapper::map)
override suspend fun findById(id: Long): Actor = Actors.select { Actors.id eq id }
.singleOr { FailedToGetResourcesException("id: $id is duplicate or does not exist.", it) }
.let(actorResultRowMapper::map)
override suspend fun findByName(name: String): List<Actor> =
Actors.select { Actors.name eq name }.let(actorQueryMapper::map)
override suspend fun findByNameAndDomain(name: String, domain: String): Actor =
Actors
.select { Actors.name eq name and (Actors.domain eq domain) }
.singleOr {
FailedToGetResourcesException("name: $name,domain: $domain is duplicate or does not exist.", it)
}
.let(actorResultRowMapper::map)
override suspend fun findByUrl(url: String): Actor {
logger.trace("findByUrl url: $url")
return Actors.select { Actors.url eq url }
.singleOr { FailedToGetResourcesException("url: $url is duplicate or does not exist.", it) }
.let(actorResultRowMapper::map)
}
override suspend fun findByIds(ids: List<Long>): List<Actor> =
Actors.select { Actors.id inList ids }.let(actorQueryMapper::map)
override suspend fun existByNameAndDomain(name: String, domain: String): Boolean =
Actors.select { Actors.name eq name and (Actors.domain eq domain) }.empty().not()
override suspend fun findByKeyId(keyId: String): Actor {
return Actors.select { Actors.keyId eq keyId }
.singleOr { FailedToGetResourcesException("keyId: $keyId is duplicate or does not exist.", it) }
.let(actorResultRowMapper::map)
}
}

View File

@ -1,23 +0,0 @@
package dev.usbharu.hideout.core.infrastructure.exposedquery
import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException
import dev.usbharu.hideout.core.domain.model.deletedActor.DeletedActor
import dev.usbharu.hideout.core.infrastructure.exposedrepository.DeletedActors
import dev.usbharu.hideout.core.infrastructure.exposedrepository.toDeletedActor
import dev.usbharu.hideout.core.query.DeletedActorQueryService
import dev.usbharu.hideout.util.singleOr
import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.select
import org.springframework.stereotype.Repository
@Repository
class DeletedActorQueryServiceImpl : DeletedActorQueryService {
override suspend fun findByNameAndDomain(name: String, domain: String): DeletedActor {
return DeletedActors
.select { DeletedActors.name eq name and (DeletedActors.domain eq domain) }
.singleOr {
FailedToGetResourcesException("name: $name domain: $domain was not exist or duplicate.", it)
}
.toDeletedActor()
}
}

View File

@ -1,21 +1,19 @@
package dev.usbharu.hideout.core.infrastructure.exposedquery package dev.usbharu.hideout.core.infrastructure.exposedquery
import dev.usbharu.hideout.core.domain.model.actor.Actor import dev.usbharu.hideout.core.domain.model.actor.Actor
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository
import dev.usbharu.hideout.core.query.ActorQueryService
import dev.usbharu.hideout.core.query.FollowerQueryService import dev.usbharu.hideout.core.query.FollowerQueryService
import dev.usbharu.hideout.core.query.RelationshipQueryService
import org.springframework.stereotype.Repository import org.springframework.stereotype.Repository
@Repository @Repository
class FollowerQueryServiceImpl( class FollowerQueryServiceImpl(
private val relationshipQueryService: RelationshipQueryService, private val relationshipRepository: RelationshipRepository,
private val actorQueryService: ActorQueryService, private val actorRepository: ActorRepository
private val relationshipRepository: RelationshipRepository
) : FollowerQueryService { ) : FollowerQueryService {
override suspend fun findFollowersById(id: Long): List<Actor> { override suspend fun findFollowersById(id: Long): List<Actor> {
return actorQueryService.findByIds( return actorRepository.findByIds(
relationshipQueryService.findByTargetIdAndFollowing(id, true).map { it.actorId } relationshipRepository.findByTargetIdAndFollowing(id, true).map { it.actorId }
) )
} }

View File

@ -1,16 +0,0 @@
package dev.usbharu.hideout.core.infrastructure.exposedquery
import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException
import dev.usbharu.hideout.core.infrastructure.exposedrepository.Instance
import dev.usbharu.hideout.core.infrastructure.exposedrepository.toInstance
import dev.usbharu.hideout.core.query.InstanceQueryService
import dev.usbharu.hideout.util.singleOr
import org.jetbrains.exposed.sql.select
import org.springframework.stereotype.Repository
import dev.usbharu.hideout.core.domain.model.instance.Instance as InstanceEntity
@Repository
class InstanceQueryServiceImpl : InstanceQueryService {
override suspend fun findByUrl(url: String): InstanceEntity = Instance.select { Instance.url eq url }
.singleOr { FailedToGetResourcesException("$url is doesn't exist", it) }.toInstance()
}

View File

@ -1,31 +0,0 @@
package dev.usbharu.hideout.core.infrastructure.exposedquery
import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException
import dev.usbharu.hideout.core.infrastructure.exposedrepository.Media
import dev.usbharu.hideout.core.infrastructure.exposedrepository.PostsMedia
import dev.usbharu.hideout.core.infrastructure.exposedrepository.toMedia
import dev.usbharu.hideout.core.query.MediaQueryService
import dev.usbharu.hideout.util.singleOr
import org.jetbrains.exposed.sql.innerJoin
import org.jetbrains.exposed.sql.select
import org.springframework.stereotype.Repository
import dev.usbharu.hideout.core.domain.model.media.Media as MediaEntity
@Repository
class MediaQueryServiceImpl : MediaQueryService {
override suspend fun findByPostId(postId: Long): List<MediaEntity> {
return Media.innerJoin(
PostsMedia,
onColumn = { id },
otherColumn = { mediaId }
)
.select { PostsMedia.postId eq postId }
.map { it.toMedia() }
}
override suspend fun findByRemoteUrl(remoteUrl: String): MediaEntity {
return Media.select { Media.remoteUrl eq remoteUrl }
.singleOr { FailedToGetResourcesException("remoteUrl: $remoteUrl is duplicate or not exist.", it) }
.toMedia()
}
}

View File

@ -1,39 +0,0 @@
package dev.usbharu.hideout.core.infrastructure.exposedquery
import dev.usbharu.hideout.application.infrastructure.exposed.QueryMapper
import dev.usbharu.hideout.application.infrastructure.exposed.ResultRowMapper
import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException
import dev.usbharu.hideout.core.domain.model.post.Post
import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts
import dev.usbharu.hideout.core.infrastructure.exposedrepository.PostsMedia
import dev.usbharu.hideout.core.query.PostQueryService
import dev.usbharu.hideout.util.singleOr
import org.jetbrains.exposed.sql.select
import org.springframework.stereotype.Repository
@Repository
class PostQueryServiceImpl(
private val postResultRowMapper: ResultRowMapper<Post>,
private val postQueryMapper: QueryMapper<Post>
) : PostQueryService {
override suspend fun findById(id: Long): Post =
Posts.leftJoin(PostsMedia)
.select { Posts.id eq id }
.singleOr { FailedToGetResourcesException("id: $id is duplicate or does not exist.", it) }
.let(postResultRowMapper::map)
override suspend fun findByUrl(url: String): Post =
Posts.leftJoin(PostsMedia)
.select { Posts.url eq url }
.let(postQueryMapper::map)
.singleOr { FailedToGetResourcesException("url: $url is duplicate or does not exist.", it) }
override suspend fun findByApId(string: String): Post =
Posts.leftJoin(PostsMedia)
.select { Posts.apId eq string }
.let(postQueryMapper::map)
.singleOr { FailedToGetResourcesException("apId: $string is duplicate or does not exist.", it) }
override suspend fun findByActorId(actorId: Long): List<Post> =
Posts.leftJoin(PostsMedia).select { Posts.actorId eq actorId }.let(postQueryMapper::map)
}

View File

@ -1,50 +0,0 @@
package dev.usbharu.hideout.core.infrastructure.exposedquery
import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException
import dev.usbharu.hideout.core.domain.model.reaction.Reaction
import dev.usbharu.hideout.core.infrastructure.exposedrepository.Reactions
import dev.usbharu.hideout.core.infrastructure.exposedrepository.toReaction
import dev.usbharu.hideout.core.query.ReactionQueryService
import dev.usbharu.hideout.util.singleOr
import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.select
import org.springframework.stereotype.Repository
@Repository
class ReactionQueryServiceImpl : ReactionQueryService {
override suspend fun findByPostId(postId: Long, actorId: Long?): List<Reaction> {
return Reactions.select {
Reactions.postId.eq(postId)
}.map { it.toReaction() }
}
@Suppress("FunctionMaxLength")
override suspend fun findByPostIdAndActorIdAndEmojiId(postId: Long, actorId: Long, emojiId: Long): Reaction {
return Reactions
.select {
Reactions.postId.eq(postId).and(Reactions.actorId.eq(actorId)).and(
Reactions.emojiId.eq(emojiId)
)
}
.singleOr {
FailedToGetResourcesException(
"postId: $postId,userId: $actorId,emojiId: $emojiId is duplicate or does not exist.",
it
)
}
.toReaction()
}
override suspend fun reactionAlreadyExist(postId: Long, actorId: Long, emojiId: Long): Boolean {
return Reactions.select {
Reactions.postId.eq(postId).and(Reactions.actorId.eq(actorId)).and(
Reactions.emojiId.eq(emojiId)
)
}.empty().not()
}
override suspend fun findByPostIdAndActorId(postId: Long, actorId: Long): List<Reaction> {
return Reactions.select { Reactions.postId eq postId and (Reactions.actorId eq actorId) }
.map { it.toReaction() }
}
}

View File

@ -1,43 +0,0 @@
package dev.usbharu.hideout.core.infrastructure.exposedquery
import dev.usbharu.hideout.core.domain.model.relationship.Relationship
import dev.usbharu.hideout.core.domain.model.relationship.Relationships
import dev.usbharu.hideout.core.domain.model.relationship.toRelationships
import dev.usbharu.hideout.core.query.RelationshipQueryService
import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.andWhere
import org.jetbrains.exposed.sql.select
import org.springframework.stereotype.Service
@Service
class RelationshipQueryServiceImpl : RelationshipQueryService {
override suspend fun findByTargetIdAndFollowing(targetId: Long, following: Boolean): List<Relationship> =
Relationships.select { Relationships.targetActorId eq targetId and (Relationships.following eq following) }
.map { it.toRelationships() }
override suspend fun findByTargetIdAndFollowRequestAndIgnoreFollowRequest(
maxId: Long?,
sinceId: Long?,
limit: Int,
targetId: Long,
followRequest: Boolean,
ignoreFollowRequest: Boolean
): List<Relationship> {
val query = Relationships
.select {
Relationships.targetActorId.eq(targetId)
.and(Relationships.followRequest.eq(followRequest))
.and(Relationships.ignoreFollowRequestFromTarget.eq(ignoreFollowRequest))
}.limit(limit)
if (maxId != null) {
query.andWhere { Relationships.id greater maxId }
}
if (sinceId != null) {
query.andWhere { Relationships.id less sinceId }
}
return query.map { it.toRelationships() }
}
}

View File

@ -0,0 +1,63 @@
package dev.usbharu.hideout.core.infrastructure.exposedrepository
import dev.usbharu.hideout.core.domain.exception.SpringDataAccessExceptionSQLExceptionTranslator
import org.slf4j.Logger
import org.springframework.beans.factory.annotation.Value
import org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator
import java.sql.SQLException
@Suppress("VarCouldBeVal")
abstract class AbstractRepository {
protected abstract val logger: Logger
private val sqlErrorCodeSQLExceptionTranslator = SQLErrorCodeSQLExceptionTranslator()
private val springDataAccessExceptionSQLExceptionTranslator = SpringDataAccessExceptionSQLExceptionTranslator()
@Value("\${hideout.debug.trace-query-exception:false}")
private var traceQueryException: Boolean = false
@Value("\${hideout.debug.trace-query-call:false}")
private var traceQueryCall: Boolean = false
protected suspend fun <T> query(block: () -> T): T = try {
if (traceQueryCall) {
@Suppress("ThrowingExceptionsWithoutMessageOrCause")
logger.trace(
"""
***** QUERY CALL STACK TRACE *****
${Throwable().stackTrace.joinToString("\n")}
***** QUERY CALL STACK TRACE *****
"""
)
}
block.invoke()
} catch (e: SQLException) {
if (traceQueryException) {
logger.trace("FAILED EXECUTE SQL", e)
}
if (e.cause !is SQLException) {
throw e
}
val dataAccessException =
sqlErrorCodeSQLExceptionTranslator.translate("Failed to persist entity", null, e.cause as SQLException)
?: throw e
if (traceQueryException) {
logger.trace("EXCEPTION TRANSLATED TO", dataAccessException)
}
val translate = springDataAccessExceptionSQLExceptionTranslator.translate(
"Failed to persist entity",
null,
dataAccessException
)
if (traceQueryException) {
logger.trace("EXCEPTION TRANSLATED TO", translate)
}
throw translate
}
}

View File

@ -1,5 +1,6 @@
package dev.usbharu.hideout.core.infrastructure.exposedrepository package dev.usbharu.hideout.core.infrastructure.exposedrepository
import dev.usbharu.hideout.application.infrastructure.exposed.QueryMapper
import dev.usbharu.hideout.application.infrastructure.exposed.ResultRowMapper import dev.usbharu.hideout.application.infrastructure.exposed.ResultRowMapper
import dev.usbharu.hideout.application.service.id.IdGenerateService import dev.usbharu.hideout.application.service.id.IdGenerateService
import dev.usbharu.hideout.core.domain.model.actor.Actor import dev.usbharu.hideout.core.domain.model.actor.Actor
@ -7,17 +8,21 @@ import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.javatime.timestamp import org.jetbrains.exposed.sql.javatime.timestamp
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Repository import org.springframework.stereotype.Repository
@Repository @Repository
class ActorRepositoryImpl( class ActorRepositoryImpl(
private val idGenerateService: IdGenerateService, private val idGenerateService: IdGenerateService,
private val actorResultRowMapper: ResultRowMapper<Actor> private val actorResultRowMapper: ResultRowMapper<Actor>,
) : private val actorQueryMapper: QueryMapper<Actor>
ActorRepository { ) : ActorRepository, AbstractRepository() {
override val logger: Logger
get() = Companion.logger
override suspend fun save(actor: Actor): Actor { override suspend fun save(actor: Actor): Actor = query {
val singleOrNull = Actors.select { Actors.id eq actor.id }.empty() val singleOrNull = Actors.select { Actors.id eq actor.id }.forUpdate().empty()
if (singleOrNull) { if (singleOrNull) {
Actors.insert { Actors.insert {
it[id] = actor.id it[id] = actor.id
@ -64,17 +69,60 @@ class ActorRepositoryImpl(
it[lastPostAt] = actor.lastPostDate it[lastPostAt] = actor.lastPostDate
} }
} }
return actor return@query actor
} }
override suspend fun findById(id: Long): Actor? = override suspend fun findById(id: Long): Actor? = query {
Actors.select { Actors.id eq id }.singleOrNull()?.let(actorResultRowMapper::map) return@query Actors.select { Actors.id eq id }.singleOrNull()?.let(actorResultRowMapper::map)
}
override suspend fun delete(id: Long) { override suspend fun findByIdWithLock(id: Long): Actor? = query {
return@query Actors.select { Actors.id eq id }.forUpdate().singleOrNull()?.let(actorResultRowMapper::map)
}
override suspend fun findAll(limit: Int, offset: Long): List<Actor> = query {
return@query Actors.selectAll().limit(limit, offset).let(actorQueryMapper::map)
}
override suspend fun findByName(name: String): List<Actor> = query {
return@query Actors.select { Actors.name eq name }.let(actorQueryMapper::map)
}
override suspend fun findByNameAndDomain(name: String, domain: String): Actor? = query {
return@query Actors.select { Actors.name eq name and (Actors.domain eq domain) }.singleOrNull()
?.let(actorResultRowMapper::map)
}
override suspend fun findByNameAndDomainWithLock(name: String, domain: String): Actor? = query {
return@query Actors.select { Actors.name eq name and (Actors.domain eq domain) }.forUpdate().singleOrNull()
?.let(actorResultRowMapper::map)
}
override suspend fun findByUrl(url: String): Actor? = query {
return@query Actors.select { Actors.url eq url }.singleOrNull()?.let(actorResultRowMapper::map)
}
override suspend fun findByUrlWithLock(url: String): Actor? = query {
return@query Actors.select { Actors.url eq url }.forUpdate().singleOrNull()?.let(actorResultRowMapper::map)
}
override suspend fun findByIds(ids: List<Long>): List<Actor> = query {
return@query Actors.select { Actors.id inList ids }.let(actorQueryMapper::map)
}
override suspend fun findByKeyId(keyId: String): Actor? = query {
return@query Actors.select { Actors.keyId eq keyId }.singleOrNull()?.let(actorResultRowMapper::map)
}
override suspend fun delete(id: Long): Unit = query {
Actors.deleteWhere { Actors.id.eq(id) } Actors.deleteWhere { Actors.id.eq(id) }
} }
override suspend fun nextId(): Long = idGenerateService.generateId() override suspend fun nextId(): Long = idGenerateService.generateId()
companion object {
private val logger = LoggerFactory.getLogger(ActorRepositoryImpl::class.java)
}
} }
object Actors : Table("actors") { object Actors : Table("actors") {

View File

@ -1,18 +1,21 @@
package dev.usbharu.hideout.core.infrastructure.exposedrepository package dev.usbharu.hideout.core.infrastructure.exposedrepository
import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException
import dev.usbharu.hideout.core.domain.model.deletedActor.DeletedActor import dev.usbharu.hideout.core.domain.model.deletedActor.DeletedActor
import dev.usbharu.hideout.core.domain.model.deletedActor.DeletedActorRepository import dev.usbharu.hideout.core.domain.model.deletedActor.DeletedActorRepository
import dev.usbharu.hideout.util.singleOr
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.javatime.timestamp import org.jetbrains.exposed.sql.javatime.timestamp
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Repository import org.springframework.stereotype.Repository
@Repository @Repository
class DeletedActorRepositoryImpl : DeletedActorRepository { class DeletedActorRepositoryImpl : DeletedActorRepository, AbstractRepository() {
override suspend fun save(deletedActor: DeletedActor): DeletedActor { override val logger: Logger
val singleOrNull = DeletedActors.select { DeletedActors.id eq deletedActor.id }.singleOrNull() get() = Companion.logger
override suspend fun save(deletedActor: DeletedActor): DeletedActor = query {
val singleOrNull = DeletedActors.select { DeletedActors.id eq deletedActor.id }.forUpdate().singleOrNull()
if (singleOrNull == null) { if (singleOrNull == null) {
DeletedActors.insert { DeletedActors.insert {
@ -30,18 +33,29 @@ class DeletedActorRepositoryImpl : DeletedActorRepository {
it[DeletedActors.deletedAt] = deletedActor.deletedAt it[DeletedActors.deletedAt] = deletedActor.deletedAt
} }
} }
return deletedActor return@query deletedActor
} }
override suspend fun delete(deletedActor: DeletedActor) { override suspend fun delete(deletedActor: DeletedActor): Unit = query {
DeletedActors.deleteWhere { DeletedActors.id eq deletedActor.id } DeletedActors.deleteWhere { DeletedActors.id eq deletedActor.id }
} }
override suspend fun findById(id: Long): DeletedActor { override suspend fun findById(id: Long): DeletedActor? = query {
val singleOr = DeletedActors.select { DeletedActors.id eq id } return@query DeletedActors
.singleOr { FailedToGetResourcesException("id: $id was not exist or duplicate", it) } .select { DeletedActors.id eq id }
.singleOrNull()
?.toDeletedActor()
}
return deletedActor(singleOr) override suspend fun findByNameAndDomain(name: String, domain: String): DeletedActor? = query {
return@query DeletedActors
.select { DeletedActors.name eq name and (DeletedActors.domain eq domain) }
.singleOrNull()
?.toDeletedActor()
}
companion object {
private val logger = LoggerFactory.getLogger(DeletedActorRepositoryImpl::class.java)
} }
} }

View File

@ -5,6 +5,8 @@ import dev.usbharu.hideout.core.domain.model.post.Visibility
import dev.usbharu.hideout.core.domain.model.timeline.Timeline import dev.usbharu.hideout.core.domain.model.timeline.Timeline
import dev.usbharu.hideout.core.domain.model.timeline.TimelineRepository import dev.usbharu.hideout.core.domain.model.timeline.TimelineRepository
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Qualifier import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.stereotype.Repository import org.springframework.stereotype.Repository
@ -12,11 +14,15 @@ import org.springframework.stereotype.Repository
@Repository @Repository
@Qualifier("jdbc") @Qualifier("jdbc")
@ConditionalOnProperty("hideout.use-mongodb", havingValue = "false", matchIfMissing = true) @ConditionalOnProperty("hideout.use-mongodb", havingValue = "false", matchIfMissing = true)
class ExposedTimelineRepository(private val idGenerateService: IdGenerateService) : TimelineRepository { class ExposedTimelineRepository(private val idGenerateService: IdGenerateService) : TimelineRepository,
AbstractRepository() {
override val logger: Logger
get() = Companion.logger
override suspend fun generateId(): Long = idGenerateService.generateId() override suspend fun generateId(): Long = idGenerateService.generateId()
override suspend fun save(timeline: Timeline): Timeline { override suspend fun save(timeline: Timeline): Timeline = query {
if (Timelines.select { Timelines.id eq timeline.id }.singleOrNull() == null) { if (Timelines.select { Timelines.id eq timeline.id }.forUpdate().singleOrNull() == null) {
Timelines.insert { Timelines.insert {
it[id] = timeline.id it[id] = timeline.id
it[userId] = timeline.userId it[userId] = timeline.userId
@ -48,10 +54,10 @@ class ExposedTimelineRepository(private val idGenerateService: IdGenerateService
it[mediaIds] = timeline.mediaIds.joinToString(",") it[mediaIds] = timeline.mediaIds.joinToString(",")
} }
} }
return timeline return@query timeline
} }
override suspend fun saveAll(timelines: List<Timeline>): List<Timeline> { override suspend fun saveAll(timelines: List<Timeline>): List<Timeline> = query {
Timelines.batchInsert(timelines, true, false) { Timelines.batchInsert(timelines, true, false) {
this[Timelines.id] = it.id this[Timelines.id] = it.id
this[Timelines.userId] = it.userId this[Timelines.userId] = it.userId
@ -67,15 +73,21 @@ class ExposedTimelineRepository(private val idGenerateService: IdGenerateService
this[Timelines.isPureRepost] = it.isPureRepost this[Timelines.isPureRepost] = it.isPureRepost
this[Timelines.mediaIds] = it.mediaIds.joinToString(",") this[Timelines.mediaIds] = it.mediaIds.joinToString(",")
} }
return timelines return@query timelines
} }
override suspend fun findByUserId(id: Long): List<Timeline> = override suspend fun findByUserId(id: Long): List<Timeline> = query {
Timelines.select { Timelines.userId eq id }.map { it.toTimeline() } return@query Timelines.select { Timelines.userId eq id }.map { it.toTimeline() }
}
override suspend fun findByUserIdAndTimelineId(userId: Long, timelineId: Long): List<Timeline> = override suspend fun findByUserIdAndTimelineId(userId: Long, timelineId: Long): List<Timeline> = query {
Timelines.select { Timelines.userId eq userId and (Timelines.timelineId eq timelineId) } return@query Timelines.select { Timelines.userId eq userId and (Timelines.timelineId eq timelineId) }
.map { it.toTimeline() } .map { it.toTimeline() }
}
companion object {
private val logger = LoggerFactory.getLogger(ExposedTimelineRepository::class.java)
}
} }
fun ResultRow.toTimeline(): Timeline { fun ResultRow.toTimeline(): Timeline {

View File

@ -1,21 +1,25 @@
package dev.usbharu.hideout.core.infrastructure.exposedrepository package dev.usbharu.hideout.core.infrastructure.exposedrepository
import dev.usbharu.hideout.application.service.id.IdGenerateService import dev.usbharu.hideout.application.service.id.IdGenerateService
import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException
import dev.usbharu.hideout.core.domain.model.instance.InstanceRepository import dev.usbharu.hideout.core.domain.model.instance.InstanceRepository
import dev.usbharu.hideout.util.singleOr
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.javatime.timestamp import org.jetbrains.exposed.sql.javatime.timestamp
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Repository import org.springframework.stereotype.Repository
import dev.usbharu.hideout.core.domain.model.instance.Instance as InstanceEntity import dev.usbharu.hideout.core.domain.model.instance.Instance as InstanceEntity
@Repository @Repository
class InstanceRepositoryImpl(private val idGenerateService: IdGenerateService) : InstanceRepository { class InstanceRepositoryImpl(private val idGenerateService: IdGenerateService) : InstanceRepository,
AbstractRepository() {
override val logger: Logger
get() = Companion.logger
override suspend fun generateId(): Long = idGenerateService.generateId() override suspend fun generateId(): Long = idGenerateService.generateId()
override suspend fun save(instance: InstanceEntity): InstanceEntity { override suspend fun save(instance: InstanceEntity): InstanceEntity = query {
if (Instance.select { Instance.id.eq(instance.id) }.empty()) { if (Instance.select { Instance.id.eq(instance.id) }.forUpdate().empty()) {
Instance.insert { Instance.insert {
it[id] = instance.id it[id] = instance.id
it[name] = instance.name it[name] = instance.name
@ -45,17 +49,25 @@ class InstanceRepositoryImpl(private val idGenerateService: IdGenerateService) :
it[createdAt] = instance.createdAt it[createdAt] = instance.createdAt
} }
} }
return instance return@query instance
} }
override suspend fun findById(id: Long): InstanceEntity { override suspend fun findById(id: Long): InstanceEntity? = query {
return Instance.select { Instance.id eq id } return@query Instance.select { Instance.id eq id }
.singleOr { FailedToGetResourcesException("id: $id doesn't exist.") }.toInstance() .singleOrNull()?.toInstance()
} }
override suspend fun delete(instance: InstanceEntity) { override suspend fun delete(instance: InstanceEntity): Unit = query {
Instance.deleteWhere { id eq instance.id } Instance.deleteWhere { id eq instance.id }
} }
override suspend fun findByUrl(url: String): dev.usbharu.hideout.core.domain.model.instance.Instance? = query {
return@query Instance.select { Instance.url eq url }.singleOrNull()?.toInstance()
}
companion object {
private val logger = LoggerFactory.getLogger(InstanceRepositoryImpl::class.java)
}
} }
fun ResultRow.toInstance(): InstanceEntity { fun ResultRow.toInstance(): InstanceEntity {

View File

@ -1,25 +1,28 @@
package dev.usbharu.hideout.core.infrastructure.exposedrepository package dev.usbharu.hideout.core.infrastructure.exposedrepository
import dev.usbharu.hideout.application.service.id.IdGenerateService import dev.usbharu.hideout.application.service.id.IdGenerateService
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.infrastructure.exposedrepository.Media.mimeType import dev.usbharu.hideout.core.infrastructure.exposedrepository.Media.mimeType
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.util.singleOr
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Repository import org.springframework.stereotype.Repository
import dev.usbharu.hideout.core.domain.model.media.Media as EntityMedia import dev.usbharu.hideout.core.domain.model.media.Media as EntityMedia
@Repository @Repository
class MediaRepositoryImpl(private val idGenerateService: IdGenerateService) : MediaRepository { class MediaRepositoryImpl(private val idGenerateService: IdGenerateService) : MediaRepository, AbstractRepository() {
override val logger: Logger
get() = Companion.logger
override suspend fun generateId(): Long = idGenerateService.generateId() override suspend fun generateId(): Long = idGenerateService.generateId()
override suspend fun save(media: EntityMedia): EntityMedia { override suspend fun save(media: EntityMedia): EntityMedia = query {
if (Media.select { if (Media.select {
Media.id eq media.id Media.id eq media.id
}.singleOrNull() != null }.forUpdate().singleOrNull() != null
) { ) {
Media.update({ Media.id eq media.id }) { Media.update({ Media.id eq media.id }) {
it[name] = media.name it[name] = media.name
@ -44,24 +47,32 @@ class MediaRepositoryImpl(private val idGenerateService: IdGenerateService) : Me
it[description] = media.description it[description] = media.description
} }
} }
return media return@query media
} }
override suspend fun findById(id: Long): EntityMedia { override suspend fun findById(id: Long): EntityMedia? = query {
return Media return@query Media
.select { .select {
Media.id eq id Media.id eq id
} }
.singleOr { .singleOrNull()
FailedToGetResourcesException("id: $id was not found.") ?.toMedia()
}.toMedia()
} }
override suspend fun delete(id: Long) { override suspend fun delete(id: Long): Unit = query {
Media.deleteWhere { Media.deleteWhere {
Media.id eq id Media.id eq id
} }
} }
override suspend fun findByRemoteUrl(remoteUrl: String): dev.usbharu.hideout.core.domain.model.media.Media? =
query {
return@query Media.select { Media.remoteUrl eq remoteUrl }.singleOrNull()?.toMedia()
}
companion object {
private val logger = LoggerFactory.getLogger(MediaRepositoryImpl::class.java)
}
} }
fun ResultRow.toMedia(): EntityMedia { fun ResultRow.toMedia(): EntityMedia {

View File

@ -2,24 +2,26 @@ package dev.usbharu.hideout.core.infrastructure.exposedrepository
import dev.usbharu.hideout.application.infrastructure.exposed.QueryMapper import dev.usbharu.hideout.application.infrastructure.exposed.QueryMapper
import dev.usbharu.hideout.application.service.id.IdGenerateService import dev.usbharu.hideout.application.service.id.IdGenerateService
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.Post
import dev.usbharu.hideout.core.domain.model.post.PostRepository import dev.usbharu.hideout.core.domain.model.post.PostRepository
import dev.usbharu.hideout.util.singleOr
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Repository import org.springframework.stereotype.Repository
@Repository @Repository
class PostRepositoryImpl( class PostRepositoryImpl(
private val idGenerateService: IdGenerateService, private val idGenerateService: IdGenerateService,
private val postQueryMapper: QueryMapper<Post> private val postQueryMapper: QueryMapper<Post>
) : PostRepository { ) : PostRepository, AbstractRepository() {
override val logger: Logger
get() = Companion.logger
override suspend fun generateId(): Long = idGenerateService.generateId() override suspend fun generateId(): Long = idGenerateService.generateId()
override suspend fun save(post: Post): Boolean { override suspend fun save(post: Post): Post = query {
val singleOrNull = Posts.select { Posts.id eq post.id }.singleOrNull() val singleOrNull = Posts.select { Posts.id eq post.id }.forUpdate().singleOrNull()
if (singleOrNull == null) { if (singleOrNull == null) {
Posts.insert { Posts.insert {
it[id] = post.id it[id] = post.id
@ -61,18 +63,45 @@ class PostRepositoryImpl(
it[deleted] = post.delted it[deleted] = post.delted
} }
} }
return singleOrNull == null return@query post
} }
override suspend fun findById(id: Long): Post = override suspend fun findById(id: Long): Post? = query {
Posts.leftJoin(PostsMedia) return@query Posts.leftJoin(PostsMedia)
.select { Posts.id eq id } .select { Posts.id eq id }
.let(postQueryMapper::map) .let(postQueryMapper::map)
.singleOr { FailedToGetResourcesException("id: $id was not found.", it) } .singleOrNull()
}
override suspend fun delete(id: Long) { override suspend fun findByUrl(url: String): Post? = query {
return@query Posts.leftJoin(PostsMedia)
.select { Posts.url eq url }
.let(postQueryMapper::map)
.singleOrNull()
}
override suspend fun findByApId(apId: String): Post? = query {
return@query Posts.leftJoin(PostsMedia)
.select { Posts.apId eq apId }
.let(postQueryMapper::map)
.singleOrNull()
}
override suspend fun existByApIdWithLock(apId: String): Boolean = query {
return@query Posts.select { Posts.apId eq apId }.forUpdate().empty().not()
}
override suspend fun findByActorId(actorId: Long): List<Post> = query {
return@query Posts.select { Posts.actorId eq actorId }.let(postQueryMapper::map)
}
override suspend fun delete(id: Long): Unit = query {
Posts.deleteWhere { Posts.id eq id } Posts.deleteWhere { Posts.id eq id }
} }
companion object {
private val logger = LoggerFactory.getLogger(PostRepositoryImpl::class.java)
}
} }
object Posts : Table() { object Posts : Table() {

View File

@ -6,17 +6,21 @@ import dev.usbharu.hideout.core.domain.model.reaction.ReactionRepository
import org.jetbrains.exposed.dao.id.LongIdTable import org.jetbrains.exposed.dao.id.LongIdTable
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Repository import org.springframework.stereotype.Repository
@Repository @Repository
class ReactionRepositoryImpl( class ReactionRepositoryImpl(
private val idGenerateService: IdGenerateService private val idGenerateService: IdGenerateService
) : ReactionRepository { ) : ReactionRepository, AbstractRepository() {
override val logger: Logger
get() = Companion.logger
override suspend fun generateId(): Long = idGenerateService.generateId() override suspend fun generateId(): Long = idGenerateService.generateId()
override suspend fun save(reaction: Reaction): Reaction { override suspend fun save(reaction: Reaction): Reaction = query {
if (Reactions.select { Reactions.id eq reaction.id }.empty()) { if (Reactions.select { Reactions.id eq reaction.id }.forUpdate().empty()) {
Reactions.insert { Reactions.insert {
it[id] = reaction.id it[id] = reaction.id
it[emojiId] = reaction.emojiId it[emojiId] = reaction.emojiId
@ -30,30 +34,62 @@ class ReactionRepositoryImpl(
it[actorId] = reaction.actorId it[actorId] = reaction.actorId
} }
} }
return reaction return@query reaction
} }
override suspend fun delete(reaction: Reaction): Reaction { override suspend fun delete(reaction: Reaction): Reaction = query {
Reactions.deleteWhere { Reactions.deleteWhere {
id.eq(reaction.id) id.eq(reaction.id).and(postId.eq(reaction.postId)).and(actorId.eq(reaction.actorId))
.and(postId.eq(reaction.postId))
.and(actorId.eq(reaction.actorId))
.and(emojiId.eq(reaction.emojiId)) .and(emojiId.eq(reaction.emojiId))
} }
return reaction return@query reaction
} }
override suspend fun deleteByPostId(postId: Long): Int { override suspend fun deleteByPostId(postId: Long): Int = query {
return Reactions.deleteWhere { return@query Reactions.deleteWhere {
Reactions.postId eq postId Reactions.postId eq postId
} }
} }
override suspend fun deleteByActorId(actorId: Long): Int { override suspend fun deleteByActorId(actorId: Long): Int = query {
return Reactions.deleteWhere { return@query Reactions.deleteWhere {
Reactions.actorId eq actorId Reactions.actorId eq actorId
} }
} }
override suspend fun findByPostId(postId: Long): List<Reaction> = query {
return@query Reactions.select { Reactions.postId eq postId }.map { it.toReaction() }
}
override suspend fun findByPostIdAndActorIdAndEmojiId(postId: Long, actorId: Long, emojiId: Long): Reaction? =
query {
return@query Reactions.select {
Reactions.postId eq postId and (Reactions.actorId eq actorId).and(
Reactions.emojiId.eq(
emojiId
)
)
}.singleOrNull()?.toReaction()
}
override suspend fun existByPostIdAndActorIdAndEmojiId(postId: Long, actorId: Long, emojiId: Long): Boolean =
query {
return@query Reactions.select {
Reactions.postId
.eq(postId)
.and(Reactions.actorId.eq(actorId))
.and(Reactions.emojiId.eq(emojiId))
}.empty().not()
}
override suspend fun findByPostIdAndActorId(postId: Long, actorId: Long): List<Reaction> = query {
return@query Reactions.select { Reactions.postId eq postId and (Reactions.actorId eq actorId) }
.map { it.toReaction() }
}
companion object {
private val logger = LoggerFactory.getLogger(ReactionRepositoryImpl::class.java)
}
} }
fun ResultRow.toReaction(): Reaction { fun ResultRow.toReaction(): Reaction {
@ -67,10 +103,10 @@ fun ResultRow.toReaction(): Reaction {
object Reactions : LongIdTable("reactions") { object Reactions : LongIdTable("reactions") {
val emojiId: Column<Long> = long("emoji_id") val emojiId: Column<Long> = long("emoji_id")
val postId: Column<Long> = long("post_id") val postId: Column<Long> =
.references(Posts.id, onDelete = ReferenceOption.CASCADE, onUpdate = ReferenceOption.CASCADE) long("post_id").references(Posts.id, onDelete = ReferenceOption.CASCADE, onUpdate = ReferenceOption.CASCADE)
val actorId: Column<Long> = long("actor_id") val actorId: Column<Long> =
.references(Actors.id, onDelete = ReferenceOption.CASCADE, onUpdate = ReferenceOption.CASCADE) long("actor_id").references(Actors.id, onDelete = ReferenceOption.CASCADE, onUpdate = ReferenceOption.CASCADE)
init { init {
uniqueIndex(emojiId, postId, actorId) uniqueIndex(emojiId, postId, actorId)

View File

@ -8,12 +8,17 @@ import org.jetbrains.exposed.sql.deleteWhere
import org.jetbrains.exposed.sql.insert import org.jetbrains.exposed.sql.insert
import org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.update import org.jetbrains.exposed.sql.update
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Repository import org.springframework.stereotype.Repository
@Repository @Repository
class UserDetailRepositoryImpl : UserDetailRepository { class UserDetailRepositoryImpl : UserDetailRepository, AbstractRepository() {
override suspend fun save(userDetail: UserDetail): UserDetail { override val logger: Logger
val singleOrNull = UserDetails.select { UserDetails.actorId eq userDetail.actorId }.singleOrNull() get() = Companion.logger
override suspend fun save(userDetail: UserDetail): UserDetail = query {
val singleOrNull = UserDetails.select { UserDetails.actorId eq userDetail.actorId }.forUpdate().singleOrNull()
if (singleOrNull == null) { if (singleOrNull == null) {
UserDetails.insert { UserDetails.insert {
it[actorId] = userDetail.actorId it[actorId] = userDetail.actorId
@ -26,15 +31,15 @@ class UserDetailRepositoryImpl : UserDetailRepository {
it[autoAcceptFolloweeFollowRequest] = userDetail.autoAcceptFolloweeFollowRequest it[autoAcceptFolloweeFollowRequest] = userDetail.autoAcceptFolloweeFollowRequest
} }
} }
return userDetail return@query userDetail
} }
override suspend fun delete(userDetail: UserDetail) { override suspend fun delete(userDetail: UserDetail): Unit = query {
UserDetails.deleteWhere { UserDetails.actorId eq userDetail.actorId } UserDetails.deleteWhere { UserDetails.actorId eq userDetail.actorId }
} }
override suspend fun findByActorId(actorId: Long): UserDetail? { override suspend fun findByActorId(actorId: Long): UserDetail? = query {
return UserDetails return@query UserDetails
.select { UserDetails.actorId eq actorId } .select { UserDetails.actorId eq actorId }
.singleOrNull() .singleOrNull()
?.let { ?.let {
@ -45,6 +50,10 @@ class UserDetailRepositoryImpl : UserDetailRepository {
) )
} }
} }
companion object {
private val logger = LoggerFactory.getLogger(UserDetailRepositoryImpl::class.java)
}
} }
object UserDetails : LongIdTable("user_details") { object UserDetails : LongIdTable("user_details") {

View File

@ -13,6 +13,7 @@ import java.net.URL
@JsonDeserialize(using = HttpRequestDeserializer::class) @JsonDeserialize(using = HttpRequestDeserializer::class)
@JsonSubTypes @JsonSubTypes
@Suppress("UnnecessaryAbstractClass")
abstract class HttpRequestMixIn abstract class HttpRequestMixIn
class HttpRequestDeserializer : JsonDeserializer<HttpRequest>() { class HttpRequestDeserializer : JsonDeserializer<HttpRequest>() {

View File

@ -7,8 +7,11 @@ import dev.usbharu.hideout.core.service.job.JobQueueWorkerService
import kjob.core.dsl.JobContextWithProps import kjob.core.dsl.JobContextWithProps
import kjob.core.dsl.JobRegisterContext import kjob.core.dsl.JobRegisterContext
import kjob.core.dsl.KJobFunctions import kjob.core.dsl.KJobFunctions
import kjob.core.job.JobExecutionType
import kjob.core.kjob import kjob.core.kjob
import kjob.mongo.Mongo import kjob.mongo.Mongo
import kotlinx.coroutines.CancellationException
import org.slf4j.MDC
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
@ -24,6 +27,8 @@ class KJobMongoJobQueueWorkerService(
nonBlockingMaxJobs = 10 nonBlockingMaxJobs = 10
blockingMaxJobs = 10 blockingMaxJobs = 10
jobExecutionPeriodInSeconds = 1 jobExecutionPeriodInSeconds = 1
maxRetries = 3
defaultJobExecutor = JobExecutionType.NON_BLOCKING
}.start() }.start()
} }
@ -37,8 +42,21 @@ class KJobMongoJobQueueWorkerService(
for (jobProcessor in jobQueueProcessorList) { for (jobProcessor in jobQueueProcessorList) {
kjob.register(jobProcessor.job()) { kjob.register(jobProcessor.job()) {
execute { execute {
val param = it.convertUnsafe(props) @Suppress("TooGenericExceptionCaught")
jobProcessor.process(param) try {
MDC.put("x-job-id", this.jobId)
val param = it.convertUnsafe(props)
jobProcessor.process(param)
} catch (e: CancellationException) {
throw e
} catch (e: Exception) {
logger.warn("FAILED Excute Job. job name: {} job id: {}", it.name, this.jobId, e)
throw e
} finally {
MDC.remove("x-job-id")
}
}.onError {
logger.warn("FAILED Excute Job. job name: {} job id: {}", this.jobName, this.jobId, error)
} }
} }
} }

View File

@ -1,11 +1,15 @@
package dev.usbharu.hideout.core.infrastructure.mongorepository package dev.usbharu.hideout.core.infrastructure.mongorepository
import dev.usbharu.hideout.application.service.id.IdGenerateService import dev.usbharu.hideout.application.service.id.IdGenerateService
import dev.usbharu.hideout.core.domain.exception.resource.DuplicateException
import dev.usbharu.hideout.core.domain.exception.resource.ResourceAccessException
import dev.usbharu.hideout.core.domain.model.timeline.Timeline import dev.usbharu.hideout.core.domain.model.timeline.Timeline
import dev.usbharu.hideout.core.domain.model.timeline.TimelineRepository import dev.usbharu.hideout.core.domain.model.timeline.TimelineRepository
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.dao.DataAccessException
import org.springframework.dao.DuplicateKeyException
import org.springframework.stereotype.Repository import org.springframework.stereotype.Repository
@Repository @Repository
@ -23,8 +27,15 @@ class MongoTimelineRepositoryWrapper(
} }
} }
override suspend fun saveAll(timelines: List<Timeline>): List<Timeline> = override suspend fun saveAll(timelines: List<Timeline>): List<Timeline> {
mongoTimelineRepository.saveAll(timelines) try {
return mongoTimelineRepository.saveAll(timelines)
} catch (e: DuplicateKeyException) {
throw DuplicateException("Timeline duplicate.", e)
} catch (e: DataAccessException) {
throw ResourceAccessException(e)
}
}
override suspend fun findByUserId(id: Long): List<Timeline> { override suspend fun findByUserId(id: Long): List<Timeline> {
return withContext(Dispatchers.IO) { return withContext(Dispatchers.IO) {

View File

@ -1,9 +1,8 @@
package dev.usbharu.hideout.core.infrastructure.springframework.httpsignature package dev.usbharu.hideout.core.infrastructure.springframework.httpsignature
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.HttpSignatureVerifyException import dev.usbharu.hideout.core.domain.exception.HttpSignatureVerifyException
import dev.usbharu.hideout.core.query.ActorQueryService import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.util.RsaUtil import dev.usbharu.hideout.util.RsaUtil
import dev.usbharu.httpsignature.common.HttpMethod import dev.usbharu.httpsignature.common.HttpMethod
import dev.usbharu.httpsignature.common.HttpRequest import dev.usbharu.httpsignature.common.HttpRequest
@ -20,10 +19,10 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken
class HttpSignatureUserDetailsService( class HttpSignatureUserDetailsService(
private val actorQueryService: ActorQueryService,
private val httpSignatureVerifier: HttpSignatureVerifier, private val httpSignatureVerifier: HttpSignatureVerifier,
private val transaction: Transaction, private val transaction: Transaction,
private val httpSignatureHeaderParser: SignatureHeaderParser private val httpSignatureHeaderParser: SignatureHeaderParser,
private val actorRepository: ActorRepository
) : ) :
AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> { AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> {
override fun loadUserDetails(token: PreAuthenticatedAuthenticationToken): UserDetails = runBlocking { override fun loadUserDetails(token: PreAuthenticatedAuthenticationToken): UserDetails = runBlocking {
@ -34,11 +33,7 @@ class HttpSignatureUserDetailsService(
val keyId = token.principal as String val keyId = token.principal as String
val findByKeyId = transaction.transaction { val findByKeyId = transaction.transaction {
try { actorRepository.findByKeyId(keyId) ?: throw UsernameNotFoundException("keyId: $keyId not found.")
actorQueryService.findByKeyId(keyId)
} catch (e: FailedToGetResourcesException) {
throw UsernameNotFoundException("User not found", e)
}
} }
val signature = httpSignatureHeaderParser.parse(credentials.headers) val signature = httpSignatureHeaderParser.parse(credentials.headers)

View File

@ -2,9 +2,9 @@ package dev.usbharu.hideout.core.infrastructure.springframework.oauth2
import dev.usbharu.hideout.application.config.ApplicationConfig import dev.usbharu.hideout.application.config.ApplicationConfig
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.resource.UserNotFoundException
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailRepository import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailRepository
import dev.usbharu.hideout.core.query.ActorQueryService
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.springframework.security.core.userdetails.UserDetails import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.core.userdetails.UserDetailsService import org.springframework.security.core.userdetails.UserDetailsService
@ -13,10 +13,10 @@ import org.springframework.stereotype.Service
@Service @Service
class UserDetailsServiceImpl( class UserDetailsServiceImpl(
private val actorQueryService: ActorQueryService,
private val applicationConfig: ApplicationConfig, private val applicationConfig: ApplicationConfig,
private val userDetailRepository: UserDetailRepository, private val userDetailRepository: UserDetailRepository,
private val transaction: Transaction private val transaction: Transaction,
private val actorRepository: ActorRepository
) : ) :
UserDetailsService { UserDetailsService {
override fun loadUserByUsername(username: String?): UserDetails = runBlocking { override fun loadUserByUsername(username: String?): UserDetails = runBlocking {
@ -24,11 +24,10 @@ class UserDetailsServiceImpl(
throw UsernameNotFoundException("$username not found") throw UsernameNotFoundException("$username not found")
} }
transaction.transaction { transaction.transaction {
val findById = try { val findById =
actorQueryService.findByNameAndDomain(username, applicationConfig.url.host) actorRepository.findByNameAndDomain(username, applicationConfig.url.host)
} catch (e: FailedToGetResourcesException) { ?: throw UserNotFoundException.withNameAndDomain(username, applicationConfig.url.host)
throw UsernameNotFoundException("$username not found", e)
}
val userDetails = userDetailRepository.findByActorId(findById.id) val userDetails = userDetailRepository.findByActorId(findById.id)
?: throw UsernameNotFoundException("${findById.id} not found.") ?: throw UsernameNotFoundException("${findById.id} not found.")
UserDetailsImpl( UserDetailsImpl(

View File

@ -6,5 +6,6 @@ import org.springframework.web.bind.annotation.GetMapping
@Controller @Controller
class AuthController { class AuthController {
@GetMapping("/auth/sign_up") @GetMapping("/auth/sign_up")
@Suppress("FunctionOnlyReturningConstant")
fun signUp(): String = "sign_up" fun signUp(): String = "sign_up"
} }

View File

@ -1,16 +0,0 @@
package dev.usbharu.hideout.core.query
import dev.usbharu.hideout.core.domain.model.actor.Actor
import org.springframework.stereotype.Repository
@Repository
interface ActorQueryService {
suspend fun findAll(limit: Int, offset: Long): List<Actor>
suspend fun findById(id: Long): Actor
suspend fun findByName(name: String): List<Actor>
suspend fun findByNameAndDomain(name: String, domain: String): Actor
suspend fun findByUrl(url: String): Actor
suspend fun findByIds(ids: List<Long>): List<Actor>
suspend fun existByNameAndDomain(name: String, domain: String): Boolean
suspend fun findByKeyId(keyId: String): Actor
}

View File

@ -1,7 +0,0 @@
package dev.usbharu.hideout.core.query
import dev.usbharu.hideout.core.domain.model.deletedActor.DeletedActor
interface DeletedActorQueryService {
suspend fun findByNameAndDomain(name: String, domain: String): DeletedActor
}

View File

@ -1,7 +0,0 @@
package dev.usbharu.hideout.core.query
import dev.usbharu.hideout.core.domain.model.instance.Instance
interface InstanceQueryService {
suspend fun findByUrl(url: String): Instance
}

View File

@ -1,8 +0,0 @@
package dev.usbharu.hideout.core.query
import dev.usbharu.hideout.core.domain.model.media.Media
interface MediaQueryService {
suspend fun findByPostId(postId: Long): List<Media>
suspend fun findByRemoteUrl(remoteUrl: String): Media
}

View File

@ -1,12 +0,0 @@
package dev.usbharu.hideout.core.query
import dev.usbharu.hideout.core.domain.model.post.Post
import org.springframework.stereotype.Repository
@Repository
interface PostQueryService {
suspend fun findById(id: Long): Post
suspend fun findByUrl(url: String): Post
suspend fun findByApId(string: String): Post
suspend fun findByActorId(actorId: Long): List<Post>
}

View File

@ -1,16 +0,0 @@
package dev.usbharu.hideout.core.query
import dev.usbharu.hideout.core.domain.model.reaction.Reaction
import org.springframework.stereotype.Repository
@Repository
interface ReactionQueryService {
suspend fun findByPostId(postId: Long, actorId: Long? = null): List<Reaction>
@Suppress("FunctionMaxLength")
suspend fun findByPostIdAndActorIdAndEmojiId(postId: Long, actorId: Long, emojiId: Long): Reaction
suspend fun reactionAlreadyExist(postId: Long, actorId: Long, emojiId: Long): Boolean
suspend fun findByPostIdAndActorId(postId: Long, actorId: Long): List<Reaction>
}

View File

@ -1,18 +0,0 @@
package dev.usbharu.hideout.core.query
import dev.usbharu.hideout.core.domain.model.relationship.Relationship
interface RelationshipQueryService {
suspend fun findByTargetIdAndFollowing(targetId: Long, following: Boolean): List<Relationship>
@Suppress("LongParameterList", "FunctionMaxLength")
suspend fun findByTargetIdAndFollowRequestAndIgnoreFollowRequest(
maxId: Long?,
sinceId: Long?,
limit: Int,
targetId: Long,
followRequest: Boolean,
ignoreFollowRequest: Boolean
): List<Relationship>
}

View File

@ -1,12 +1,10 @@
package dev.usbharu.hideout.core.service.instance package dev.usbharu.hideout.core.service.instance
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException
import dev.usbharu.hideout.core.domain.model.instance.Instance import dev.usbharu.hideout.core.domain.model.instance.Instance
import dev.usbharu.hideout.core.domain.model.instance.InstanceRepository import dev.usbharu.hideout.core.domain.model.instance.InstanceRepository
import dev.usbharu.hideout.core.domain.model.instance.Nodeinfo import dev.usbharu.hideout.core.domain.model.instance.Nodeinfo
import dev.usbharu.hideout.core.domain.model.instance.Nodeinfo2_0 import dev.usbharu.hideout.core.domain.model.instance.Nodeinfo2_0
import dev.usbharu.hideout.core.query.InstanceQueryService
import dev.usbharu.hideout.core.service.resource.ResourceResolveService import dev.usbharu.hideout.core.service.resource.ResourceResolveService
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Qualifier import org.springframework.beans.factory.annotation.Qualifier
@ -23,20 +21,20 @@ interface InstanceService {
class InstanceServiceImpl( class InstanceServiceImpl(
private val instanceRepository: InstanceRepository, private val instanceRepository: InstanceRepository,
private val resourceResolveService: ResourceResolveService, private val resourceResolveService: ResourceResolveService,
@Qualifier("activitypub") private val objectMapper: ObjectMapper, @Qualifier("activitypub") private val objectMapper: ObjectMapper
private val instanceQueryService: InstanceQueryService
) : InstanceService { ) : InstanceService {
override suspend fun fetchInstance(url: String, sharedInbox: String?): Instance { override suspend fun fetchInstance(url: String, sharedInbox: String?): Instance {
val u = URL(url) val u = URL(url)
val resolveInstanceUrl = u.protocol + "://" + u.host val resolveInstanceUrl = u.protocol + "://" + u.host
try { val instance = instanceRepository.findByUrl(resolveInstanceUrl)
return instanceQueryService.findByUrl(resolveInstanceUrl)
} catch (e: FailedToGetResourcesException) { if (instance != null) {
logger.info("Instance not found. try fetch instance info. url: {}", resolveInstanceUrl) return instance
logger.debug("Failed to get resources. url: {}", resolveInstanceUrl, e)
} }
logger.info("Instance not found. try fetch instance info. url: {}", resolveInstanceUrl)
val nodeinfoJson = resourceResolveService.resolve("$resolveInstanceUrl/.well-known/nodeinfo").bodyAsText() val nodeinfoJson = resourceResolveService.resolve("$resolveInstanceUrl/.well-known/nodeinfo").bodyAsText()
val nodeinfo = objectMapper.readValue(nodeinfoJson, Nodeinfo::class.java) val nodeinfo = objectMapper.readValue(nodeinfoJson, Nodeinfo::class.java)
val nodeinfoPathMap = nodeinfo.links.associate { it.rel to it.href } val nodeinfoPathMap = nodeinfo.links.associate { it.rel to it.href }

View File

@ -74,6 +74,7 @@ class LocalFileSystemMediaDataStore(
val fileSavePathString = fileSavePath.toAbsolutePath().toString() val fileSavePathString = fileSavePath.toAbsolutePath().toString()
logger.info("MEDIA save. path: {}", fileSavePathString) logger.info("MEDIA save. path: {}", fileSavePathString)
@Suppress("TooGenericExceptionCaught")
try { try {
dataSaveRequest.filePath.copyTo(fileSavePath) dataSaveRequest.filePath.copyTo(fileSavePath)
dataSaveRequest.thumbnailPath?.copyTo(thumbnailSavePath) dataSaveRequest.thumbnailPath?.copyTo(thumbnailSavePath)
@ -97,6 +98,7 @@ class LocalFileSystemMediaDataStore(
*/ */
override suspend fun delete(id: String) { override suspend fun delete(id: String) {
logger.info("START Media delete. id: {}", id) logger.info("START Media delete. id: {}", id)
@Suppress("TooGenericExceptionCaught")
try { try {
buildSavePath(savePath, id).deleteIfExists() buildSavePath(savePath, id).deleteIfExists()
buildSavePath(savePath, "thumbnail-$id").deleteIfExists() buildSavePath(savePath, "thumbnail-$id").deleteIfExists()

View File

@ -1,11 +1,9 @@
package dev.usbharu.hideout.core.service.media package dev.usbharu.hideout.core.service.media
import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException
import dev.usbharu.hideout.core.domain.exception.media.MediaSaveException import dev.usbharu.hideout.core.domain.exception.media.MediaSaveException
import dev.usbharu.hideout.core.domain.exception.media.UnsupportedMediaException import dev.usbharu.hideout.core.domain.exception.media.UnsupportedMediaException
import dev.usbharu.hideout.core.domain.model.media.Media import dev.usbharu.hideout.core.domain.model.media.Media
import dev.usbharu.hideout.core.domain.model.media.MediaRepository import dev.usbharu.hideout.core.domain.model.media.MediaRepository
import dev.usbharu.hideout.core.query.MediaQueryService
import dev.usbharu.hideout.core.service.media.converter.MediaProcessService import dev.usbharu.hideout.core.service.media.converter.MediaProcessService
import dev.usbharu.hideout.mastodon.interfaces.api.media.MediaRequest import dev.usbharu.hideout.mastodon.interfaces.api.media.MediaRequest
import dev.usbharu.hideout.util.withDelete import dev.usbharu.hideout.util.withDelete
@ -24,8 +22,7 @@ class MediaServiceImpl(
private val mediaRepository: MediaRepository, private val mediaRepository: MediaRepository,
private val mediaProcessServices: List<MediaProcessService>, private val mediaProcessServices: List<MediaProcessService>,
private val remoteMediaDownloadService: RemoteMediaDownloadService, private val remoteMediaDownloadService: RemoteMediaDownloadService,
private val renameService: MediaFileRenameService, private val renameService: MediaFileRenameService
private val mediaQueryService: MediaQueryService
) : MediaService { ) : MediaService {
@Suppress("LongMethod", "NestedBlockDepth") @Suppress("LongMethod", "NestedBlockDepth")
override suspend fun uploadLocalMedia(mediaRequest: MediaRequest): EntityMedia { override suspend fun uploadLocalMedia(mediaRequest: MediaRequest): EntityMedia {
@ -102,11 +99,10 @@ class MediaServiceImpl(
override suspend fun uploadRemoteMedia(remoteMedia: RemoteMedia): Media { override suspend fun uploadRemoteMedia(remoteMedia: RemoteMedia): Media {
logger.info("MEDIA Remote media. filename:${remoteMedia.name} url:${remoteMedia.url}") logger.info("MEDIA Remote media. filename:${remoteMedia.name} url:${remoteMedia.url}")
try { val findByRemoteUrl = mediaRepository.findByRemoteUrl(remoteMedia.url)
val findByRemoteUrl = mediaQueryService.findByRemoteUrl(remoteMedia.url) if (findByRemoteUrl != null) {
logger.warn("DUPLICATED Remote media is duplicated. url: {}", remoteMedia.url) logger.warn("DUPLICATED Remote media is duplicated. url: {}", remoteMedia.url)
return findByRemoteUrl return findByRemoteUrl
} catch (_: FailedToGetResourcesException) {
} }
remoteMediaDownloadService.download(remoteMedia.url).withDelete().use { remoteMediaDownloadService.download(remoteMedia.url).withDelete().use {
@ -159,7 +155,7 @@ class MediaServiceImpl(
} }
} }
protected fun findMediaProcessor(mimeType: MimeType): MediaProcessService { private fun findMediaProcessor(mimeType: MimeType): MediaProcessService {
try { try {
return mediaProcessServices.first { return mediaProcessServices.first {
try { try {
@ -173,7 +169,7 @@ class MediaServiceImpl(
} }
} }
protected fun generateBlurhash(process: ProcessedMediaPath): String { private fun generateBlurhash(process: ProcessedMediaPath): String {
val path = if (process.thumbnailPath != null && process.thumbnailMimeType != null) { val path = if (process.thumbnailPath != null && process.thumbnailMimeType != null) {
process.thumbnailPath process.thumbnailPath
} else { } else {

View File

@ -2,16 +2,15 @@ package dev.usbharu.hideout.core.service.post
import dev.usbharu.hideout.activitypub.service.activity.create.ApSendCreateService import dev.usbharu.hideout.activitypub.service.activity.create.ApSendCreateService
import dev.usbharu.hideout.core.domain.exception.UserNotFoundException import dev.usbharu.hideout.core.domain.exception.UserNotFoundException
import dev.usbharu.hideout.core.domain.exception.resource.DuplicateException
import dev.usbharu.hideout.core.domain.exception.resource.PostNotFoundException
import dev.usbharu.hideout.core.domain.model.actor.Actor import dev.usbharu.hideout.core.domain.model.actor.Actor
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.domain.model.post.Post import dev.usbharu.hideout.core.domain.model.post.Post
import dev.usbharu.hideout.core.domain.model.post.PostRepository import dev.usbharu.hideout.core.domain.model.post.PostRepository
import dev.usbharu.hideout.core.domain.model.reaction.ReactionRepository import dev.usbharu.hideout.core.domain.model.reaction.ReactionRepository
import dev.usbharu.hideout.core.query.PostQueryService
import dev.usbharu.hideout.core.service.timeline.TimelineService import dev.usbharu.hideout.core.service.timeline.TimelineService
import org.jetbrains.exposed.exceptions.ExposedSQLException
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.dao.DuplicateKeyException
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import java.time.Instant import java.time.Instant
@ -20,7 +19,6 @@ class PostServiceImpl(
private val postRepository: PostRepository, private val postRepository: PostRepository,
private val actorRepository: ActorRepository, private val actorRepository: ActorRepository,
private val timelineService: TimelineService, private val timelineService: TimelineService,
private val postQueryService: PostQueryService,
private val postBuilder: Post.PostBuilder, private val postBuilder: Post.PostBuilder,
private val apSendCreateService: ApSendCreateService, private val apSendCreateService: ApSendCreateService,
private val reactionRepository: ReactionRepository private val reactionRepository: ReactionRepository
@ -69,7 +67,7 @@ class PostServiceImpl(
} }
override suspend fun deleteByActor(actorId: Long) { override suspend fun deleteByActor(actorId: Long) {
postQueryService.findByActorId(actorId).filterNot { it.delted }.forEach { postRepository.save(it.delete()) } postRepository.findByActorId(actorId).filterNot { it.delted }.forEach { postRepository.save(it.delete()) }
val actor = actorRepository.findById(actorId) val actor = actorRepository.findById(actorId)
?: throw IllegalStateException("actor: $actorId was not found.") ?: throw IllegalStateException("actor: $actorId was not found.")
@ -79,18 +77,12 @@ class PostServiceImpl(
private suspend fun internalCreate(post: Post, isLocal: Boolean, actor: Actor): Post { private suspend fun internalCreate(post: Post, isLocal: Boolean, actor: Actor): Post {
return try { return try {
if (postRepository.save(post)) { val save = postRepository.save(post)
try { timelineService.publishTimeline(post, isLocal)
timelineService.publishTimeline(post, isLocal) actorRepository.save(actor.incrementPostsCount())
actorRepository.save(actor.incrementPostsCount()) save
} catch (e: DuplicateKeyException) { } catch (_: DuplicateException) {
logger.trace("Timeline already exists.", e) postRepository.findByApId(post.apId) ?: throw PostNotFoundException.withApId(post.apId)
}
}
post
} catch (e: ExposedSQLException) {
logger.warn("FAILED Save to post. url: ${post.apId}", e)
postQueryService.findByApId(post.apId)
} }
} }

View File

@ -1,10 +1,8 @@
package dev.usbharu.hideout.core.service.reaction package dev.usbharu.hideout.core.service.reaction
import dev.usbharu.hideout.activitypub.service.activity.like.APReactionService import dev.usbharu.hideout.activitypub.service.activity.like.APReactionService
import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException
import dev.usbharu.hideout.core.domain.model.reaction.Reaction import dev.usbharu.hideout.core.domain.model.reaction.Reaction
import dev.usbharu.hideout.core.domain.model.reaction.ReactionRepository import dev.usbharu.hideout.core.domain.model.reaction.ReactionRepository
import dev.usbharu.hideout.core.query.ReactionQueryService
import org.jetbrains.exposed.exceptions.ExposedSQLException import org.jetbrains.exposed.exceptions.ExposedSQLException
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
@ -13,11 +11,10 @@ import org.springframework.stereotype.Service
@Service @Service
class ReactionServiceImpl( class ReactionServiceImpl(
private val reactionRepository: ReactionRepository, private val reactionRepository: ReactionRepository,
private val apReactionService: APReactionService, private val apReactionService: APReactionService
private val reactionQueryService: ReactionQueryService
) : ReactionService { ) : ReactionService {
override suspend fun receiveReaction(name: String, domain: String, actorId: Long, postId: Long) { override suspend fun receiveReaction(name: String, domain: String, actorId: Long, postId: Long) {
if (reactionQueryService.reactionAlreadyExist(postId, actorId, 0).not()) { if (reactionRepository.existByPostIdAndActorIdAndEmojiId(postId, actorId, 0).not()) {
try { try {
reactionRepository.save( reactionRepository.save(
Reaction(reactionRepository.generateId(), 0, postId, actorId) Reaction(reactionRepository.generateId(), 0, postId, actorId)
@ -28,31 +25,37 @@ class ReactionServiceImpl(
} }
override suspend fun receiveRemoveReaction(actorId: Long, postId: Long) { override suspend fun receiveRemoveReaction(actorId: Long, postId: Long) {
val reaction = reactionQueryService.findByPostIdAndActorIdAndEmojiId(postId, actorId, 0) val reaction = reactionRepository.findByPostIdAndActorIdAndEmojiId(postId, actorId, 0)
if (reaction == null) {
LOGGER.warn("FAILED receive Remove Reaction. $actorId $postId")
return
}
reactionRepository.delete(reaction) reactionRepository.delete(reaction)
} }
override suspend fun sendReaction(name: String, actorId: Long, postId: Long) { override suspend fun sendReaction(name: String, actorId: Long, postId: Long) {
try { val findByPostIdAndUserIdAndEmojiId =
val findByPostIdAndUserIdAndEmojiId = reactionRepository.findByPostIdAndActorIdAndEmojiId(postId, actorId, 0)
reactionQueryService.findByPostIdAndActorIdAndEmojiId(postId, actorId, 0)
if (findByPostIdAndUserIdAndEmojiId != null) {
apReactionService.removeReaction(findByPostIdAndUserIdAndEmojiId) apReactionService.removeReaction(findByPostIdAndUserIdAndEmojiId)
reactionRepository.delete(findByPostIdAndUserIdAndEmojiId) reactionRepository.delete(findByPostIdAndUserIdAndEmojiId)
} catch (_: FailedToGetResourcesException) {
} }
val reaction = Reaction(reactionRepository.generateId(), 0, postId, actorId) val reaction = Reaction(reactionRepository.generateId(), 0, postId, actorId)
reactionRepository.save(reaction) reactionRepository.save(reaction)
apReactionService.reaction(reaction) apReactionService.reaction(reaction)
} }
override suspend fun removeReaction(actorId: Long, postId: Long) { override suspend fun removeReaction(actorId: Long, postId: Long) {
try { val findByPostIdAndUserIdAndEmojiId =
val findByPostIdAndUserIdAndEmojiId = reactionRepository.findByPostIdAndActorIdAndEmojiId(postId, actorId, 0)
reactionQueryService.findByPostIdAndActorIdAndEmojiId(postId, actorId, 0) if (findByPostIdAndUserIdAndEmojiId == null) {
reactionRepository.delete(findByPostIdAndUserIdAndEmojiId) LOGGER.warn("FAILED Remove reaction. actorId: $actorId postId: $postId")
apReactionService.removeReaction(findByPostIdAndUserIdAndEmojiId) return
} catch (_: FailedToGetResourcesException) {
} }
reactionRepository.delete(findByPostIdAndUserIdAndEmojiId)
apReactionService.removeReaction(findByPostIdAndUserIdAndEmojiId)
} }
companion object { companion object {

View File

@ -6,12 +6,11 @@ import dev.usbharu.hideout.activitypub.service.activity.follow.APSendFollowServi
import dev.usbharu.hideout.activitypub.service.activity.reject.ApSendRejectService import dev.usbharu.hideout.activitypub.service.activity.reject.ApSendRejectService
import dev.usbharu.hideout.activitypub.service.activity.undo.APSendUndoService import dev.usbharu.hideout.activitypub.service.activity.undo.APSendUndoService
import dev.usbharu.hideout.application.config.ApplicationConfig import dev.usbharu.hideout.application.config.ApplicationConfig
import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException
import dev.usbharu.hideout.core.domain.model.actor.Actor import dev.usbharu.hideout.core.domain.model.actor.Actor
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.domain.model.relationship.Relationship import dev.usbharu.hideout.core.domain.model.relationship.Relationship
import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository
import dev.usbharu.hideout.core.query.ActorQueryService
import dev.usbharu.hideout.core.service.follow.SendFollowDto import dev.usbharu.hideout.core.service.follow.SendFollowDto
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
@ -19,7 +18,6 @@ import org.springframework.stereotype.Service
@Service @Service
class RelationshipServiceImpl( class RelationshipServiceImpl(
private val applicationConfig: ApplicationConfig, private val applicationConfig: ApplicationConfig,
private val actorQueryService: ActorQueryService,
private val relationshipRepository: RelationshipRepository, private val relationshipRepository: RelationshipRepository,
private val apSendFollowService: APSendFollowService, private val apSendFollowService: APSendFollowService,
private val apSendBlockService: APSendBlockService, private val apSendBlockService: APSendBlockService,
@ -78,10 +76,10 @@ class RelationshipServiceImpl(
val remoteUser = isRemoteUser(targetId) val remoteUser = isRemoteUser(targetId)
if (remoteUser != null) { if (remoteUser != null) {
val user = actorQueryService.findById(actorId) val user = actorRepository.findById(actorId) ?: throw UserNotFoundException.withId(actorId)
apSendFollowService.sendFollow(SendFollowDto(user, remoteUser)) apSendFollowService.sendFollow(SendFollowDto(user, remoteUser))
} else { } else {
val target = actorQueryService.findById(targetId) val target = actorRepository.findById(targetId) ?: throw UserNotFoundException.withId(targetId)
if (target.locked.not()) { if (target.locked.not()) {
acceptFollowRequest(targetId, actorId) acceptFollowRequest(targetId, actorId)
} }
@ -93,8 +91,8 @@ class RelationshipServiceImpl(
override suspend fun block(actorId: Long, targetId: Long) { override suspend fun block(actorId: Long, targetId: Long) {
val relationship = relationshipRepository.findByUserIdAndTargetUserId(actorId, targetId) val relationship = relationshipRepository.findByUserIdAndTargetUserId(actorId, targetId)
val user = actorQueryService.findById(actorId) val user = actorRepository.findById(actorId) ?: throw UserNotFoundException.withId(actorId)
val targetActor = actorQueryService.findById(targetId) val targetActor = actorRepository.findById(targetId) ?: throw UserNotFoundException.withId(targetId)
if (relationship?.following == true) { if (relationship?.following == true) {
actorRepository.save(user.decrementFollowing()) actorRepository.save(user.decrementFollowing())
actorRepository.save(targetActor.decrementFollowers()) actorRepository.save(targetActor.decrementFollowers())
@ -174,13 +172,13 @@ class RelationshipServiceImpl(
val copy = relationship.copy(followRequest = false, following = true, blocking = false) val copy = relationship.copy(followRequest = false, following = true, blocking = false)
val user = actorQueryService.findById(actorId) val user = actorRepository.findById(actorId) ?: throw UserNotFoundException.withId(actorId)
actorRepository.save(user.incrementFollowers()) actorRepository.save(user.incrementFollowers())
relationshipRepository.save(copy) relationshipRepository.save(copy)
val remoteActor = actorQueryService.findById(targetId) val remoteActor = actorRepository.findById(targetId) ?: throw UserNotFoundException.withId(targetId)
actorRepository.save(remoteActor.incrementFollowing()) actorRepository.save(remoteActor.incrementFollowing())
@ -209,7 +207,7 @@ class RelationshipServiceImpl(
val remoteUser = isRemoteUser(targetId) val remoteUser = isRemoteUser(targetId)
if (remoteUser != null) { if (remoteUser != null) {
val user = actorQueryService.findById(actorId) val user = actorRepository.findById(actorId) ?: throw UserNotFoundException.withId(actorId)
apSendRejectService.sendRejectFollow(user, remoteUser) apSendRejectService.sendRejectFollow(user, remoteUser)
} }
} }
@ -238,8 +236,8 @@ class RelationshipServiceImpl(
return return
} }
val user = actorQueryService.findById(actorId) val user = actorRepository.findById(actorId) ?: throw UserNotFoundException.withId(actorId)
val targetActor = actorQueryService.findById(targetId) val targetActor = actorRepository.findById(targetId) ?: throw UserNotFoundException.withId(targetId)
if (relationship.following) { if (relationship.following) {
actorRepository.save(user.decrementFollowing()) actorRepository.save(user.decrementFollowing())
@ -280,7 +278,7 @@ class RelationshipServiceImpl(
val remoteUser = isRemoteUser(targetId) val remoteUser = isRemoteUser(targetId)
if (remoteUser != null) { if (remoteUser != null) {
val user = actorQueryService.findById(actorId) val user = actorRepository.findById(actorId) ?: throw UserNotFoundException.withId(actorId)
apSendUndoService.sendUndoBlock(user, remoteUser) apSendUndoService.sendUndoBlock(user, remoteUser)
} }
} }
@ -315,12 +313,8 @@ class RelationshipServiceImpl(
private suspend fun isRemoteUser(userId: Long): Actor? { private suspend fun isRemoteUser(userId: Long): Actor? {
logger.trace("isRemoteUser({})", userId) logger.trace("isRemoteUser({})", userId)
val user = try { val user =
actorQueryService.findById(userId) actorRepository.findById(userId) ?: throw UserNotFoundException.withId(userId)
} catch (e: FailedToGetResourcesException) {
logger.warn("User not found.", e)
throw IllegalStateException("User not found.", e)
}
logger.trace("user info {}", user) logger.trace("user info {}", user)

View File

@ -46,7 +46,7 @@ class InMemoryCacheManager : CacheManager {
override suspend fun getOrWait(key: String): ResolveResponse { override suspend fun getOrWait(key: String): ResolveResponse {
while (valueStore.contains(key).not()) { while (valueStore.contains(key).not()) {
if (cacheKey.containsKey(key).not()) { if (cacheKey.containsKey(key).not()) {
throw IllegalStateException("Invalid cache key.") throw IllegalStateException("Invalid cache key. $key")
} }
delay(1) delay(1)
} }

View File

@ -1,10 +1,11 @@
package dev.usbharu.hideout.core.service.timeline package dev.usbharu.hideout.core.service.timeline
import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.domain.model.post.Post 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.domain.model.timeline.Timeline import dev.usbharu.hideout.core.domain.model.timeline.Timeline
import dev.usbharu.hideout.core.domain.model.timeline.TimelineRepository import dev.usbharu.hideout.core.domain.model.timeline.TimelineRepository
import dev.usbharu.hideout.core.query.ActorQueryService
import dev.usbharu.hideout.core.query.FollowerQueryService import dev.usbharu.hideout.core.query.FollowerQueryService
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
@ -12,14 +13,14 @@ import org.springframework.stereotype.Service
@Service @Service
class TimelineService( class TimelineService(
private val followerQueryService: FollowerQueryService, private val followerQueryService: FollowerQueryService,
private val actorQueryService: ActorQueryService, private val timelineRepository: TimelineRepository,
private val timelineRepository: TimelineRepository private val actorRepository: ActorRepository
) { ) {
suspend fun publishTimeline(post: Post, isLocal: Boolean) { suspend fun publishTimeline(post: Post, isLocal: Boolean) {
val findFollowersById = followerQueryService.findFollowersById(post.actorId).toMutableList() val findFollowersById = followerQueryService.findFollowersById(post.actorId).toMutableList()
if (isLocal) { if (isLocal) {
// 自分自身も含める必要がある // 自分自身も含める必要がある
val user = actorQueryService.findById(post.actorId) val user = actorRepository.findById(post.actorId) ?: throw UserNotFoundException.withId(post.actorId)
findFollowersById.add(user) findFollowersById.add(user)
} }
val timelines = findFollowersById.map { val timelines = findFollowersById.map {

View File

@ -1,6 +1,7 @@
package dev.usbharu.hideout.core.service.user package dev.usbharu.hideout.core.service.user
import dev.usbharu.hideout.core.query.ActorQueryService import dev.usbharu.hideout.application.config.ApplicationConfig
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import java.security.* import java.security.*
@ -8,13 +9,14 @@ import java.util.*
@Service @Service
class UserAuthServiceImpl( class UserAuthServiceImpl(
val actorQueryService: ActorQueryService private val actorRepository: ActorRepository,
private val applicationConfig: ApplicationConfig
) : UserAuthService { ) : UserAuthService {
override fun hash(password: String): String = BCryptPasswordEncoder().encode(password) override fun hash(password: String): String = BCryptPasswordEncoder().encode(password)
override suspend fun usernameAlreadyUse(username: String): Boolean { override suspend fun usernameAlreadyUse(username: String): Boolean {
actorQueryService.findByName(username) actorRepository.findByNameAndDomain(username, applicationConfig.url.host) ?: return false
return true return true
} }

View File

@ -2,7 +2,8 @@ package dev.usbharu.hideout.core.service.user
import dev.usbharu.hideout.activitypub.service.activity.delete.APSendDeleteService import dev.usbharu.hideout.activitypub.service.activity.delete.APSendDeleteService
import dev.usbharu.hideout.application.config.ApplicationConfig import dev.usbharu.hideout.application.config.ApplicationConfig
import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException import dev.usbharu.hideout.core.domain.exception.resource.DuplicateException
import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException
import dev.usbharu.hideout.core.domain.model.actor.Actor import dev.usbharu.hideout.core.domain.model.actor.Actor
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.domain.model.deletedActor.DeletedActor import dev.usbharu.hideout.core.domain.model.deletedActor.DeletedActor
@ -11,14 +12,10 @@ import dev.usbharu.hideout.core.domain.model.reaction.ReactionRepository
import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetail import dev.usbharu.hideout.core.domain.model.userdetails.UserDetail
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailRepository import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailRepository
import dev.usbharu.hideout.core.query.ActorQueryService
import dev.usbharu.hideout.core.query.DeletedActorQueryService
import dev.usbharu.hideout.core.service.instance.InstanceService import dev.usbharu.hideout.core.service.instance.InstanceService
import dev.usbharu.hideout.core.service.post.PostService import dev.usbharu.hideout.core.service.post.PostService
import org.jetbrains.exposed.exceptions.ExposedSQLException
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.time.Instant import java.time.Instant
@Service @Service
@ -26,13 +23,11 @@ import java.time.Instant
class UserServiceImpl( class UserServiceImpl(
private val actorRepository: ActorRepository, private val actorRepository: ActorRepository,
private val userAuthService: UserAuthService, private val userAuthService: UserAuthService,
private val actorQueryService: ActorQueryService,
private val actorBuilder: Actor.UserBuilder, private val actorBuilder: Actor.UserBuilder,
private val applicationConfig: ApplicationConfig, private val applicationConfig: ApplicationConfig,
private val instanceService: InstanceService, private val instanceService: InstanceService,
private val userDetailRepository: UserDetailRepository, private val userDetailRepository: UserDetailRepository,
private val deletedActorRepository: DeletedActorRepository, private val deletedActorRepository: DeletedActorRepository,
private val deletedActorQueryService: DeletedActorQueryService,
private val reactionRepository: ReactionRepository, private val reactionRepository: ReactionRepository,
private val relationshipRepository: RelationshipRepository, private val relationshipRepository: RelationshipRepository,
private val postService: PostService, private val postService: PostService,
@ -42,7 +37,7 @@ class UserServiceImpl(
UserService { UserService {
override suspend fun usernameAlreadyUse(username: String): Boolean { override suspend fun usernameAlreadyUse(username: String): Boolean {
val findByNameAndDomain = actorQueryService.findByNameAndDomain(username, applicationConfig.url.host) val findByNameAndDomain = actorRepository.findByNameAndDomain(username, applicationConfig.url.host)
return findByNameAndDomain != null return findByNameAndDomain != null
} }
@ -73,15 +68,14 @@ class UserServiceImpl(
return save return save
} }
@Transactional
override suspend fun createRemoteUser(user: RemoteUserCreateDto): Actor { override suspend fun createRemoteUser(user: RemoteUserCreateDto): Actor {
logger.info("START Create New remote user. name: {} url: {}", user.name, user.url) logger.info("START Create New remote user. name: {} url: {}", user.name, user.url)
try { val deletedActor = deletedActorRepository.findByNameAndDomain(user.name, user.domain)
deletedActorQueryService.findByNameAndDomain(user.name, user.domain)
if (deletedActor != null) {
logger.warn("FAILED Deleted actor. user: ${user.name} domain: ${user.domain}") logger.warn("FAILED Deleted actor. user: ${user.name} domain: ${user.domain}")
throw IllegalStateException("Cannot create Deleted actor.") throw IllegalStateException("Cannot create Deleted actor.")
} catch (_: FailedToGetResourcesException) {
} }
@Suppress("TooGenericExceptionCaught") @Suppress("TooGenericExceptionCaught")
@ -114,9 +108,8 @@ class UserServiceImpl(
val save = actorRepository.save(userEntity) val save = actorRepository.save(userEntity)
logger.warn("SUCCESS Create New remote user. id: {} name: {} url: {}", userEntity.id, user.name, user.url) logger.warn("SUCCESS Create New remote user. id: {} name: {} url: {}", userEntity.id, user.name, user.url)
save save
} catch (_: ExposedSQLException) { } catch (_: DuplicateException) {
logger.warn("FAILED User already exists. name: {} url: {}", user.name, user.url) actorRepository.findByUrl(user.url)!!
actorQueryService.findByUrl(user.url)
} }
} }
@ -142,7 +135,7 @@ class UserServiceImpl(
} }
override suspend fun deleteRemoteActor(actorId: Long) { override suspend fun deleteRemoteActor(actorId: Long) {
val actor = actorQueryService.findById(actorId) val actor = actorRepository.findByIdWithLock(actorId) ?: throw UserNotFoundException.withId(actorId)
val deletedActor = DeletedActor( val deletedActor = DeletedActor(
actor.id, actor.id,
actor.name, actor.name,
@ -161,7 +154,7 @@ class UserServiceImpl(
} }
override suspend fun deleteLocalUser(userId: Long) { override suspend fun deleteLocalUser(userId: Long) {
val actor = actorQueryService.findById(userId) val actor = actorRepository.findByIdWithLock(userId) ?: throw UserNotFoundException.withId(userId)
apSendDeleteService.sendDeleteActor(actor) apSendDeleteService.sendDeleteActor(actor)
val deletedActor = DeletedActor( val deletedActor = DeletedActor(
actor.id, actor.id,

View File

@ -1,11 +1,9 @@
package dev.usbharu.hideout.mastodon.infrastructure.exposedquery package dev.usbharu.hideout.mastodon.infrastructure.exposedquery
import dev.usbharu.hideout.application.config.ApplicationConfig import dev.usbharu.hideout.application.config.ApplicationConfig
import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException
import dev.usbharu.hideout.core.infrastructure.exposedrepository.Actors import dev.usbharu.hideout.core.infrastructure.exposedrepository.Actors
import dev.usbharu.hideout.domain.mastodon.model.generated.Account import dev.usbharu.hideout.domain.mastodon.model.generated.Account
import dev.usbharu.hideout.mastodon.query.AccountQueryService import dev.usbharu.hideout.mastodon.query.AccountQueryService
import dev.usbharu.hideout.util.singleOr
import org.jetbrains.exposed.sql.ResultRow import org.jetbrains.exposed.sql.ResultRow
import org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.select
import org.springframework.stereotype.Repository import org.springframework.stereotype.Repository
@ -13,12 +11,12 @@ import java.time.Instant
@Repository @Repository
class AccountQueryServiceImpl(private val applicationConfig: ApplicationConfig) : AccountQueryService { class AccountQueryServiceImpl(private val applicationConfig: ApplicationConfig) : AccountQueryService {
override suspend fun findById(accountId: Long): Account { override suspend fun findById(accountId: Long): Account? {
val query = Actors.select { Actors.id eq accountId } val query = Actors.select { Actors.id eq accountId }
return query return query
.singleOr { FailedToGetResourcesException("accountId: $accountId wad not exist or duplicate", it) } .singleOrNull()
.let { toAccount(it) } ?.let { toAccount(it) }
} }
override suspend fun findByIds(accountIds: List<Long>): List<Account> { override suspend fun findByIds(accountIds: List<Long>): List<Account> {

View File

@ -3,6 +3,6 @@ package dev.usbharu.hideout.mastodon.query
import dev.usbharu.hideout.domain.mastodon.model.generated.Account import dev.usbharu.hideout.domain.mastodon.model.generated.Account
interface AccountQueryService { interface AccountQueryService {
suspend fun findById(accountId: Long): Account suspend fun findById(accountId: Long): Account?
suspend fun findByIds(accountIds: List<Long>): List<Account> suspend fun findByIds(accountIds: List<Long>): List<Account>
} }

View File

@ -2,7 +2,6 @@ package dev.usbharu.hideout.mastodon.service.account
import dev.usbharu.hideout.application.external.Transaction import dev.usbharu.hideout.application.external.Transaction
import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository
import dev.usbharu.hideout.core.query.RelationshipQueryService
import dev.usbharu.hideout.core.service.media.MediaService import dev.usbharu.hideout.core.service.media.MediaService
import dev.usbharu.hideout.core.service.relationship.RelationshipService import dev.usbharu.hideout.core.service.relationship.RelationshipService
import dev.usbharu.hideout.core.service.user.UpdateUserDto import dev.usbharu.hideout.core.service.user.UpdateUserDto
@ -71,8 +70,7 @@ class AccountApiServiceImpl(
private val statusQueryService: StatusQueryService, private val statusQueryService: StatusQueryService,
private val relationshipService: RelationshipService, private val relationshipService: RelationshipService,
private val relationshipRepository: RelationshipRepository, private val relationshipRepository: RelationshipRepository,
private val mediaService: MediaService, private val mediaService: MediaService
private val relationshipQueryService: RelationshipQueryService
) : ) :
AccountApiService { AccountApiService {
override suspend fun accountsStatuses( override suspend fun accountsStatuses(
@ -221,7 +219,7 @@ class AccountApiServiceImpl(
limit: Int, limit: Int,
withIgnore: Boolean withIgnore: Boolean
): List<Account> = transaction.transaction { ): List<Account> = transaction.transaction {
val actorIdList = relationshipQueryService val actorIdList = relationshipRepository
.findByTargetIdAndFollowRequestAndIgnoreFollowRequest( .findByTargetIdAndFollowRequestAndIgnoreFollowRequest(
maxId = maxId, maxId = maxId,
sinceId = sinceId, sinceId = sinceId,

View File

@ -14,7 +14,8 @@ interface AccountService {
class AccountServiceImpl( class AccountServiceImpl(
private val accountQueryService: AccountQueryService private val accountQueryService: AccountQueryService
) : AccountService { ) : AccountService {
override suspend fun findById(id: Long): Account = accountQueryService.findById(id) override suspend fun findById(id: Long): Account =
accountQueryService.findById(id) ?: throw IllegalArgumentException("Account $id not found.")
override suspend fun findByIds(ids: List<Long>): List<Account> = accountQueryService.findByIds(ids) override suspend fun findByIds(ids: List<Long>): List<Account> = accountQueryService.findByIds(ids)
} }

View File

@ -1,11 +1,10 @@
package dev.usbharu.hideout.mastodon.service.status 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.model.actor.ActorRepository
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.media.toMediaAttachments import dev.usbharu.hideout.core.domain.model.media.toMediaAttachments
import dev.usbharu.hideout.core.query.ActorQueryService import dev.usbharu.hideout.core.domain.model.post.PostRepository
import dev.usbharu.hideout.core.query.PostQueryService
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.Status import dev.usbharu.hideout.domain.mastodon.model.generated.Status
@ -29,10 +28,10 @@ interface StatusesApiService {
class StatsesApiServiceImpl( class StatsesApiServiceImpl(
private val postService: PostService, private val postService: PostService,
private val accountService: AccountService, private val accountService: AccountService,
private val postQueryService: PostQueryService,
private val actorQueryService: ActorQueryService,
private val mediaRepository: MediaRepository, private val mediaRepository: MediaRepository,
private val transaction: Transaction private val transaction: Transaction,
private val actorRepository: ActorRepository,
private val postRepository: PostRepository
) : ) :
StatusesApiService { StatusesApiService {
override suspend fun postStatus( override suspend fun postStatus(
@ -54,17 +53,18 @@ class StatsesApiServiceImpl(
val account = accountService.findById(userId) val account = accountService.findById(userId)
val replyUser = if (post.replyId != null) { val replyUser = if (post.replyId != null) {
try { val findById = postRepository.findById(post.replyId)
actorQueryService.findById(postQueryService.findById(post.replyId).actorId).id if (findById == null) {
} catch (ignore: FailedToGetResourcesException) {
null null
} else {
actorRepository.findById(findById.actorId)?.id
} }
} else { } else {
null null
} }
// TODO: n+1解消 // TODO: n+1解消
val mediaAttachment = post.mediaIds.map { mediaId -> val mediaAttachment = post.mediaIds.mapNotNull { mediaId ->
mediaRepository.findById(mediaId) mediaRepository.findById(mediaId)
}.map { }.map {
it.toMediaAttachments() it.toMediaAttachments()

View File

@ -1,6 +1,6 @@
hideout: hideout:
url: "https://test-hideout.usbharu.dev" url: "https://test-hideout.usbharu.dev"
use-mongodb: false use-mongodb: true
security: security:
jwt: jwt:
generate: true generate: true
@ -22,14 +22,14 @@ spring:
url: "jdbc:postgresql:hideout2" url: "jdbc:postgresql:hideout2"
username: "postgres" username: "postgres"
password: "" password: ""
# data: data:
# mongodb: mongodb:
# auto-index-creation: true auto-index-creation: true
# host: localhost host: localhost
# port: 27017 port: 27017
# database: hideout database: hideout
# username: hideoutuser # username: hideoutuser
# password: hideoutpass # password: hideoutpass
servlet: servlet:
multipart: multipart:
max-file-size: 40MB max-file-size: 40MB

View File

@ -1,13 +1,28 @@
<configuration> <configuration>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logFile.log</file>
<encoder>
<charset>UTF-8</charset>
<pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%X{x-request-id}] %logger{36} - %msg%n</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- daily rollover -->
<fileNamePattern>logFile.%d{yyyy-MM-dd_HH}.log</fileNamePattern>
<!-- keep 30 days worth of history -->
<maxHistory>30</maxHistory>
</rollingPolicy>
</appender>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder> <encoder>
<pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%X{x-request-id}] [%X{x-job-id}] %logger{36} - <pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%X{x-request-id},%X{x-job-id}] %logger{36} -
%msg%n %msg%n
</pattern> </pattern>
</encoder> </encoder>
</appender> </appender>
<root level="DEBUG"> <root level="DEBUG">
<appender-ref ref="STDOUT"/> <appender-ref ref="STDOUT"/>
<appender-ref ref="FILE"/>
</root> </root>
<logger name="org.eclipse.jetty" level="INFO"/> <logger name="org.eclipse.jetty" level="INFO"/>
<logger name="io.netty" level="INFO"/> <logger name="io.netty" level="INFO"/>

Some files were not shown because too many files have changed in this diff Show More