diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/Person.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/Person.kt index a6a5f8b8..93f3f4cf 100644 --- a/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/Person.kt +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/Person.kt @@ -8,6 +8,8 @@ open class Person : Object { var url: String? = null private var icon: Image? = null var publicKey: Key? = null + var endpoints: Map = emptyMap() + protected constructor() : super() @@ -22,7 +24,8 @@ open class Person : Object { outbox: String?, url: String?, icon: Image?, - publicKey: Key? + publicKey: Key?, + endpoints: Map = emptyMap() ) : super(add(type, "Person"), name, id = id) { this.preferredUsername = preferredUsername this.summary = summary @@ -31,24 +34,28 @@ open class Person : Object { this.url = url this.icon = icon this.publicKey = publicKey + this.endpoints = endpoints } override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is Person) return false + if (!super.equals(other)) return false - if (id != other.id) return false if (preferredUsername != other.preferredUsername) return false if (summary != other.summary) return false if (inbox != other.inbox) return false if (outbox != other.outbox) return false if (url != other.url) return false if (icon != other.icon) return false - return publicKey == other.publicKey + if (publicKey != other.publicKey) return false + if (endpoints != other.endpoints) return false + + return true } override fun hashCode(): Int { - var result = id?.hashCode() ?: 0 + var result = super.hashCode() result = 31 * result + (preferredUsername?.hashCode() ?: 0) result = 31 * result + (summary?.hashCode() ?: 0) result = 31 * result + (inbox?.hashCode() ?: 0) @@ -56,6 +63,8 @@ open class Person : Object { result = 31 * result + (url?.hashCode() ?: 0) result = 31 * result + (icon?.hashCode() ?: 0) result = 31 * result + (publicKey?.hashCode() ?: 0) + result = 31 * result + endpoints.hashCode() return result } + } diff --git a/src/main/kotlin/dev/usbharu/hideout/exception/FailedToGetResourcesException.kt b/src/main/kotlin/dev/usbharu/hideout/exception/FailedToGetResourcesException.kt new file mode 100644 index 00000000..95cd08a9 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/exception/FailedToGetResourcesException.kt @@ -0,0 +1,8 @@ +package dev.usbharu.hideout.exception + +open class FailedToGetResourcesException : IllegalArgumentException { + constructor() : super() + constructor(s: String?) : super(s) + constructor(message: String?, cause: Throwable?) : super(message, cause) + constructor(cause: Throwable?) : super(cause) +} diff --git a/src/main/kotlin/dev/usbharu/hideout/plugins/StatusPages.kt b/src/main/kotlin/dev/usbharu/hideout/plugins/StatusPages.kt index fed89e28..68b1f541 100644 --- a/src/main/kotlin/dev/usbharu/hideout/plugins/StatusPages.kt +++ b/src/main/kotlin/dev/usbharu/hideout/plugins/StatusPages.kt @@ -7,16 +7,18 @@ import io.ktor.server.plugins.statuspages.* import io.ktor.server.response.* fun Application.configureStatusPages() { + install(StatusPages) { exception { call, cause -> call.respondText(text = "400: $cause", status = HttpStatusCode.BadRequest) + call.application.log.warn("Bad Request", cause) } exception { call, _ -> call.respond(HttpStatusCode.Unauthorized) } exception { call, cause -> call.respondText(text = "500: ${cause.stackTraceToString()}", status = HttpStatusCode.InternalServerError) - cause.printStackTrace() + call.application.log.error("Internal Server Error", cause) } } } diff --git a/src/main/kotlin/dev/usbharu/hideout/query/FollowerQueryService.kt b/src/main/kotlin/dev/usbharu/hideout/query/FollowerQueryService.kt index 3ca4e4e6..4922b268 100644 --- a/src/main/kotlin/dev/usbharu/hideout/query/FollowerQueryService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/query/FollowerQueryService.kt @@ -9,4 +9,5 @@ interface FollowerQueryService { suspend fun findFollowingByNameAndDomain(name: String, domain: String): List suspend fun appendFollower(user: Long, follower: Long) suspend fun removeFollower(user: Long, follower: Long) + suspend fun alreadyFollow(userId: Long, followerId: Long): Boolean } diff --git a/src/main/kotlin/dev/usbharu/hideout/query/FollowerQueryServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/query/FollowerQueryServiceImpl.kt index e360ed4f..c9f0bdc8 100644 --- a/src/main/kotlin/dev/usbharu/hideout/query/FollowerQueryServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/query/FollowerQueryServiceImpl.kt @@ -198,6 +198,12 @@ class FollowerQueryServiceImpl : FollowerQueryService { } override suspend fun removeFollower(user: Long, follower: Long) { - UsersFollowers.deleteWhere { Users.id eq user and (followerId eq follower) } + UsersFollowers.deleteWhere { userId eq user and (followerId eq follower) } + } + + override suspend fun alreadyFollow(userId: Long, followerId: Long): Boolean { + return UsersFollowers.select { UsersFollowers.userId eq userId or (UsersFollowers.followerId eq followerId) } + .empty() + .not() } } diff --git a/src/main/kotlin/dev/usbharu/hideout/query/JwtRefreshTokenQueryServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/query/JwtRefreshTokenQueryServiceImpl.kt index 672b3b43..7b6affff 100644 --- a/src/main/kotlin/dev/usbharu/hideout/query/JwtRefreshTokenQueryServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/query/JwtRefreshTokenQueryServiceImpl.kt @@ -1,8 +1,10 @@ package dev.usbharu.hideout.query import dev.usbharu.hideout.domain.model.hideout.entity.JwtRefreshToken +import dev.usbharu.hideout.exception.FailedToGetResourcesException import dev.usbharu.hideout.repository.JwtRefreshTokens import dev.usbharu.hideout.repository.toJwtRefreshToken +import dev.usbharu.hideout.util.singleOr import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.deleteAll import org.jetbrains.exposed.sql.deleteWhere @@ -12,13 +14,19 @@ import org.koin.core.annotation.Single @Single class JwtRefreshTokenQueryServiceImpl : JwtRefreshTokenQueryService { override suspend fun findById(id: Long): JwtRefreshToken = - JwtRefreshTokens.select { JwtRefreshTokens.id.eq(id) }.single().toJwtRefreshToken() + JwtRefreshTokens.select { JwtRefreshTokens.id.eq(id) } + .singleOr { FailedToGetResourcesException("id: $id is a duplicate or does not exist.", it) } + .toJwtRefreshToken() override suspend fun findByToken(token: String): JwtRefreshToken = - JwtRefreshTokens.select { JwtRefreshTokens.refreshToken.eq(token) }.single().toJwtRefreshToken() + JwtRefreshTokens.select { JwtRefreshTokens.refreshToken.eq(token) } + .singleOr { FailedToGetResourcesException("token: $token is a duplicate or does not exist.", it) } + .toJwtRefreshToken() override suspend fun findByUserId(userId: Long): JwtRefreshToken = - JwtRefreshTokens.select { JwtRefreshTokens.userId.eq(userId) }.single().toJwtRefreshToken() + JwtRefreshTokens.select { JwtRefreshTokens.userId.eq(userId) } + .singleOr { FailedToGetResourcesException("userId: $userId is a duplicate or does not exist.", it) } + .toJwtRefreshToken() override suspend fun deleteById(id: Long) { JwtRefreshTokens.deleteWhere { JwtRefreshTokens.id eq id } diff --git a/src/main/kotlin/dev/usbharu/hideout/query/PostQueryServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/query/PostQueryServiceImpl.kt index 1b3969c7..4f98710b 100644 --- a/src/main/kotlin/dev/usbharu/hideout/query/PostQueryServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/query/PostQueryServiceImpl.kt @@ -1,16 +1,22 @@ package dev.usbharu.hideout.query import dev.usbharu.hideout.domain.model.hideout.entity.Post +import dev.usbharu.hideout.exception.FailedToGetResourcesException import dev.usbharu.hideout.repository.Posts import dev.usbharu.hideout.repository.toPost +import dev.usbharu.hideout.util.singleOr import org.jetbrains.exposed.sql.select import org.koin.core.annotation.Single @Single class PostQueryServiceImpl : PostQueryService { - override suspend fun findById(id: Long): Post = Posts.select { Posts.id eq id }.single().toPost() + override suspend fun findById(id: Long): Post = + Posts.select { Posts.id eq id } + .singleOr { FailedToGetResourcesException("id: $id is duplicate or does not exist.", it) }.toPost() - override suspend fun findByUrl(url: String): Post = Posts.select { Posts.url eq url }.single().toPost() + override suspend fun findByUrl(url: String): Post = Posts.select { Posts.url eq url } + .singleOr { FailedToGetResourcesException("url: $url is duplicate or does not exist.", it) }.toPost() - override suspend fun findByApId(string: String): Post = Posts.select { Posts.apId eq string }.single().toPost() + override suspend fun findByApId(string: String): Post = Posts.select { Posts.apId eq string } + .singleOr { FailedToGetResourcesException("apId: $string is duplicate or does not exist.", it) }.toPost() } diff --git a/src/main/kotlin/dev/usbharu/hideout/query/PostResponseQueryServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/query/PostResponseQueryServiceImpl.kt index 51bd8ebe..a8ef3930 100644 --- a/src/main/kotlin/dev/usbharu/hideout/query/PostResponseQueryServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/query/PostResponseQueryServiceImpl.kt @@ -1,10 +1,12 @@ package dev.usbharu.hideout.query import dev.usbharu.hideout.domain.model.hideout.dto.PostResponse +import dev.usbharu.hideout.exception.FailedToGetResourcesException import dev.usbharu.hideout.repository.Posts import dev.usbharu.hideout.repository.Users import dev.usbharu.hideout.repository.toPost import dev.usbharu.hideout.repository.toUser +import dev.usbharu.hideout.util.singleOr import org.jetbrains.exposed.sql.and import org.jetbrains.exposed.sql.innerJoin import org.jetbrains.exposed.sql.select @@ -17,7 +19,7 @@ class PostResponseQueryServiceImpl : PostResponseQueryService { return Posts .innerJoin(Users, onColumn = { Posts.userId }, otherColumn = { Users.id }) .select { Posts.id eq id } - .single() + .singleOr { FailedToGetResourcesException("id: $id,userId: $userId is a duplicate or does not exist.", it) } .let { PostResponse.from(it.toPost(), it.toUser()) } } diff --git a/src/main/kotlin/dev/usbharu/hideout/query/ReactionQueryServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/query/ReactionQueryServiceImpl.kt index ff1d5ab3..00baae4e 100644 --- a/src/main/kotlin/dev/usbharu/hideout/query/ReactionQueryServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/query/ReactionQueryServiceImpl.kt @@ -3,9 +3,11 @@ package dev.usbharu.hideout.query import dev.usbharu.hideout.domain.model.hideout.dto.Account import dev.usbharu.hideout.domain.model.hideout.dto.ReactionResponse import dev.usbharu.hideout.domain.model.hideout.entity.Reaction +import dev.usbharu.hideout.exception.FailedToGetResourcesException import dev.usbharu.hideout.repository.Reactions import dev.usbharu.hideout.repository.Users import dev.usbharu.hideout.repository.toReaction +import dev.usbharu.hideout.util.singleOr import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.koin.core.annotation.Single @@ -26,7 +28,12 @@ class ReactionQueryServiceImpl : ReactionQueryService { Reactions.emojiId.eq(emojiId) ) } - .single() + .singleOr { + FailedToGetResourcesException( + "postId: $postId,userId: $userId,emojiId: $emojiId is duplicate or does not exist.", + it + ) + } .toReaction() } diff --git a/src/main/kotlin/dev/usbharu/hideout/query/UserQueryServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/query/UserQueryServiceImpl.kt index 41042d8f..9d18d2cd 100644 --- a/src/main/kotlin/dev/usbharu/hideout/query/UserQueryServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/query/UserQueryServiceImpl.kt @@ -1,8 +1,10 @@ package dev.usbharu.hideout.query import dev.usbharu.hideout.domain.model.hideout.entity.User +import dev.usbharu.hideout.exception.FailedToGetResourcesException import dev.usbharu.hideout.repository.Users import dev.usbharu.hideout.repository.toUser +import dev.usbharu.hideout.util.singleOr import org.jetbrains.exposed.sql.and import org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.selectAll @@ -13,14 +15,22 @@ class UserQueryServiceImpl : UserQueryService { override suspend fun findAll(limit: Int, offset: Long): List = Users.selectAll().limit(limit, offset).map { it.toUser() } - override suspend fun findById(id: Long): User = Users.select { Users.id eq id }.single().toUser() + override suspend fun findById(id: Long): User = Users.select { Users.id eq id } + .singleOr { FailedToGetResourcesException("id: $id is duplicate or does not exist.", it) }.toUser() override suspend fun findByName(name: String): List = Users.select { Users.name eq name }.map { it.toUser() } override suspend fun findByNameAndDomain(name: String, domain: String): User = - Users.select { Users.name eq name and (Users.domain eq domain) }.single().toUser() + Users + .select { Users.name eq name and (Users.domain eq domain) } + .singleOr { + FailedToGetResourcesException("name: $name,domain: $domain is duplicate or does not exist.", it) + } + .toUser() - override suspend fun findByUrl(url: String): User = Users.select { Users.url eq url }.single().toUser() + override suspend fun findByUrl(url: String): User = Users.select { Users.url eq url } + .singleOr { FailedToGetResourcesException("url: $url is duplicate or does not exist.", it) } + .toUser() override suspend fun findByIds(ids: List): List = Users.select { Users.id inList ids }.map { it.toUser() } diff --git a/src/main/kotlin/dev/usbharu/hideout/repository/PostRepositoryImpl.kt b/src/main/kotlin/dev/usbharu/hideout/repository/PostRepositoryImpl.kt index 8782a54f..764bf32d 100644 --- a/src/main/kotlin/dev/usbharu/hideout/repository/PostRepositoryImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/repository/PostRepositoryImpl.kt @@ -2,6 +2,7 @@ package dev.usbharu.hideout.repository import dev.usbharu.hideout.domain.model.hideout.entity.Post import dev.usbharu.hideout.domain.model.hideout.entity.Visibility +import dev.usbharu.hideout.exception.FailedToGetResourcesException import dev.usbharu.hideout.service.core.IdGenerateService import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq @@ -53,7 +54,8 @@ class PostRepositoryImpl(database: Database, private val idGenerateService: IdGe return post } - override suspend fun findById(id: Long): Post = Posts.select { Posts.id eq id }.single().toPost() + override suspend fun findById(id: Long): Post = Posts.select { Posts.id eq id }.singleOrNull()?.toPost() + ?: throw FailedToGetResourcesException("id: $id was not found.") override suspend fun delete(id: Long) { Posts.deleteWhere { Posts.id eq id } diff --git a/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/UserRouting.kt b/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/UserRouting.kt index c4b03cba..8543e9a7 100644 --- a/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/UserRouting.kt +++ b/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/UserRouting.kt @@ -41,7 +41,12 @@ fun Routing.usersAP( ?: throw ParameterNotExistException("Parameter(name='name') does not exist."), Config.configData.domain ) - call.respondText(userEntity.toString() + "\n" + followerQueryService.findFollowersById(userEntity.id)) + val personByName = apUserService.getPersonByName(userEntity.name) + call.respondText( + userEntity.toString() + "\n" + followerQueryService.findFollowersById(userEntity.id) + "\n" + Config.configData.objectMapper.writeValueAsString( + personByName + ) + ) } } } diff --git a/src/main/kotlin/dev/usbharu/hideout/routing/api/internal/v1/Users.kt b/src/main/kotlin/dev/usbharu/hideout/routing/api/internal/v1/Users.kt index 1a73c694..dd7b64f9 100644 --- a/src/main/kotlin/dev/usbharu/hideout/routing/api/internal/v1/Users.kt +++ b/src/main/kotlin/dev/usbharu/hideout/routing/api/internal/v1/Users.kt @@ -42,11 +42,11 @@ fun Route.users(userService: UserService, userApiService: UserApiService) { authenticate(TOKEN_AUTH, optional = true) { get { val userParameter = ( - call.parameters["name"] - ?: throw ParameterNotExistException( - "Parameter(name='userName@domain') does not exist." + call.parameters["name"] + ?: throw ParameterNotExistException( + "Parameter(name='userName@domain') does not exist." + ) ) - ) if (userParameter.toLongOrNull() != null) { return@get call.respond(userApiService.findById(userParameter.toLong())) } else { @@ -72,19 +72,16 @@ fun Route.users(userService: UserService, userApiService: UserApiService) { ?: throw IllegalStateException("no principal") val userParameter = call.parameters["name"] ?: throw ParameterNotExistException("Parameter(name='userName@domain') does not exist.") - if (userParameter.toLongOrNull() != null) { - if (userService.followRequest(userParameter.toLong(), userId)) { - return@post call.respond(HttpStatusCode.OK) + if (if (userParameter.toLongOrNull() != null) { + userApiService.follow(userParameter.toLong(), userId) } else { - return@post call.respond(HttpStatusCode.Accepted) + val parse = AcctUtil.parse(userParameter) + userApiService.follow(parse, userId) } - } - val acct = AcctUtil.parse(userParameter) - val targetUser = userApiService.findByAcct(acct) - if (userService.followRequest(targetUser.id.toLong(), userId)) { - return@post call.respond(HttpStatusCode.OK) + ) { + call.respond(HttpStatusCode.OK) } else { - return@post call.respond(HttpStatusCode.Accepted) + call.respond(HttpStatusCode.Accepted) } } } @@ -92,11 +89,11 @@ fun Route.users(userService: UserService, userApiService: UserApiService) { route("/following") { get { val userParameter = ( - call.parameters["name"] - ?: throw ParameterNotExistException( - "Parameter(name='userName@domain') does not exist." + call.parameters["name"] + ?: throw ParameterNotExistException( + "Parameter(name='userName@domain') does not exist." + ) ) - ) if (userParameter.toLongOrNull() != null) { return@get call.respond(userApiService.findFollowings(userParameter.toLong())) } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/ap/APAcceptService.kt b/src/main/kotlin/dev/usbharu/hideout/service/ap/APAcceptService.kt index b1fbc9b2..3af3fbcb 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/ap/APAcceptService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/ap/APAcceptService.kt @@ -5,7 +5,9 @@ import dev.usbharu.hideout.domain.model.ActivityPubStringResponse import dev.usbharu.hideout.domain.model.ap.Accept import dev.usbharu.hideout.domain.model.ap.Follow import dev.usbharu.hideout.exception.ap.IllegalActivityPubObjectException +import dev.usbharu.hideout.query.FollowerQueryService import dev.usbharu.hideout.query.UserQueryService +import dev.usbharu.hideout.service.core.Transaction import dev.usbharu.hideout.service.user.UserService import io.ktor.http.* import org.koin.core.annotation.Single @@ -17,20 +19,27 @@ interface APAcceptService { @Single class APAcceptServiceImpl( private val userService: UserService, - private val userQueryService: UserQueryService + private val userQueryService: UserQueryService, + private val followerQueryService: FollowerQueryService, + private val transaction: Transaction ) : APAcceptService { override suspend fun receiveAccept(accept: Accept): ActivityPubResponse { - val value = accept.`object` ?: throw IllegalActivityPubObjectException("object is null") - if (value.type.contains("Follow").not()) { - throw IllegalActivityPubObjectException("Invalid type ${value.type}") - } + return transaction.transaction { + val value = accept.`object` ?: throw IllegalActivityPubObjectException("object is null") + if (value.type.contains("Follow").not()) { + throw IllegalActivityPubObjectException("Invalid type ${value.type}") + } - val follow = value as Follow - val userUrl = follow.`object` ?: throw IllegalActivityPubObjectException("object is null") - val followerUrl = follow.actor ?: throw IllegalActivityPubObjectException("actor is null") - val user = userQueryService.findByUrl(userUrl) - val follower = userQueryService.findByUrl(followerUrl) - userService.follow(user.id, follower.id) - return ActivityPubStringResponse(HttpStatusCode.OK, "accepted") + val follow = value as Follow + val userUrl = follow.`object` ?: throw IllegalActivityPubObjectException("object is null") + val followerUrl = follow.actor ?: throw IllegalActivityPubObjectException("actor is null") + val user = userQueryService.findByUrl(userUrl) + val follower = userQueryService.findByUrl(followerUrl) + if (followerQueryService.alreadyFollow(user.id, follower.id)) { + return@transaction ActivityPubStringResponse(HttpStatusCode.OK, "accepted") + } + userService.follow(user.id, follower.id) + ActivityPubStringResponse(HttpStatusCode.OK, "accepted") + } } } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/ap/APNoteService.kt b/src/main/kotlin/dev/usbharu/hideout/service/ap/APNoteService.kt index a0b76dcb..77d06bb5 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/ap/APNoteService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/ap/APNoteService.kt @@ -7,6 +7,7 @@ import dev.usbharu.hideout.domain.model.ap.Note import dev.usbharu.hideout.domain.model.hideout.entity.Post import dev.usbharu.hideout.domain.model.hideout.entity.Visibility import dev.usbharu.hideout.domain.model.job.DeliverPostJob +import dev.usbharu.hideout.exception.FailedToGetResourcesException import dev.usbharu.hideout.exception.ap.IllegalActivityPubObjectException import dev.usbharu.hideout.plugins.getAp import dev.usbharu.hideout.plugins.postAp @@ -83,11 +84,10 @@ class APNoteServiceImpl( } override suspend fun fetchNote(url: String, targetActor: String?): Note { - val post = postQueryService.findByUrl(url) try { + val post = postQueryService.findByUrl(url) return postToNote(post) - } catch (_: NoSuchElementException) { - } catch (_: IllegalArgumentException) { + } catch (_: FailedToGetResourcesException) { } val response = httpClient.getAp( @@ -125,21 +125,18 @@ class APNoteServiceImpl( val findByApId = try { postQueryService.findByApId(note.id!!) - } catch (_: NoSuchElementException) { - return internalNote(note, targetActor, url) - } catch (_: IllegalArgumentException) { + } catch (_: FailedToGetResourcesException) { return internalNote(note, targetActor, url) } return postToNote(findByApId) } private suspend fun internalNote(note: Note, targetActor: String?, url: String): Note { - val person = apUserService.fetchPerson( + val person = apUserService.fetchPersonWithEntity( note.attributedTo ?: throw IllegalActivityPubObjectException("note.attributedTo is null"), targetActor ) - val user = - userQueryService.findByUrl(person.url ?: throw IllegalActivityPubObjectException("person.url is null")) + val visibility = if (note.to.contains(public) && note.cc.contains(public)) { @@ -160,7 +157,7 @@ class APNoteServiceImpl( postRepository.save( Post( id = postRepository.generateId(), - userId = user.id, + userId = person.second.id, overview = null, text = note.content.orEmpty(), createdAt = Instant.parse(note.published).toEpochMilli(), diff --git a/src/main/kotlin/dev/usbharu/hideout/service/ap/APUserService.kt b/src/main/kotlin/dev/usbharu/hideout/service/ap/APUserService.kt index caf9f91b..2f095a6a 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/ap/APUserService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/ap/APUserService.kt @@ -6,6 +6,8 @@ import dev.usbharu.hideout.domain.model.ap.Image import dev.usbharu.hideout.domain.model.ap.Key import dev.usbharu.hideout.domain.model.ap.Person import dev.usbharu.hideout.domain.model.hideout.dto.RemoteUserCreateDto +import dev.usbharu.hideout.domain.model.hideout.entity.User +import dev.usbharu.hideout.exception.FailedToGetResourcesException import dev.usbharu.hideout.exception.ap.IllegalActivityPubObjectException import dev.usbharu.hideout.plugins.getAp import dev.usbharu.hideout.query.UserQueryService @@ -29,6 +31,8 @@ interface APUserService { * @return */ suspend fun fetchPerson(url: String, targetActor: String? = null): Person + + suspend fun fetchPersonWithEntity(url: String, targetActor: String? = null): Pair } @Single @@ -67,11 +71,15 @@ class APUserServiceImpl( id = "$userUrl#pubkey", owner = userUrl, publicKeyPem = userEntity.publicKey - ) + ), + endpoints = mapOf("sharedInbox" to "${Config.configData.url}/inbox") ) } - override suspend fun fetchPerson(url: String, targetActor: String?): Person { + override suspend fun fetchPerson(url: String, targetActor: String?): Person = + fetchPersonWithEntity(url, targetActor).first + + override suspend fun fetchPersonWithEntity(url: String, targetActor: String?): Pair { return try { val userEntity = userQueryService.findByUrl(url) return Person( @@ -95,9 +103,10 @@ class APUserServiceImpl( id = "$url#pubkey", owner = url, publicKeyPem = userEntity.publicKey - ) - ) - } catch (ignore: NoSuchElementException) { + ), + endpoints = mapOf("sharedInbox" to "${Config.configData.url}/inbox") + ) to userEntity + } catch (ignore: FailedToGetResourcesException) { val httpResponse = if (targetActor != null) { httpClient.getAp(url, "$targetActor#pubkey") } else { @@ -107,7 +116,7 @@ class APUserServiceImpl( } val person = Config.configData.objectMapper.readValue(httpResponse.bodyAsText()) - userService.createRemoteUser( + person to userService.createRemoteUser( RemoteUserCreateDto( name = person.preferredUsername ?: throw IllegalActivityPubObjectException("preferredUsername is null"), @@ -122,7 +131,6 @@ class APUserServiceImpl( ?: throw IllegalActivityPubObjectException("publicKey is null"), ) ) - person } } } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/api/UserApiService.kt b/src/main/kotlin/dev/usbharu/hideout/service/api/UserApiService.kt index fb8ce555..4cdf8ac7 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/api/UserApiService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/api/UserApiService.kt @@ -30,6 +30,9 @@ interface UserApiService { suspend fun findFollowingsByAcct(acct: Acct): List suspend fun createUser(username: String, password: String): UserResponse + + suspend fun follow(targetId: Long, sourceId: Long): Boolean + suspend fun follow(targetAcct: Acct, sourceId: Long): Boolean } @Single @@ -39,30 +42,50 @@ class UserApiServiceImpl( private val userService: UserService, private val transaction: Transaction ) : UserApiService { - override suspend fun findAll(limit: Int?, offset: Long): List = + override suspend fun findAll(limit: Int?, offset: Long): List = transaction.transaction { userQueryService.findAll(min(limit ?: 100, 100), offset).map { UserResponse.from(it) } + } - override suspend fun findById(id: Long): UserResponse = UserResponse.from(userQueryService.findById(id)) - override suspend fun findByIds(ids: List): List = - userQueryService.findByIds(ids).map { UserResponse.from(it) } + override suspend fun findById(id: Long): UserResponse = + transaction.transaction { UserResponse.from(userQueryService.findById(id)) } - override suspend fun findByAcct(acct: Acct): UserResponse = - UserResponse.from(userQueryService.findByNameAndDomain(acct.username, acct.domain ?: Config.configData.domain)) + override suspend fun findByIds(ids: List): List { + return transaction.transaction { + userQueryService.findByIds(ids).map { UserResponse.from(it) } + } + } - override suspend fun findFollowers(userId: Long): List = + override suspend fun findByAcct(acct: Acct): UserResponse { + return transaction.transaction { + UserResponse.from( + userQueryService.findByNameAndDomain( + acct.username, + acct.domain ?: Config.configData.domain + ) + ) + } + } + + override suspend fun findFollowers(userId: Long): List = transaction.transaction { followerQueryService.findFollowersById(userId).map { UserResponse.from(it) } + } - override suspend fun findFollowings(userId: Long): List = + + override suspend fun findFollowings(userId: Long): List = transaction.transaction { followerQueryService.findFollowingById(userId).map { UserResponse.from(it) } + } - override suspend fun findFollowersByAcct(acct: Acct): List = + override suspend fun findFollowersByAcct(acct: Acct): List = transaction.transaction { followerQueryService.findFollowersByNameAndDomain(acct.username, acct.domain ?: Config.configData.domain) .map { UserResponse.from(it) } + } - override suspend fun findFollowingsByAcct(acct: Acct): List = + override suspend fun findFollowingsByAcct(acct: Acct): List = transaction.transaction { followerQueryService.findFollowingByNameAndDomain(acct.username, acct.domain ?: Config.configData.domain) .map { UserResponse.from(it) } + } + override suspend fun createUser(username: String, password: String): UserResponse { return transaction.transaction { @@ -72,4 +95,21 @@ class UserApiServiceImpl( UserResponse.from(userService.createLocalUser(UserCreateDto(username, username, "", password))) } } + + override suspend fun follow(targetId: Long, sourceId: Long): Boolean { + return transaction.transaction { + userService.followRequest(targetId, sourceId) + } + } + + override suspend fun follow(targetAcct: Acct, sourceId: Long): Boolean { + return transaction.transaction { + userService.followRequest( + userQueryService.findByNameAndDomain( + targetAcct.username, + targetAcct.domain ?: Config.configData.domain + ).id, sourceId + ) + } + } } diff --git a/src/main/kotlin/dev/usbharu/hideout/util/CollectionUtil.kt b/src/main/kotlin/dev/usbharu/hideout/util/CollectionUtil.kt new file mode 100644 index 00000000..6f418646 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/util/CollectionUtil.kt @@ -0,0 +1,14 @@ +package dev.usbharu.hideout.util + +class CollectionUtil { +} + +fun Iterable.singleOr(block: (e: RuntimeException) -> Throwable): T { + return try { + this.single() + } catch (e: NoSuchElementException) { + throw block(e) + } catch (e: IllegalArgumentException) { + throw block(e) + } +} diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 9129b1b2..ad457f2b 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -4,7 +4,7 @@ %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - +