feat: フォローリクエストに対応。フォローした後無限ループする問題を修正

This commit is contained in:
usbharu 2023-05-22 23:33:33 +09:00
parent c1f7f838ca
commit 5b6e0a3657
12 changed files with 83 additions and 48 deletions

View File

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

View File

@ -0,0 +1,3 @@
package dev.usbharu.hideout.domain.model.hideout.entity
data class FollowRequest(val userId: Long, val followerId: Long)

View File

@ -35,5 +35,9 @@ interface IUserRepository {
suspend fun deleteFollower(id: Long, follower: Long)
suspend fun findFollowersById(id: Long): List<User>
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
}

View File

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

View File

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

View File

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

View File

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

View File

@ -39,13 +39,21 @@ interface IUserService {
suspend fun findFollowingByNameAndDomain(name: String, domain: String?): List<User>
/**
* フォロワーを追加する
* フォローリクエストを送信する
*
* @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
}

View File

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

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="trace">
<root level="DEBUG">
<appender-ref ref="STDOUT"/>
</root>
<logger name="org.eclipse.jetty" level="INFO"/>

View File

@ -432,7 +432,7 @@ class UsersTest {
)
}
val userService = mock<IUserService> {
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<IUserService> {
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<IUserService> {
onBlocking { follow(eq(1235), eq(1234)) } doReturn false
onBlocking { followRequest(eq(1235), eq(1234)) } doReturn false
}
application {
configureSerialization()

View File

@ -116,7 +116,7 @@ class ActivityPubReceiveFollowServiceImplTest {
createdAt = Instant.now()
)
)
onBlocking { follow(any(), any()) } doReturn false
onBlocking { followRequest(any(), any()) } doReturn false
}
val activityPubFollowService =
ActivityPubReceiveFollowServiceImpl(