refactor: FailedToGetResourceExceptionを使わないように

This commit is contained in:
usbharu 2023-12-19 17:52:08 +09:00
parent aa2741d614
commit 3571ab22e3
36 changed files with 185 additions and 210 deletions

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

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.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.ResponseEntity
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> {
val person = try {
apUserService.getPersonByName(username)
} catch (_: FailedToGetResourcesException) {
} catch (_: UserNotFoundException) {
return ResponseEntity.notFound().build()
}
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.service.webfinger.WebFingerApiService
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 kotlinx.coroutines.runBlocking
import org.slf4j.LoggerFactory
@ -29,7 +29,7 @@ class WebFingerController(
}
val user = try {
webFingerApiService.findByNameAndDomain(acct.username, acct.domain ?: applicationConfig.url.host)
} catch (_: FailedToGetResourcesException) {
} catch (_: UserNotFoundException) {
return@runBlocking ResponseEntity.notFound().build()
}
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
interface NoteQueryService {
suspend fun findById(id: Long): Pair<Note, Post>
suspend fun findByApid(apId: String): Pair<Note, Post>
suspend fun findById(id: Long): Pair<Note, Post>?
suspend fun findByApid(apId: String): Pair<Note, Post>?
}

View File

@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper
import dev.usbharu.hideout.activitypub.domain.model.Create
import dev.usbharu.hideout.activitypub.query.NoteQueryService
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
@ -31,7 +32,7 @@ class ApSendCreateServiceImpl(
logger.debug("DELIVER Deliver Note Create ${followers.size} accounts.")
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(
name = "Create 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.ActivityType
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.PostQueryService
import dev.usbharu.hideout.core.domain.model.post.PostRepository
import dev.usbharu.hideout.core.service.post.PostService
import dev.usbharu.hideout.core.service.user.UserService
import org.springframework.stereotype.Service
@ -18,10 +17,10 @@ import org.springframework.stereotype.Service
@Service
class APDeleteProcessor(
transaction: Transaction,
private val postQueryService: PostQueryService,
private val userService: UserService,
private val postService: PostService,
private val actorRepository: ActorRepository
private val actorRepository: ActorRepository,
private val postRepository: PostRepository
) :
AbstractActivityPubProcessor<Delete>(transaction) {
override suspend fun internalProcess(activity: ActivityPubProcessContext<Delete>) {
@ -38,12 +37,13 @@ class APDeleteProcessor(
actor?.let { userService.deleteRemoteActor(it.id) }
try {
val post = postQueryService.findByApId(deleteId)
postService.deleteRemote(post)
} catch (e: FailedToGetResourcesException) {
logger.warn("FAILED delete id: {} is not found.", deleteId, e)
val post = postRepository.findByApId(deleteId)
if (post == null) {
logger.warn("FAILED Delete id: {} is not found.", deleteId)
return
}
postService.deleteRemote(post)
}
override fun isSupported(activityType: ActivityType): Boolean = activityType == ActivityType.Delete

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.user.APUserService
import dev.usbharu.hideout.application.external.Transaction
import dev.usbharu.hideout.core.query.PostQueryService
import dev.usbharu.hideout.core.service.reaction.ReactionService
import org.springframework.stereotype.Service
@ -17,7 +16,6 @@ class APLikeProcessor(
transaction: Transaction,
private val apUserService: APUserService,
private val apNoteService: APNoteService,
private val postQueryService: PostQueryService,
private val reactionService: ReactionService
) :
AbstractActivityPubProcessor<Like>(transaction) {
@ -30,23 +28,21 @@ class APLikeProcessor(
val personWithEntity = apUserService.fetchPersonWithEntity(actor)
try {
apNoteService.fetchNote(target)
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) {
logger.debug("FAILED failed to get {}", target)
logger.trace("", e)
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

View File

@ -1,6 +1,7 @@
package dev.usbharu.hideout.activitypub.service.activity.like
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.reaction.Reaction
@ -29,7 +30,7 @@ class APReactionServiceImpl(
val followers = followerQueryService.findFollowersById(like.actorId)
val user = actorRepository.findById(like.actorId) ?: throw UserNotFoundException.withId(like.actorId)
val post =
postQueryService.findById(like.postId)
postQueryService.findById(like.postId) ?: throw PostNotFoundException.withId(like.postId)
followers.forEach { follower ->
jobQueueParentService.schedule(DeliverReactionJob) {
props[DeliverReactionJob.actor] = user.url
@ -45,7 +46,7 @@ class APReactionServiceImpl(
val followers = followerQueryService.findFollowersById(like.actorId)
val user = actorRepository.findById(like.actorId) ?: throw UserNotFoundException.withId(like.actorId)
val post =
postQueryService.findById(like.postId)
postQueryService.findById(like.postId) ?: throw PostNotFoundException.withId(like.postId)
followers.forEach { follower ->
jobQueueParentService.schedule(DeliverRemoveReactionJob) {
props[DeliverRemoveReactionJob.actor] = user.url

View File

@ -7,6 +7,7 @@ import dev.usbharu.hideout.activitypub.service.common.ActivityPubProcessContext
import dev.usbharu.hideout.activitypub.service.common.ActivityType
import dev.usbharu.hideout.activitypub.service.objects.user.APUserService
import dev.usbharu.hideout.application.external.Transaction
import dev.usbharu.hideout.core.domain.exception.resource.PostNotFoundException
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
@ -79,7 +80,8 @@ class APUndoProcessor(
"Like" -> {
val like = undo.apObject as Like
val post = postQueryService.findByUrl(like.apObject)
val post =
postQueryService.findByUrl(like.apObject) ?: throw PostNotFoundException.withUrl(like.apObject)
val signer =
actorRepository.findById(post.actorId) ?: throw LocalUserNotFoundException.withId(post.actorId)

View File

@ -6,7 +6,6 @@ import dev.usbharu.hideout.activitypub.query.NoteQueryService
import dev.usbharu.hideout.activitypub.service.common.APResourceResolveService
import dev.usbharu.hideout.activitypub.service.common.resolve
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.PostRepository
import dev.usbharu.hideout.core.domain.model.post.Visibility
@ -20,8 +19,9 @@ import org.springframework.stereotype.Service
import java.time.Instant
interface APNoteService {
suspend fun fetchNote(url: String, targetActor: String? = null): Note
suspend fun fetchNote(url: String, targetActor: String? = null): Note = fetchNoteWithEntity(url, targetActor).first
suspend fun fetchNote(note: Note, targetActor: String? = null): Note
suspend fun fetchNoteWithEntity(url: String, targetActor: String? = null): Pair<Note, Post>
}
@Service
@ -40,13 +40,14 @@ class APNoteServiceImpl(
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)
try {
val post = noteQueryService.findByApid(url)
val post = noteQueryService.findByApid(url)
if (post != null) {
logger.debug("SUCCESS Found in local url: {}", url)
return post.first
} catch (_: FailedToGetResourcesException) {
return post
}
logger.info("AP GET url: {}", url)
@ -54,9 +55,7 @@ class APNoteServiceImpl(
apResourceResolveService.resolve<Note>(url, null as Long?)
} catch (e: ClientRequestException) {
logger.warn(
"FAILED Failed to retrieve ActivityPub resource. HTTP Status Code: {} url: {}",
e.response.status,
url
"FAILED Failed to retrieve ActivityPub resource. HTTP Status Code: {} url: {}", e.response.status, url
)
throw FailedToGetActivityPubResourceException("Could not retrieve $url.", e)
}
@ -66,45 +65,33 @@ class APNoteServiceImpl(
}
private suspend fun saveIfMissing(
note: Note,
targetActor: String?,
url: String
): Note {
requireNotNull(note.id) { "id is null" }
return try {
noteQueryService.findByApid(note.id).first
} catch (e: FailedToGetResourcesException) {
saveNote(note, targetActor, url)
}
note: Note, targetActor: String?, url: String
): Pair<Note, Post> {
return noteQueryService.findByApid(note.id) ?: saveNote(note, targetActor, url)
}
private suspend fun saveNote(note: Note, targetActor: String?, url: String): Note {
private suspend fun saveNote(note: Note, targetActor: String?, url: String): Pair<Note, Post> {
val person = apUserService.fetchPersonWithEntity(
note.attributedTo,
targetActor
note.attributedTo, targetActor
)
if (postRepository.existByApIdWithLock(note.id)) {
return note
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)
val visibility =
if (note.to.contains(public)) {
Visibility.PUBLIC
} else if (note.to.contains(person.second.followers) && note.cc.contains(public)) {
Visibility.UNLISTED
} else if (note.to.contains(person.second.followers)) {
Visibility.FOLLOWERS
} else {
Visibility.DIRECT
}
val visibility = if (note.to.contains(public)) {
Visibility.PUBLIC
} else if (note.to.contains(person.second.followers) && note.cc.contains(public)) {
Visibility.UNLISTED
} else if (note.to.contains(person.second.followers)) {
Visibility.FOLLOWERS
} else {
Visibility.DIRECT
}
logger.debug("VISIBILITY is {} url: {}", visibility.name, note.id)
@ -113,22 +100,15 @@ class APNoteServiceImpl(
postQueryService.findByUrl(it)
}
val mediaList = note.attachment
.filter { it.url != null }
.map {
mediaService.uploadRemoteMedia(
RemoteMedia(
it.name,
it.url,
it.mediaType,
description = it.name
)
val mediaList = note.attachment.map {
mediaService.uploadRemoteMedia(
RemoteMedia(
it.name, it.url, it.mediaType, description = it.name
)
}
.map { it.id }
)
}.map { it.id }
// TODO: リモートのメディア処理を追加
postService.createRemote(
val createRemote = postService.createRemote(
postBuilder.of(
id = postRepository.generateId(),
actorId = person.second.id,
@ -142,11 +122,11 @@ class APNoteServiceImpl(
mediaIds = mediaList
)
)
return note
return note to createRemote
}
override suspend fun fetchNote(note: Note, targetActor: String?): Note =
saveIfMissing(note, targetActor, note.id)
saveIfMissing(note, targetActor, note.id).first
companion object {
const val public: String = "https://www.w3.org/ns/activitystreams#Public"

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

View File

@ -19,5 +19,9 @@ class PostNotFoundException : NotFoundException {
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

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

View File

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

View File

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

View File

@ -1,23 +1,19 @@
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 {
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()
.singleOrNull()
?.toDeletedActor()
}
}

View File

@ -1,16 +1,14 @@
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()
override suspend fun findByUrl(url: String): InstanceEntity? = Instance.select { Instance.url eq url }
.singleOrNull()?.toInstance()
}

View File

@ -1,11 +1,9 @@
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
@ -23,9 +21,9 @@ class MediaQueryServiceImpl : MediaQueryService {
.map { it.toMedia() }
}
override suspend fun findByRemoteUrl(remoteUrl: String): MediaEntity {
override suspend fun findByRemoteUrl(remoteUrl: String): MediaEntity? {
return Media.select { Media.remoteUrl eq remoteUrl }.forUpdate()
.singleOr { FailedToGetResourcesException("remoteUrl: $remoteUrl is duplicate or not exist.", it) }
.toMedia()
.singleOrNull()
?.toMedia()
}
}

View File

@ -2,12 +2,10 @@ 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
@ -16,23 +14,23 @@ class PostQueryServiceImpl(
private val postResultRowMapper: ResultRowMapper<Post>,
private val postQueryMapper: QueryMapper<Post>
) : PostQueryService {
override suspend fun findById(id: Long): Post =
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)
.singleOrNull()
?.let(postResultRowMapper::map)
override suspend fun findByUrl(url: String): Post =
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) }
.singleOrNull()
override suspend fun findByApId(string: String): Post =
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) }
.singleOrNull()
override suspend fun findByActorId(actorId: Long): List<Post> =
Posts.leftJoin(PostsMedia).select { Posts.actorId eq actorId }.let(postQueryMapper::map)

View File

@ -1,11 +1,9 @@
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
@ -19,20 +17,15 @@ class ReactionQueryServiceImpl : ReactionQueryService {
}
@Suppress("FunctionMaxLength")
override suspend fun findByPostIdAndActorIdAndEmojiId(postId: Long, actorId: Long, emojiId: Long): Reaction {
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()
.singleOrNull()
?.toReaction()
}
override suspend fun reactionAlreadyExist(postId: Long, actorId: Long, emojiId: Long): Boolean {

View File

@ -1,18 +1,18 @@
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.DeletedActorRepository
import dev.usbharu.hideout.util.singleOr
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.javatime.timestamp
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Repository
@Repository
class DeletedActorRepositoryImpl : DeletedActorRepository {
override suspend fun save(deletedActor: DeletedActor): DeletedActor {
val singleOrNull = DeletedActors.select { DeletedActors.id eq deletedActor.id }.singleOrNull()
class DeletedActorRepositoryImpl : DeletedActorRepository, AbstractRepository() {
override suspend fun save(deletedActor: DeletedActor): DeletedActor = query {
val singleOrNull = DeletedActors.select { DeletedActors.id eq deletedActor.id }.forUpdate().singleOrNull()
if (singleOrNull == null) {
DeletedActors.insert {
@ -30,18 +30,24 @@ class DeletedActorRepositoryImpl : DeletedActorRepository {
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 }
}
override suspend fun findById(id: Long): DeletedActor {
val singleOr = DeletedActors.select { DeletedActors.id eq id }
.singleOr { FailedToGetResourcesException("id: $id was not exist or duplicate", it) }
override suspend fun findById(id: Long): DeletedActor? = query {
return@query DeletedActors.select { DeletedActors.id eq id }
.singleOrNull()
?.let { it.toDeletedActor() }
}
return deletedActor(singleOr)
override val logger: Logger
get() = Companion.logger
companion object {
private val logger = LoggerFactory.getLogger(DeletedActorRepositoryImpl::class.java)
}
}

View File

@ -1,9 +1,7 @@
package dev.usbharu.hideout.core.infrastructure.exposedrepository
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.util.singleOr
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.javatime.timestamp
@ -48,9 +46,9 @@ class InstanceRepositoryImpl(private val idGenerateService: IdGenerateService) :
return instance
}
override suspend fun findById(id: Long): InstanceEntity {
override suspend fun findById(id: Long): InstanceEntity? {
return Instance.select { Instance.id eq id }
.singleOr { FailedToGetResourcesException("id: $id doesn't exist.") }.toInstance()
.singleOrNull()?.toInstance()
}
override suspend fun delete(instance: InstanceEntity) {

View File

@ -1,12 +1,10 @@
package dev.usbharu.hideout.core.infrastructure.exposedrepository
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.infrastructure.exposedrepository.Media.mimeType
import dev.usbharu.hideout.core.service.media.FileType
import dev.usbharu.hideout.core.service.media.MimeType
import dev.usbharu.hideout.util.singleOr
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.springframework.stereotype.Repository
@ -47,14 +45,13 @@ class MediaRepositoryImpl(private val idGenerateService: IdGenerateService) : Me
return media
}
override suspend fun findById(id: Long): EntityMedia {
override suspend fun findById(id: Long): EntityMedia? {
return Media
.select {
Media.id eq id
}
.singleOr {
FailedToGetResourcesException("id: $id was not found.")
}.toMedia()
.singleOrNull()
?.toMedia()
}
override suspend fun delete(id: Long) {

View File

@ -3,5 +3,5 @@ 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
suspend fun findByNameAndDomain(name: String, domain: String): DeletedActor?
}

View File

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

View File

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

View File

@ -5,8 +5,8 @@ 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 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

@ -8,7 +8,7 @@ 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 findByPostIdAndActorIdAndEmojiId(postId: Long, actorId: Long, emojiId: Long): Reaction?
suspend fun reactionAlreadyExist(postId: Long, actorId: Long, emojiId: Long): Boolean

View File

@ -1,7 +1,6 @@
package dev.usbharu.hideout.core.service.instance
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.InstanceRepository
import dev.usbharu.hideout.core.domain.model.instance.Nodeinfo
@ -30,13 +29,14 @@ class InstanceServiceImpl(
val u = URL(url)
val resolveInstanceUrl = u.protocol + "://" + u.host
try {
return instanceQueryService.findByUrl(resolveInstanceUrl)
} catch (e: FailedToGetResourcesException) {
logger.info("Instance not found. try fetch instance info. url: {}", resolveInstanceUrl)
logger.debug("Failed to get resources. url: {}", resolveInstanceUrl, e)
val instance = instanceQueryService.findByUrl(resolveInstanceUrl)
if (instance != null) {
return instance
}
logger.info("Instance not found. try fetch instance info. url: {}", resolveInstanceUrl)
val nodeinfoJson = resourceResolveService.resolve("$resolveInstanceUrl/.well-known/nodeinfo").bodyAsText()
val nodeinfo = objectMapper.readValue(nodeinfoJson, Nodeinfo::class.java)
val nodeinfoPathMap = nodeinfo.links.associate { it.rel to it.href }

View File

@ -1,6 +1,5 @@
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.UnsupportedMediaException
import dev.usbharu.hideout.core.domain.model.media.Media
@ -102,11 +101,11 @@ class MediaServiceImpl(
override suspend fun uploadRemoteMedia(remoteMedia: RemoteMedia): Media {
logger.info("MEDIA Remote media. filename:${remoteMedia.name} url:${remoteMedia.url}")
try {
val findByRemoteUrl = mediaQueryService.findByRemoteUrl(remoteMedia.url)
val findByRemoteUrl = mediaQueryService.findByRemoteUrl(remoteMedia.url)
if (findByRemoteUrl != null) {
logger.warn("DUPLICATED Remote media is duplicated. url: {}", remoteMedia.url)
return findByRemoteUrl
} catch (_: FailedToGetResourcesException) {
}
remoteMediaDownloadService.download(remoteMedia.url).withDelete().use {

View File

@ -1,7 +1,6 @@
package dev.usbharu.hideout.core.service.reaction
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.ReactionRepository
import dev.usbharu.hideout.core.query.ReactionQueryService
@ -29,30 +28,38 @@ class ReactionServiceImpl(
override suspend fun receiveRemoveReaction(actorId: Long, postId: Long) {
val reaction = reactionQueryService.findByPostIdAndActorIdAndEmojiId(postId, actorId, 0)
if (reaction == null) {
LOGGER.warn("FAILED receive Remove Reaction. $actorId $postId")
return
}
reactionRepository.delete(reaction)
}
override suspend fun sendReaction(name: String, actorId: Long, postId: Long) {
try {
val findByPostIdAndUserIdAndEmojiId =
reactionQueryService.findByPostIdAndActorIdAndEmojiId(postId, actorId, 0)
apReactionService.removeReaction(findByPostIdAndUserIdAndEmojiId)
reactionRepository.delete(findByPostIdAndUserIdAndEmojiId)
} catch (_: FailedToGetResourcesException) {
val findByPostIdAndUserIdAndEmojiId =
reactionQueryService.findByPostIdAndActorIdAndEmojiId(postId, actorId, 0)
if (findByPostIdAndUserIdAndEmojiId == null) {
LOGGER.warn("FAILED Send reaction. $postId $actorId")
return
}
apReactionService.removeReaction(findByPostIdAndUserIdAndEmojiId)
reactionRepository.delete(findByPostIdAndUserIdAndEmojiId)
val reaction = Reaction(reactionRepository.generateId(), 0, postId, actorId)
reactionRepository.save(reaction)
apReactionService.reaction(reaction)
}
override suspend fun removeReaction(actorId: Long, postId: Long) {
try {
val findByPostIdAndUserIdAndEmojiId =
reactionQueryService.findByPostIdAndActorIdAndEmojiId(postId, actorId, 0)
reactionRepository.delete(findByPostIdAndUserIdAndEmojiId)
apReactionService.removeReaction(findByPostIdAndUserIdAndEmojiId)
} catch (_: FailedToGetResourcesException) {
val findByPostIdAndUserIdAndEmojiId =
reactionQueryService.findByPostIdAndActorIdAndEmojiId(postId, actorId, 0)
if (findByPostIdAndUserIdAndEmojiId == null) {
LOGGER.warn("FAILED Remove reaction. actorId: $actorId postId: $postId")
return
}
reactionRepository.delete(findByPostIdAndUserIdAndEmojiId)
apReactionService.removeReaction(findByPostIdAndUserIdAndEmojiId)
}
companion object {

View File

@ -2,7 +2,6 @@ package dev.usbharu.hideout.core.service.user
import dev.usbharu.hideout.activitypub.service.activity.delete.APSendDeleteService
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
@ -74,11 +73,11 @@ class UserServiceImpl(
override suspend fun createRemoteUser(user: RemoteUserCreateDto): Actor {
logger.info("START Create New remote user. name: {} url: {}", user.name, user.url)
try {
deletedActorQueryService.findByNameAndDomain(user.name, user.domain)
val deletedActor = deletedActorQueryService.findByNameAndDomain(user.name, user.domain)
if (deletedActor != null) {
logger.warn("FAILED Deleted actor. user: ${user.name} domain: ${user.domain}")
throw IllegalStateException("Cannot create Deleted actor.")
} catch (_: FailedToGetResourcesException) {
}
@Suppress("TooGenericExceptionCaught")

View File

@ -1,11 +1,9 @@
package dev.usbharu.hideout.mastodon.infrastructure.exposedquery
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.domain.mastodon.model.generated.Account
import dev.usbharu.hideout.mastodon.query.AccountQueryService
import dev.usbharu.hideout.util.singleOr
import org.jetbrains.exposed.sql.ResultRow
import org.jetbrains.exposed.sql.select
import org.springframework.stereotype.Repository
@ -13,12 +11,12 @@ import java.time.Instant
@Repository
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 }
return query
.singleOr { FailedToGetResourcesException("accountId: $accountId wad not exist or duplicate", it) }
.let { toAccount(it) }
.singleOrNull()
?.let { toAccount(it) }
}
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
interface AccountQueryService {
suspend fun findById(accountId: Long): Account
suspend fun findById(accountId: Long): Account?
suspend fun findByIds(accountIds: List<Long>): List<Account>
}

View File

@ -14,7 +14,8 @@ interface AccountService {
class AccountServiceImpl(
private val accountQueryService: AccountQueryService
) : 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)
}

View File

@ -53,13 +53,19 @@ class StatsesApiServiceImpl(
val account = accountService.findById(userId)
val replyUser = if (post.replyId != null) {
actorRepository.findById(postQueryService.findById(post.replyId).actorId)?.id
val findById = postQueryService.findById(post.replyId)
if (findById == null) {
null
} else {
actorRepository.findById(findById.actorId)?.id
}
} else {
null
}
// TODO: n+1解消
val mediaAttachment = post.mediaIds.map { mediaId ->
val mediaAttachment = post.mediaIds.mapNotNull { mediaId ->
mediaRepository.findById(mediaId)
}.map {
it.toMediaAttachments()