mirror of https://github.com/usbharu/Hideout.git
Merge pull request #16 from usbharu/feature/ap-follow
Feature/ap follow
This commit is contained in:
commit
ae6fcbd599
|
@ -0,0 +1,5 @@
|
||||||
|
package dev.usbharu.hideout.domain.model.hideout.dto
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.domain.model.hideout.entity.User
|
||||||
|
|
||||||
|
data class SendFollowDto(val userId: User, val followTargetUserId: User)
|
|
@ -15,4 +15,8 @@ data class User(
|
||||||
val publicKey: String,
|
val publicKey: String,
|
||||||
val privateKey: String? = null,
|
val privateKey: String? = null,
|
||||||
val createdAt: Instant
|
val createdAt: Instant
|
||||||
)
|
) {
|
||||||
|
override fun toString(): String {
|
||||||
|
return "User(id=$id, name='$name', domain='$domain', screenName='$screenName', description='$description', password=****, inbox='$inbox', outbox='$outbox', url='$url', publicKey='$publicKey', privateKey=****, createdAt=$createdAt)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
package dev.usbharu.hideout.service.activitypub
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.domain.model.ActivityPubResponse
|
||||||
|
import dev.usbharu.hideout.domain.model.ap.Accept
|
||||||
|
|
||||||
|
interface ActivityPubAcceptService {
|
||||||
|
suspend fun receiveAccept(accept: Accept): ActivityPubResponse
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package dev.usbharu.hideout.service.activitypub
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.domain.model.ActivityPubResponse
|
||||||
|
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.service.impl.IUserService
|
||||||
|
import io.ktor.http.*
|
||||||
|
import org.koin.core.annotation.Single
|
||||||
|
|
||||||
|
@Single
|
||||||
|
class ActivityPubAcceptServiceImpl(private val userService: IUserService) : ActivityPubAcceptService {
|
||||||
|
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}")
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = userService.findByUrl(userUrl)
|
||||||
|
val follower = userService.findByUrl(followerUrl)
|
||||||
|
userService.follow(user.id, follower.id)
|
||||||
|
return ActivityPubStringResponse(HttpStatusCode.OK, "accepted")
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,7 +5,7 @@ import dev.usbharu.hideout.domain.model.ap.Follow
|
||||||
import dev.usbharu.hideout.domain.model.job.ReceiveFollowJob
|
import dev.usbharu.hideout.domain.model.job.ReceiveFollowJob
|
||||||
import kjob.core.job.JobProps
|
import kjob.core.job.JobProps
|
||||||
|
|
||||||
interface ActivityPubFollowService {
|
interface ActivityPubReceiveFollowService {
|
||||||
suspend fun receiveFollow(follow: Follow): ActivityPubResponse
|
suspend fun receiveFollow(follow: Follow): ActivityPubResponse
|
||||||
suspend fun receiveFollowJob(props: JobProps<ReceiveFollowJob>)
|
suspend fun receiveFollowJob(props: JobProps<ReceiveFollowJob>)
|
||||||
}
|
}
|
|
@ -16,12 +16,12 @@ import kjob.core.job.JobProps
|
||||||
import org.koin.core.annotation.Single
|
import org.koin.core.annotation.Single
|
||||||
|
|
||||||
@Single
|
@Single
|
||||||
class ActivityPubFollowServiceImpl(
|
class ActivityPubReceiveFollowServiceImpl(
|
||||||
private val jobQueueParentService: JobQueueParentService,
|
private val jobQueueParentService: JobQueueParentService,
|
||||||
private val activityPubUserService: ActivityPubUserService,
|
private val activityPubUserService: ActivityPubUserService,
|
||||||
private val userService: IUserService,
|
private val userService: IUserService,
|
||||||
private val httpClient: HttpClient
|
private val httpClient: HttpClient
|
||||||
) : ActivityPubFollowService {
|
) : ActivityPubReceiveFollowService {
|
||||||
override suspend fun receiveFollow(follow: Follow): ActivityPubResponse {
|
override suspend fun receiveFollow(follow: Follow): ActivityPubResponse {
|
||||||
// TODO: Verify HTTP Signature
|
// TODO: Verify HTTP Signature
|
||||||
jobQueueParentService.schedule(ReceiveFollowJob) {
|
jobQueueParentService.schedule(ReceiveFollowJob) {
|
|
@ -0,0 +1,7 @@
|
||||||
|
package dev.usbharu.hideout.service.activitypub
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.domain.model.hideout.dto.SendFollowDto
|
||||||
|
|
||||||
|
interface ActivityPubSendFollowService {
|
||||||
|
suspend fun sendFollow(sendFollowDto: SendFollowDto)
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package dev.usbharu.hideout.service.activitypub
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.domain.model.ap.Follow
|
||||||
|
import dev.usbharu.hideout.domain.model.hideout.dto.SendFollowDto
|
||||||
|
import dev.usbharu.hideout.plugins.postAp
|
||||||
|
import io.ktor.client.*
|
||||||
|
import org.koin.core.annotation.Single
|
||||||
|
|
||||||
|
@Single
|
||||||
|
class ActivityPubSendFollowServiceImpl(private val httpClient: HttpClient) : ActivityPubSendFollowService {
|
||||||
|
override suspend fun sendFollow(sendFollowDto: SendFollowDto) {
|
||||||
|
val follow = Follow(
|
||||||
|
name = "Follow",
|
||||||
|
`object` = sendFollowDto.followTargetUserId.url,
|
||||||
|
actor = sendFollowDto.userId.url
|
||||||
|
)
|
||||||
|
httpClient.postAp(
|
||||||
|
urlString = sendFollowDto.followTargetUserId.inbox,
|
||||||
|
username = sendFollowDto.userId.url,
|
||||||
|
jsonLd = follow
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,7 @@ package dev.usbharu.hideout.service.activitypub
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode
|
import com.fasterxml.jackson.databind.JsonNode
|
||||||
import com.fasterxml.jackson.module.kotlin.readValue
|
import com.fasterxml.jackson.module.kotlin.readValue
|
||||||
import dev.usbharu.hideout.config.Config
|
import dev.usbharu.hideout.config.Config.configData
|
||||||
import dev.usbharu.hideout.domain.model.ActivityPubResponse
|
import dev.usbharu.hideout.domain.model.ActivityPubResponse
|
||||||
import dev.usbharu.hideout.domain.model.ap.Follow
|
import dev.usbharu.hideout.domain.model.ap.Follow
|
||||||
import dev.usbharu.hideout.domain.model.job.DeliverPostJob
|
import dev.usbharu.hideout.domain.model.job.DeliverPostJob
|
||||||
|
@ -17,14 +17,15 @@ import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
@Single
|
@Single
|
||||||
class ActivityPubServiceImpl(
|
class ActivityPubServiceImpl(
|
||||||
private val activityPubFollowService: ActivityPubFollowService,
|
private val activityPubReceiveFollowService: ActivityPubReceiveFollowService,
|
||||||
private val activityPubNoteService: ActivityPubNoteService,
|
private val activityPubNoteService: ActivityPubNoteService,
|
||||||
private val activityPubUndoService: ActivityPubUndoService
|
private val activityPubUndoService: ActivityPubUndoService,
|
||||||
|
private val activityPubAcceptService: ActivityPubAcceptService
|
||||||
) : ActivityPubService {
|
) : ActivityPubService {
|
||||||
|
|
||||||
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 = Config.configData.objectMapper.readTree(json)
|
val readTree = configData.objectMapper.readTree(json)
|
||||||
logger.debug("readTree: {}", readTree)
|
logger.debug("readTree: {}", readTree)
|
||||||
if (readTree.isObject.not()) {
|
if (readTree.isObject.not()) {
|
||||||
throw JsonParseException("Json is not object.")
|
throw JsonParseException("Json is not object.")
|
||||||
|
@ -41,7 +42,7 @@ 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 {
|
||||||
return when (type) {
|
return when (type) {
|
||||||
ActivityType.Accept -> TODO()
|
ActivityType.Accept -> activityPubAcceptService.receiveAccept(configData.objectMapper.readValue(json))
|
||||||
ActivityType.Add -> TODO()
|
ActivityType.Add -> TODO()
|
||||||
ActivityType.Announce -> TODO()
|
ActivityType.Announce -> TODO()
|
||||||
ActivityType.Arrive -> TODO()
|
ActivityType.Arrive -> TODO()
|
||||||
|
@ -50,8 +51,8 @@ class ActivityPubServiceImpl(
|
||||||
ActivityType.Delete -> TODO()
|
ActivityType.Delete -> TODO()
|
||||||
ActivityType.Dislike -> TODO()
|
ActivityType.Dislike -> TODO()
|
||||||
ActivityType.Flag -> TODO()
|
ActivityType.Flag -> TODO()
|
||||||
ActivityType.Follow -> activityPubFollowService.receiveFollow(
|
ActivityType.Follow -> activityPubReceiveFollowService.receiveFollow(
|
||||||
Config.configData.objectMapper.readValue(
|
configData.objectMapper.readValue(
|
||||||
json,
|
json,
|
||||||
Follow::class.java
|
Follow::class.java
|
||||||
)
|
)
|
||||||
|
@ -72,7 +73,7 @@ class ActivityPubServiceImpl(
|
||||||
ActivityType.TentativeReject -> TODO()
|
ActivityType.TentativeReject -> TODO()
|
||||||
ActivityType.TentativeAccept -> TODO()
|
ActivityType.TentativeAccept -> TODO()
|
||||||
ActivityType.Travel -> TODO()
|
ActivityType.Travel -> TODO()
|
||||||
ActivityType.Undo -> activityPubUndoService.receiveUndo(Config.configData.objectMapper.readValue(json))
|
ActivityType.Undo -> activityPubUndoService.receiveUndo(configData.objectMapper.readValue(json))
|
||||||
ActivityType.Update -> TODO()
|
ActivityType.Update -> TODO()
|
||||||
ActivityType.View -> TODO()
|
ActivityType.View -> TODO()
|
||||||
ActivityType.Other -> TODO()
|
ActivityType.Other -> TODO()
|
||||||
|
@ -82,7 +83,7 @@ class ActivityPubServiceImpl(
|
||||||
override suspend fun <T : HideoutJob> processActivity(job: JobContextWithProps<T>, hideoutJob: HideoutJob) {
|
override suspend fun <T : HideoutJob> processActivity(job: JobContextWithProps<T>, hideoutJob: HideoutJob) {
|
||||||
logger.debug("processActivity: ${hideoutJob.name}")
|
logger.debug("processActivity: ${hideoutJob.name}")
|
||||||
when (hideoutJob) {
|
when (hideoutJob) {
|
||||||
ReceiveFollowJob -> activityPubFollowService.receiveFollowJob(job.props as JobProps<ReceiveFollowJob>)
|
ReceiveFollowJob -> activityPubReceiveFollowService.receiveFollowJob(job.props as JobProps<ReceiveFollowJob>)
|
||||||
DeliverPostJob -> activityPubNoteService.createNoteJob(job.props as JobProps<DeliverPostJob>)
|
DeliverPostJob -> activityPubNoteService.createNoteJob(job.props as JobProps<DeliverPostJob>)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,17 +2,23 @@ package dev.usbharu.hideout.service.impl
|
||||||
|
|
||||||
import dev.usbharu.hideout.config.Config
|
import dev.usbharu.hideout.config.Config
|
||||||
import dev.usbharu.hideout.domain.model.hideout.dto.RemoteUserCreateDto
|
import dev.usbharu.hideout.domain.model.hideout.dto.RemoteUserCreateDto
|
||||||
|
import dev.usbharu.hideout.domain.model.hideout.dto.SendFollowDto
|
||||||
import dev.usbharu.hideout.domain.model.hideout.dto.UserCreateDto
|
import dev.usbharu.hideout.domain.model.hideout.dto.UserCreateDto
|
||||||
import dev.usbharu.hideout.domain.model.hideout.entity.User
|
import dev.usbharu.hideout.domain.model.hideout.entity.User
|
||||||
import dev.usbharu.hideout.exception.UserNotFoundException
|
import dev.usbharu.hideout.exception.UserNotFoundException
|
||||||
import dev.usbharu.hideout.repository.IUserRepository
|
import dev.usbharu.hideout.repository.IUserRepository
|
||||||
import dev.usbharu.hideout.service.IUserAuthService
|
import dev.usbharu.hideout.service.IUserAuthService
|
||||||
|
import dev.usbharu.hideout.service.activitypub.ActivityPubSendFollowService
|
||||||
import org.koin.core.annotation.Single
|
import org.koin.core.annotation.Single
|
||||||
import java.lang.Integer.min
|
import java.lang.Integer.min
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
@Single
|
@Single
|
||||||
class UserService(private val userRepository: IUserRepository, private val userAuthService: IUserAuthService) :
|
class UserService(
|
||||||
|
private val userRepository: IUserRepository,
|
||||||
|
private val userAuthService: IUserAuthService,
|
||||||
|
private val activityPubSendFollowService: ActivityPubSendFollowService
|
||||||
|
) :
|
||||||
IUserService {
|
IUserService {
|
||||||
|
|
||||||
private val maxLimit = 100
|
private val maxLimit = 100
|
||||||
|
@ -105,9 +111,19 @@ class UserService(private val userRepository: IUserRepository, private val userA
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO APのフォロー処理を作る
|
// TODO APのフォロー処理を作る
|
||||||
override suspend fun follow(id: Long, follower: Long): Boolean {
|
override suspend fun follow(id: Long, followerId: Long): Boolean {
|
||||||
userRepository.createFollower(id, follower)
|
val user = userRepository.findById(id) ?: throw UserNotFoundException("$id was not found.")
|
||||||
return false
|
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)
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
activityPubSendFollowService.sendFollow(SendFollowDto(follower, user))
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun unfollow(id: Long, follower: Long): Boolean {
|
override suspend fun unfollow(id: Long, follower: Long): Boolean {
|
||||||
|
|
|
@ -25,13 +25,14 @@ import org.mockito.kotlin.*
|
||||||
import utils.JsonObjectMapper
|
import utils.JsonObjectMapper
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
class ActivityPubFollowServiceImplTest {
|
class ActivityPubReceiveFollowServiceImplTest {
|
||||||
@Test
|
@Test
|
||||||
fun `receiveFollow フォロー受付処理`() = runTest {
|
fun `receiveFollow フォロー受付処理`() = runTest {
|
||||||
val jobQueueParentService = mock<JobQueueParentService> {
|
val jobQueueParentService = mock<JobQueueParentService> {
|
||||||
onBlocking { schedule(eq(ReceiveFollowJob), any()) } doReturn Unit
|
onBlocking { schedule(eq(ReceiveFollowJob), any()) } doReturn Unit
|
||||||
}
|
}
|
||||||
val activityPubFollowService = ActivityPubFollowServiceImpl(jobQueueParentService, mock(), mock(), mock())
|
val activityPubFollowService =
|
||||||
|
ActivityPubReceiveFollowServiceImpl(jobQueueParentService, mock(), mock(), mock())
|
||||||
activityPubFollowService.receiveFollow(
|
activityPubFollowService.receiveFollow(
|
||||||
Follow(
|
Follow(
|
||||||
emptyList(),
|
emptyList(),
|
||||||
|
@ -118,7 +119,7 @@ class ActivityPubFollowServiceImplTest {
|
||||||
onBlocking { follow(any(), any()) } doReturn false
|
onBlocking { follow(any(), any()) } doReturn false
|
||||||
}
|
}
|
||||||
val activityPubFollowService =
|
val activityPubFollowService =
|
||||||
ActivityPubFollowServiceImpl(
|
ActivityPubReceiveFollowServiceImpl(
|
||||||
mock(),
|
mock(),
|
||||||
activityPubUserService,
|
activityPubUserService,
|
||||||
userService,
|
userService,
|
|
@ -29,7 +29,7 @@ class UserServiceTest {
|
||||||
onBlocking { hash(anyString()) } doReturn "hashedPassword"
|
onBlocking { hash(anyString()) } doReturn "hashedPassword"
|
||||||
onBlocking { generateKeyPair() } doReturn generateKeyPair
|
onBlocking { generateKeyPair() } doReturn generateKeyPair
|
||||||
}
|
}
|
||||||
val userService = UserService(userRepository, userAuthService)
|
val userService = UserService(userRepository, userAuthService, mock())
|
||||||
userService.createLocalUser(UserCreateDto("test", "testUser", "XXXXXXXXXXXXX", "test"))
|
userService.createLocalUser(UserCreateDto("test", "testUser", "XXXXXXXXXXXXX", "test"))
|
||||||
verify(userRepository, times(1)).save(any())
|
verify(userRepository, times(1)).save(any())
|
||||||
argumentCaptor<dev.usbharu.hideout.domain.model.hideout.entity.User> {
|
argumentCaptor<dev.usbharu.hideout.domain.model.hideout.entity.User> {
|
||||||
|
@ -55,7 +55,7 @@ class UserServiceTest {
|
||||||
val userRepository = mock<IUserRepository> {
|
val userRepository = mock<IUserRepository> {
|
||||||
onBlocking { nextId() } doReturn 113345L
|
onBlocking { nextId() } doReturn 113345L
|
||||||
}
|
}
|
||||||
val userService = UserService(userRepository, mock())
|
val userService = UserService(userRepository, mock(), mock())
|
||||||
val user = RemoteUserCreateDto(
|
val user = RemoteUserCreateDto(
|
||||||
"test",
|
"test",
|
||||||
"example.com",
|
"example.com",
|
||||||
|
|
Loading…
Reference in New Issue