diff --git a/src/main/kotlin/dev/usbharu/hideout/Application.kt b/src/main/kotlin/dev/usbharu/hideout/Application.kt index 6b0b8c90..7b3c4fbf 100644 --- a/src/main/kotlin/dev/usbharu/hideout/Application.kt +++ b/src/main/kotlin/dev/usbharu/hideout/Application.kt @@ -66,7 +66,7 @@ fun Application.parent() { HttpClient(CIO).config { install(Logging) { logger = Logger.DEFAULT - level = LogLevel.ALL + level = LogLevel.INFO } install(httpSignaturePlugin) { keyMap = KtorKeyMap(get()) diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/entity/FollowRequest.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/entity/FollowRequest.kt new file mode 100644 index 00000000..b9769227 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/entity/FollowRequest.kt @@ -0,0 +1,3 @@ +package dev.usbharu.hideout.domain.model.hideout.entity + +data class FollowRequest(val userId: Long, val followerId: Long) diff --git a/src/main/kotlin/dev/usbharu/hideout/repository/IUserRepository.kt b/src/main/kotlin/dev/usbharu/hideout/repository/IUserRepository.kt index c35382dd..07912009 100644 --- a/src/main/kotlin/dev/usbharu/hideout/repository/IUserRepository.kt +++ b/src/main/kotlin/dev/usbharu/hideout/repository/IUserRepository.kt @@ -35,5 +35,9 @@ interface IUserRepository { suspend fun deleteFollower(id: Long, follower: Long) suspend fun findFollowersById(id: Long): List + suspend fun addFollowRequest(id: Long, follower: Long) + suspend fun deleteFollowRequest(id: Long, follower: Long) + suspend fun findFollowRequestsById(id: Long, follower: Long): Boolean + suspend fun nextId(): Long } diff --git a/src/main/kotlin/dev/usbharu/hideout/repository/UserRepository.kt b/src/main/kotlin/dev/usbharu/hideout/repository/UserRepository.kt index 763f71eb..6bfa4449 100644 --- a/src/main/kotlin/dev/usbharu/hideout/repository/UserRepository.kt +++ b/src/main/kotlin/dev/usbharu/hideout/repository/UserRepository.kt @@ -20,6 +20,8 @@ class UserRepository(private val database: Database, private val idGenerateServi SchemaUtils.create(UsersFollowers) SchemaUtils.createMissingTablesAndColumns(Users) SchemaUtils.createMissingTablesAndColumns(UsersFollowers) + SchemaUtils.create(FollowRequests) + SchemaUtils.createMissingTablesAndColumns(FollowRequests) } } @@ -180,6 +182,28 @@ class UserRepository(private val database: Database, private val idGenerateServi } } + override suspend fun addFollowRequest(id: Long, follower: Long) { + query { + FollowRequests.insert { + it[userId] = id + it[followerId] = follower + } + } + } + + override suspend fun deleteFollowRequest(id: Long, follower: Long) { + query { + FollowRequests.deleteWhere { userId.eq(id) and followerId.eq(follower) } + } + } + + override suspend fun findFollowRequestsById(id: Long, follower: Long): Boolean { + return query { + FollowRequests.select { (FollowRequests.userId eq id) and (FollowRequests.followerId eq follower) } + .singleOrNull() != null + } + } + override suspend fun delete(id: Long) { query { Users.deleteWhere { Users.id.eq(id) } @@ -253,3 +277,12 @@ object UsersFollowers : LongIdTable("users_followers") { uniqueIndex(userId, followerId) } } + +object FollowRequests : LongIdTable("follow_requests") { + val userId = long("user_id").references(Users.id) + val followerId = long("follower_id").references(Users.id) + + init { + uniqueIndex(userId, followerId) + } +} 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 bf8c4b11..20d7481a 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 @@ -71,7 +71,7 @@ fun Route.users(userService: IUserService, userApiService: IUserApiService) { val userParameter = call.parameters["name"] ?: throw ParameterNotExistException("Parameter(name='userName@domain') does not exist.") if (userParameter.toLongOrNull() != null) { - if (userService.follow(userParameter.toLong(), userId)) { + if (userService.followRequest(userParameter.toLong(), userId)) { return@post call.respond(HttpStatusCode.OK) } else { return@post call.respond(HttpStatusCode.Accepted) @@ -79,7 +79,7 @@ fun Route.users(userService: IUserService, userApiService: IUserApiService) { } val acct = AcctUtil.parse(userParameter) val targetUser = userApiService.findByAcct(acct) - if (userService.follow(targetUser.id, userId)) { + if (userService.followRequest(targetUser.id, userId)) { return@post call.respond(HttpStatusCode.OK) } else { return@post call.respond(HttpStatusCode.Accepted) diff --git a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubReceiveFollowServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubReceiveFollowServiceImpl.kt index 31a64738..da036de7 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubReceiveFollowServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubReceiveFollowServiceImpl.kt @@ -49,6 +49,6 @@ class ActivityPubReceiveFollowServiceImpl( val users = userService.findByUrls(listOf(targetActor, follow.actor ?: throw IllegalArgumentException("actor is null"))) - userService.follow(users.first { it.url == targetActor }.id, users.first { it.url == follow.actor }.id) + userService.followRequest(users.first { it.url == targetActor }.id, users.first { it.url == follow.actor }.id) } } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubServiceImpl.kt index 1c9a11e7..4760f05b 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubServiceImpl.kt @@ -26,7 +26,7 @@ class ActivityPubServiceImpl( val logger: Logger = LoggerFactory.getLogger(this::class.java) override fun parseActivity(json: String): ActivityType { val readTree = configData.objectMapper.readTree(json) - logger.debug("readTree: {}", readTree) + logger.trace("readTree: {}", readTree) if (readTree.isObject.not()) { throw JsonParseException("Json is not object.") } @@ -41,16 +41,9 @@ class ActivityPubServiceImpl( @Suppress("CyclomaticComplexMethod", "NotImplementedDeclaration") override suspend fun processActivity(json: String, type: ActivityType): ActivityPubResponse { + logger.debug("proccess activity: {}", type) return when (type) { ActivityType.Accept -> activityPubAcceptService.receiveAccept(configData.objectMapper.readValue(json)) - ActivityType.Add -> TODO() - ActivityType.Announce -> TODO() - ActivityType.Arrive -> TODO() - ActivityType.Block -> TODO() - ActivityType.Create -> TODO() - ActivityType.Delete -> TODO() - ActivityType.Dislike -> TODO() - ActivityType.Flag -> TODO() ActivityType.Follow -> activityPubReceiveFollowService.receiveFollow( configData.objectMapper.readValue( json, @@ -58,25 +51,11 @@ class ActivityPubServiceImpl( ) ) - ActivityType.Ignore -> TODO() - ActivityType.Invite -> TODO() - ActivityType.Join -> TODO() - ActivityType.Leave -> TODO() - ActivityType.Like -> TODO() - ActivityType.Listen -> TODO() - ActivityType.Move -> TODO() - ActivityType.Offer -> TODO() - ActivityType.Question -> TODO() - ActivityType.Reject -> TODO() - ActivityType.Read -> TODO() - ActivityType.Remove -> TODO() - ActivityType.TentativeReject -> TODO() - ActivityType.TentativeAccept -> TODO() - ActivityType.Travel -> TODO() ActivityType.Undo -> activityPubUndoService.receiveUndo(configData.objectMapper.readValue(json)) - ActivityType.Update -> TODO() - ActivityType.View -> TODO() - ActivityType.Other -> TODO() + + else -> { + throw IllegalArgumentException("$type is not supported.") + } } } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/impl/IUserService.kt b/src/main/kotlin/dev/usbharu/hideout/service/impl/IUserService.kt index c6b7c0b0..b83911ba 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/impl/IUserService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/impl/IUserService.kt @@ -39,13 +39,21 @@ interface IUserService { suspend fun findFollowingByNameAndDomain(name: String, domain: String?): List /** - * フォロワーを追加する + * フォローリクエストを送信する * * @param id - * @param follower + * @param followerId * @return リクエストが成功したか */ - suspend fun follow(id: Long, follower: Long): Boolean + suspend fun followRequest(id: Long, followerId: Long): Boolean - suspend fun unfollow(id: Long, follower: Long): Boolean + /** + * フォローする + * + * @param id + * @param followerId + */ + suspend fun follow(id: Long, followerId: Long) + + suspend fun unfollow(id: Long, followerId: Long): Boolean } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/impl/UserService.kt b/src/main/kotlin/dev/usbharu/hideout/service/impl/UserService.kt index 23ccaa6c..43a3a2dd 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/impl/UserService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/impl/UserService.kt @@ -111,23 +111,31 @@ class UserService( } // TODO APのフォロー処理を作る - override suspend fun follow(id: Long, followerId: Long): Boolean { + override suspend fun followRequest(id: Long, followerId: Long): Boolean { val user = userRepository.findById(id) ?: throw UserNotFoundException("$id was not found.") val follower = userRepository.findById(followerId) ?: throw UserNotFoundException("$followerId was not found.") - if (follower.domain != Config.configData.domain) { - throw IllegalArgumentException("follower is not local user.") - } return if (user.domain == Config.configData.domain) { - userRepository.createFollower(id, followerId) + follow(id, followerId) true } else { - activityPubSendFollowService.sendFollow(SendFollowDto(follower, user)) + if (userRepository.findFollowRequestsById(id, followerId)) { + // do-nothing + } else { + activityPubSendFollowService.sendFollow(SendFollowDto(follower, user)) + } false } } - override suspend fun unfollow(id: Long, follower: Long): Boolean { - userRepository.deleteFollower(id, follower) + override suspend fun follow(id: Long, followerId: Long) { + userRepository.createFollower(id, followerId) + if (userRepository.findFollowRequestsById(id, followerId)) { + userRepository.deleteFollowRequest(id, followerId) + } + } + + override suspend fun unfollow(id: Long, followerId: Long): Boolean { + userRepository.deleteFollower(id, followerId) return false } } diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index eb4be7a0..4593b633 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 - + diff --git a/src/test/kotlin/dev/usbharu/hideout/routing/api/internal/v1/UsersTest.kt b/src/test/kotlin/dev/usbharu/hideout/routing/api/internal/v1/UsersTest.kt index acc7f9a3..91da7b03 100644 --- a/src/test/kotlin/dev/usbharu/hideout/routing/api/internal/v1/UsersTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/routing/api/internal/v1/UsersTest.kt @@ -432,7 +432,7 @@ class UsersTest { ) } val userService = mock { - onBlocking { follow(eq(1235), eq(1234)) } doReturn true + onBlocking { followRequest(eq(1235), eq(1234)) } doReturn true } application { configureSerialization() @@ -482,7 +482,7 @@ class UsersTest { ) } val userService = mock { - onBlocking { follow(eq(1235), eq(1234)) } doReturn false + onBlocking { followRequest(eq(1235), eq(1234)) } doReturn false } application { configureSerialization() @@ -532,7 +532,7 @@ class UsersTest { ) } val userService = mock { - onBlocking { follow(eq(1235), eq(1234)) } doReturn false + onBlocking { followRequest(eq(1235), eq(1234)) } doReturn false } application { configureSerialization() diff --git a/src/test/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubReceiveFollowServiceImplTest.kt b/src/test/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubReceiveFollowServiceImplTest.kt index 29525057..b58068f3 100644 --- a/src/test/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubReceiveFollowServiceImplTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubReceiveFollowServiceImplTest.kt @@ -116,7 +116,7 @@ class ActivityPubReceiveFollowServiceImplTest { createdAt = Instant.now() ) ) - onBlocking { follow(any(), any()) } doReturn false + onBlocking { followRequest(any(), any()) } doReturn false } val activityPubFollowService = ActivityPubReceiveFollowServiceImpl(