Merge pull request #33 from usbharu/feature/resource-not-found-exception

Feature/resource not found exception
This commit is contained in:
usbharu 2023-08-15 01:56:31 +09:00 committed by GitHub
commit 5a56c61ee4
20 changed files with 217 additions and 97 deletions

View File

@ -8,6 +8,7 @@ open class Person : Object {
var url: String? = null
private var icon: Image? = null
var publicKey: Key? = null
var endpoints: Map<String, String> = emptyMap()
protected constructor() : super()
@ -22,7 +23,8 @@ open class Person : Object {
outbox: String?,
url: String?,
icon: Image?,
publicKey: Key?
publicKey: Key?,
endpoints: Map<String, String> = emptyMap()
) : super(add(type, "Person"), name, id = id) {
this.preferredUsername = preferredUsername
this.summary = summary
@ -31,24 +33,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 +62,7 @@ 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
}
}

View File

@ -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)
}

View File

@ -10,13 +10,14 @@ fun Application.configureStatusPages() {
install(StatusPages) {
exception<IllegalArgumentException> { call, cause ->
call.respondText(text = "400: $cause", status = HttpStatusCode.BadRequest)
call.application.log.warn("Bad Request", cause)
}
exception<InvalidUsernameOrPasswordException> { call, _ ->
call.respond(HttpStatusCode.Unauthorized)
}
exception<Throwable> { call, cause ->
call.respondText(text = "500: ${cause.stackTraceToString()}", status = HttpStatusCode.InternalServerError)
cause.printStackTrace()
call.application.log.error("Internal Server Error", cause)
}
}
}

View File

@ -9,4 +9,5 @@ interface FollowerQueryService {
suspend fun findFollowingByNameAndDomain(name: String, domain: String): List<User>
suspend fun appendFollower(user: Long, follower: Long)
suspend fun removeFollower(user: Long, follower: Long)
suspend fun alreadyFollow(userId: Long, followerId: Long): Boolean
}

View File

@ -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()
}
}

View File

@ -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 }

View File

@ -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()
}

View File

@ -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()) }
}

View File

@ -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()
}

View File

@ -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<User> =
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<User> = 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<Long>): List<User> =
Users.select { Users.id inList ids }.map { it.toUser() }

View File

@ -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 }

View File

@ -28,10 +28,7 @@ fun Routing.usersAP(
val name =
call.parameters["name"] ?: throw ParameterNotExistException("Parameter(name='name') does not exist.")
val person = apUserService.getPersonByName(name)
return@handle call.respondAp(
person,
HttpStatusCode.OK
)
return@handle call.respondAp(person, HttpStatusCode.OK)
}
get {
// TODO: 暫定処置なので治す
@ -41,7 +38,13 @@ 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
)
)
}
}
}
@ -51,9 +54,7 @@ class ContentTypeRouteSelector(private vararg val contentType: ContentType) : Ro
override fun evaluate(context: RoutingResolveContext, segmentIndex: Int): RouteSelectorEvaluation {
context.call.application.log.debug("Accept: ${context.call.request.accept()}")
val requestContentType = context.call.request.accept() ?: return RouteSelectorEvaluation.FailedParameter
return if (requestContentType.split(",")
.any { contentType.any { contentType -> contentType.match(it) } }
) {
return if (requestContentType.split(",").any { contentType.any { contentType -> contentType.match(it) } }) {
RouteSelectorEvaluation.Constant
} else {
RouteSelectorEvaluation.FailedParameter

View File

@ -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()))
}

View File

@ -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")
}
}
}

View File

@ -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(
@ -119,23 +119,23 @@ class APNoteServiceImpl(
targetActor: String?,
url: String
): Note {
val findByApId = try {
postQueryService.findByApId(url)
} catch (_: NoSuchElementException) {
if (note.id == null) {
return internalNote(note, targetActor, url)
} catch (_: IllegalArgumentException) {
}
val findByApId = try {
postQueryService.findByApId(note.id!!)
} 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)) {
@ -156,7 +156,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(),

View File

@ -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<Person, User>
}
@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<Person, User> {
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<Person>(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
}
}
}

View File

@ -12,6 +12,7 @@ import dev.usbharu.hideout.service.user.UserService
import org.koin.core.annotation.Single
import kotlin.math.min
@Suppress("TooManyFunctions")
interface UserApiService {
suspend fun findAll(limit: Int? = 100, offset: Long = 0): List<UserResponse>
@ -30,6 +31,9 @@ interface UserApiService {
suspend fun findFollowingsByAcct(acct: Acct): List<UserResponse>
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 +43,47 @@ class UserApiServiceImpl(
private val userService: UserService,
private val transaction: Transaction
) : UserApiService {
override suspend fun findAll(limit: Int?, offset: Long): List<UserResponse> =
override suspend fun findAll(limit: Int?, offset: Long): List<UserResponse> = 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 findById(id: Long): UserResponse =
transaction.transaction { UserResponse.from(userQueryService.findById(id)) }
override suspend fun findByIds(ids: List<Long>): List<UserResponse> =
userQueryService.findByIds(ids).map { UserResponse.from(it) }
override suspend fun findByIds(ids: List<Long>): List<UserResponse> {
return transaction.transaction {
userQueryService.findByIds(ids).map { UserResponse.from(it) }
}
}
override suspend fun findByAcct(acct: Acct): UserResponse =
UserResponse.from(userQueryService.findByNameAndDomain(acct.username, acct.domain ?: Config.configData.domain))
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<UserResponse> =
override suspend fun findFollowers(userId: Long): List<UserResponse> = transaction.transaction {
followerQueryService.findFollowersById(userId).map { UserResponse.from(it) }
}
override suspend fun findFollowings(userId: Long): List<UserResponse> =
override suspend fun findFollowings(userId: Long): List<UserResponse> = transaction.transaction {
followerQueryService.findFollowingById(userId).map { UserResponse.from(it) }
}
override suspend fun findFollowersByAcct(acct: Acct): List<UserResponse> =
override suspend fun findFollowersByAcct(acct: Acct): List<UserResponse> = transaction.transaction {
followerQueryService.findFollowersByNameAndDomain(acct.username, acct.domain ?: Config.configData.domain)
.map { UserResponse.from(it) }
}
override suspend fun findFollowingsByAcct(acct: Acct): List<UserResponse> =
override suspend fun findFollowingsByAcct(acct: Acct): List<UserResponse> = 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 +93,22 @@ 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
)
}
}
}

View File

@ -0,0 +1,13 @@
package dev.usbharu.hideout.util
class CollectionUtil
fun <T> Iterable<T>.singleOr(block: (e: RuntimeException) -> Throwable): T {
return try {
this.single()
} catch (e: NoSuchElementException) {
throw block(e)
} catch (e: IllegalArgumentException) {
throw block(e)
}
}

View File

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

View File

@ -4,6 +4,7 @@ import com.auth0.jwt.interfaces.Claim
import com.auth0.jwt.interfaces.Payload
import com.fasterxml.jackson.module.kotlin.readValue
import dev.usbharu.hideout.config.Config
import dev.usbharu.hideout.domain.model.Acct
import dev.usbharu.hideout.domain.model.hideout.dto.UserResponse
import dev.usbharu.hideout.domain.model.hideout.entity.User
import dev.usbharu.hideout.domain.model.hideout.form.UserCreate
@ -433,9 +434,7 @@ class UsersTest {
"https://example.com/test",
Instant.now().toEpochMilli()
)
}
val userService = mock<UserService> {
onBlocking { followRequest(eq(1235), eq(1234)) } doReturn true
onBlocking { follow(any<Acct>(), eq(1234)) } doReturn true
}
application {
configureSerialization()
@ -448,7 +447,7 @@ class UsersTest {
}
routing {
route("/api/internal/v1") {
users(userService, userApiService)
users(mock(), userApiService)
}
}
}
@ -483,9 +482,7 @@ class UsersTest {
"https://example.com/test",
Instant.now().toEpochMilli()
)
}
val userService = mock<UserService> {
onBlocking { followRequest(eq(1235), eq(1234)) } doReturn false
onBlocking { follow(any<Acct>(), eq(1234)) } doReturn false
}
application {
configureSerialization()
@ -498,7 +495,7 @@ class UsersTest {
}
routing {
route("/api/internal/v1") {
users(userService, userApiService)
users(mock(), userApiService)
}
}
}
@ -533,9 +530,7 @@ class UsersTest {
"https://example.com/test",
Instant.now().toEpochMilli()
)
}
val userService = mock<UserService> {
onBlocking { followRequest(eq(1235), eq(1234)) } doReturn false
onBlocking { follow(eq(1235), eq(1234)) } doReturn false
}
application {
configureSerialization()
@ -548,7 +543,7 @@ class UsersTest {
}
routing {
route("/api/internal/v1") {
users(userService, userApiService)
users(mock(), userApiService)
}
}
}