mirror of https://github.com/usbharu/Hideout.git
feat: フォローリクエストに対応。フォローした後無限ループする問題を修正
This commit is contained in:
parent
c1f7f838ca
commit
5b6e0a3657
|
@ -66,7 +66,7 @@ fun Application.parent() {
|
||||||
HttpClient(CIO).config {
|
HttpClient(CIO).config {
|
||||||
install(Logging) {
|
install(Logging) {
|
||||||
logger = Logger.DEFAULT
|
logger = Logger.DEFAULT
|
||||||
level = LogLevel.ALL
|
level = LogLevel.INFO
|
||||||
}
|
}
|
||||||
install(httpSignaturePlugin) {
|
install(httpSignaturePlugin) {
|
||||||
keyMap = KtorKeyMap(get())
|
keyMap = KtorKeyMap(get())
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
package dev.usbharu.hideout.domain.model.hideout.entity
|
||||||
|
|
||||||
|
data class FollowRequest(val userId: Long, val followerId: Long)
|
|
@ -35,5 +35,9 @@ interface IUserRepository {
|
||||||
suspend fun deleteFollower(id: Long, follower: Long)
|
suspend fun deleteFollower(id: Long, follower: Long)
|
||||||
suspend fun findFollowersById(id: Long): List<User>
|
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
|
suspend fun nextId(): Long
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,8 @@ class UserRepository(private val database: Database, private val idGenerateServi
|
||||||
SchemaUtils.create(UsersFollowers)
|
SchemaUtils.create(UsersFollowers)
|
||||||
SchemaUtils.createMissingTablesAndColumns(Users)
|
SchemaUtils.createMissingTablesAndColumns(Users)
|
||||||
SchemaUtils.createMissingTablesAndColumns(UsersFollowers)
|
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) {
|
override suspend fun delete(id: Long) {
|
||||||
query {
|
query {
|
||||||
Users.deleteWhere { Users.id.eq(id) }
|
Users.deleteWhere { Users.id.eq(id) }
|
||||||
|
@ -253,3 +277,12 @@ object UsersFollowers : LongIdTable("users_followers") {
|
||||||
uniqueIndex(userId, followerId)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -71,7 +71,7 @@ fun Route.users(userService: IUserService, userApiService: IUserApiService) {
|
||||||
val userParameter = call.parameters["name"]
|
val userParameter = call.parameters["name"]
|
||||||
?: throw ParameterNotExistException("Parameter(name='userName@domain') does not exist.")
|
?: throw ParameterNotExistException("Parameter(name='userName@domain') does not exist.")
|
||||||
if (userParameter.toLongOrNull() != null) {
|
if (userParameter.toLongOrNull() != null) {
|
||||||
if (userService.follow(userParameter.toLong(), userId)) {
|
if (userService.followRequest(userParameter.toLong(), userId)) {
|
||||||
return@post call.respond(HttpStatusCode.OK)
|
return@post call.respond(HttpStatusCode.OK)
|
||||||
} else {
|
} else {
|
||||||
return@post call.respond(HttpStatusCode.Accepted)
|
return@post call.respond(HttpStatusCode.Accepted)
|
||||||
|
@ -79,7 +79,7 @@ fun Route.users(userService: IUserService, userApiService: IUserApiService) {
|
||||||
}
|
}
|
||||||
val acct = AcctUtil.parse(userParameter)
|
val acct = AcctUtil.parse(userParameter)
|
||||||
val targetUser = userApiService.findByAcct(acct)
|
val targetUser = userApiService.findByAcct(acct)
|
||||||
if (userService.follow(targetUser.id, userId)) {
|
if (userService.followRequest(targetUser.id, userId)) {
|
||||||
return@post call.respond(HttpStatusCode.OK)
|
return@post call.respond(HttpStatusCode.OK)
|
||||||
} else {
|
} else {
|
||||||
return@post call.respond(HttpStatusCode.Accepted)
|
return@post call.respond(HttpStatusCode.Accepted)
|
||||||
|
|
|
@ -49,6 +49,6 @@ class ActivityPubReceiveFollowServiceImpl(
|
||||||
val users =
|
val users =
|
||||||
userService.findByUrls(listOf(targetActor, follow.actor ?: throw IllegalArgumentException("actor is null")))
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ class ActivityPubServiceImpl(
|
||||||
val logger: Logger = LoggerFactory.getLogger(this::class.java)
|
val logger: Logger = LoggerFactory.getLogger(this::class.java)
|
||||||
override fun parseActivity(json: String): ActivityType {
|
override fun parseActivity(json: String): ActivityType {
|
||||||
val readTree = configData.objectMapper.readTree(json)
|
val readTree = configData.objectMapper.readTree(json)
|
||||||
logger.debug("readTree: {}", readTree)
|
logger.trace("readTree: {}", readTree)
|
||||||
if (readTree.isObject.not()) {
|
if (readTree.isObject.not()) {
|
||||||
throw JsonParseException("Json is not object.")
|
throw JsonParseException("Json is not object.")
|
||||||
}
|
}
|
||||||
|
@ -41,16 +41,9 @@ class ActivityPubServiceImpl(
|
||||||
|
|
||||||
@Suppress("CyclomaticComplexMethod", "NotImplementedDeclaration")
|
@Suppress("CyclomaticComplexMethod", "NotImplementedDeclaration")
|
||||||
override suspend fun processActivity(json: String, type: ActivityType): ActivityPubResponse {
|
override suspend fun processActivity(json: String, type: ActivityType): ActivityPubResponse {
|
||||||
|
logger.debug("proccess activity: {}", type)
|
||||||
return when (type) {
|
return when (type) {
|
||||||
ActivityType.Accept -> activityPubAcceptService.receiveAccept(configData.objectMapper.readValue(json))
|
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(
|
ActivityType.Follow -> activityPubReceiveFollowService.receiveFollow(
|
||||||
configData.objectMapper.readValue(
|
configData.objectMapper.readValue(
|
||||||
json,
|
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.Undo -> activityPubUndoService.receiveUndo(configData.objectMapper.readValue(json))
|
||||||
ActivityType.Update -> TODO()
|
|
||||||
ActivityType.View -> TODO()
|
else -> {
|
||||||
ActivityType.Other -> TODO()
|
throw IllegalArgumentException("$type is not supported.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,13 +39,21 @@ interface IUserService {
|
||||||
suspend fun findFollowingByNameAndDomain(name: String, domain: String?): List<User>
|
suspend fun findFollowingByNameAndDomain(name: String, domain: String?): List<User>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* フォロワーを追加する
|
* フォローリクエストを送信する
|
||||||
*
|
*
|
||||||
* @param id
|
* @param id
|
||||||
* @param follower
|
* @param followerId
|
||||||
* @return リクエストが成功したか
|
* @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
|
||||||
}
|
}
|
||||||
|
|
|
@ -111,23 +111,31 @@ class UserService(
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO APのフォロー処理を作る
|
// 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 user = userRepository.findById(id) ?: throw UserNotFoundException("$id was not found.")
|
||||||
val follower = userRepository.findById(followerId) ?: throw UserNotFoundException("$followerId 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) {
|
return if (user.domain == Config.configData.domain) {
|
||||||
userRepository.createFollower(id, followerId)
|
follow(id, followerId)
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
activityPubSendFollowService.sendFollow(SendFollowDto(follower, user))
|
if (userRepository.findFollowRequestsById(id, followerId)) {
|
||||||
|
// do-nothing
|
||||||
|
} else {
|
||||||
|
activityPubSendFollowService.sendFollow(SendFollowDto(follower, user))
|
||||||
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun unfollow(id: Long, follower: Long): Boolean {
|
override suspend fun follow(id: Long, followerId: Long) {
|
||||||
userRepository.deleteFollower(id, follower)
|
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
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
<pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||||
</encoder>
|
</encoder>
|
||||||
</appender>
|
</appender>
|
||||||
<root level="trace">
|
<root level="DEBUG">
|
||||||
<appender-ref ref="STDOUT"/>
|
<appender-ref ref="STDOUT"/>
|
||||||
</root>
|
</root>
|
||||||
<logger name="org.eclipse.jetty" level="INFO"/>
|
<logger name="org.eclipse.jetty" level="INFO"/>
|
||||||
|
|
|
@ -432,7 +432,7 @@ class UsersTest {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val userService = mock<IUserService> {
|
val userService = mock<IUserService> {
|
||||||
onBlocking { follow(eq(1235), eq(1234)) } doReturn true
|
onBlocking { followRequest(eq(1235), eq(1234)) } doReturn true
|
||||||
}
|
}
|
||||||
application {
|
application {
|
||||||
configureSerialization()
|
configureSerialization()
|
||||||
|
@ -482,7 +482,7 @@ class UsersTest {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val userService = mock<IUserService> {
|
val userService = mock<IUserService> {
|
||||||
onBlocking { follow(eq(1235), eq(1234)) } doReturn false
|
onBlocking { followRequest(eq(1235), eq(1234)) } doReturn false
|
||||||
}
|
}
|
||||||
application {
|
application {
|
||||||
configureSerialization()
|
configureSerialization()
|
||||||
|
@ -532,7 +532,7 @@ class UsersTest {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val userService = mock<IUserService> {
|
val userService = mock<IUserService> {
|
||||||
onBlocking { follow(eq(1235), eq(1234)) } doReturn false
|
onBlocking { followRequest(eq(1235), eq(1234)) } doReturn false
|
||||||
}
|
}
|
||||||
application {
|
application {
|
||||||
configureSerialization()
|
configureSerialization()
|
||||||
|
|
|
@ -116,7 +116,7 @@ class ActivityPubReceiveFollowServiceImplTest {
|
||||||
createdAt = Instant.now()
|
createdAt = Instant.now()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
onBlocking { follow(any(), any()) } doReturn false
|
onBlocking { followRequest(any(), any()) } doReturn false
|
||||||
}
|
}
|
||||||
val activityPubFollowService =
|
val activityPubFollowService =
|
||||||
ActivityPubReceiveFollowServiceImpl(
|
ActivityPubReceiveFollowServiceImpl(
|
||||||
|
|
Loading…
Reference in New Issue